Android中的Looper,Handler和MessageQueue之间是什么关系?


95

我已经检查了Android官方文档/指南LooperHandlerMessageQueue。但是我不明白。我是android新手,对这些概念感到非常困惑。

Answers:


103

A Looper是消息处理循环:它从中读取和处理项目MessageQueue。所述Looper类通常是在与结合使用HandlerThread(的一个子类Thread)。

A Handler是一种实用工具类,Looper主要通过将消息和Runnable对象发布到线程的上来促进与的交互MessageQueue。当Handler被创建,它被绑定到特定的Looper(和相关联的线程和消息队列)。

在典型用法中,创建并启动HandlerThread,然后创建一个Handler或多个对象,其他线程可以通过该对象与HandlerThread实例进行交互。在Handler必须同时在运行中创建HandlerThread,虽然曾经创造有什么的线程可以使用没有限制Handler的调度方法(post(Runnable)等)

在创建应用程序实例之前,将Android应用程序中的主线程(即UI线程)设置为处理程序线程。

除了类文档,有这一切的一个很好的讨论在这里

PS以上提到的所有类都在包装中android.os


@Ted Hopp-Looper的消息队列与Thread的消息队列不同吗?
CopsOnRoad

2
@杰克-他们是同一回事。的Android API文档MessageQueue指出a MessageQueue是“ 保存要由a调度的消息列表的低级类Looper
Ted Hopp '18年

95

众所周知,直接从android中的主线程之外的线程直接更新UI组件是非法的。如果我们需要启动单独的线程来执行一些昂贵的工作并在完成后更新UI,则此android文档(在UI线程中处理昂贵的操作)建议了以下步骤。这个想法是创建一个与主线程关联的Handler对象,并在适当的时候向其发布一个Runnable。这将在主线程上调用。该机制通过LooperHandler类实现。Runnable

Looper班保持的MessageQueue,其中包含一个列表的消息。Looper的一个重要特征是,它与创建线程相关联。该关联将永久保存,不能被破坏或更改。另外请注意,一个线程不能超过相关联的一个。为了保证这种关联,它存储在线程本地存储中,并且不能直接通过其构造函数创建。创建它的唯一方法是在上调用prepare静态方法。准备方法首先检查ThreadLocalLooperLooperLooperLooper当前线程以确保没有与该线程关联的Looper。检查后,将Looper创建一个新文件并将其保存在中ThreadLocal。准备好之后Looper,我们可以在其上调用循环方法来检查新消息并Handler进行处理。

顾名思义,Handler该类主要负责处理(添加,删除,调度)当前线程的消息MessageQueue。一个Handler实例也被绑定到一个线程。Handler和Thread之间绑定是通过Looper和实现的MessageQueue。阿Handler始终绑定到一个Looper,并且随后绑定到相关的螺纹与所述Looper。与不同Looper,可以将多个Handler实例绑定到同一线程。每当我们在上调用post或其他任何方法时Handler,都会向关联的中添加新消息MessageQueue。消息的目标字段设置为当前Handler实例。当。。。的时候Looper收到此消息后,它将在消息的目标字段上调用dispatchMessage,以便该消息路由回到要处理的Handler实例,但在正确的线程上。之间的关系LooperHandlerMessageQueue显示如下:

在此处输入图片说明


5
谢谢!但最新的点,如果处理程序首先发布消息到消息队列,然后处理来自同一队列中的消息?为什么不直接处理消息?
布莱克

4
@Blake b / c,您正在从一个线程(非
循环程序

developer.android.com上记录的要好得多-但是很高兴看到您提供的图表的代码。
tfmontague

@numansalati-处理程序无法从循环程序线程发布消息吗?
CopsOnRoad '18年

78

让我们从Looper开始。当您了解什么是Looper时,您可以更轻松地了解Looper,Handler和MessageQueue之间的关系。您也可以更好地了解GUI框架中的Looper。Looper可以做2件事。

1)Looper 将普通线程(该线程在其run()方法返回时终止)转换为持续运行直到Android应用程序运行(这在GUI框架中是必需的)(从技术上讲,它仍然在run()方法返回时终止。但是让我澄清一下,下面)。

2)Looper 提供了一个队列,将要完成的工作排入队列,这在GUI框架中也是必需的。

如您所知,启动应用程序时,系统会为该应用程序创建一个执行线程,称为“主线程”,而Android应用程序通常默认情况下完全在单个线程上运行,即“主线程”。但是主线程不是某个秘密的特殊线程。这只是一个普通线程,您也可以使用new Thread()代码创建它,这意味着它的run()方法返回时将终止!想想下面的例子。

public class HelloRunnable implements Runnable {
    public void run() {
        System.out.println("Hello from a thread!");
    }

    public static void main(String args[]) {
        (new Thread(new HelloRunnable())).start();
    }
}

现在,让我们将此简单原理应用于Android应用。如果Android应用程序在普通线程上运行会怎样?称为“ main”或“ UI”或任何启动应用程序的线程,并绘制所有UI。因此,第一个屏幕显示给用户。所以现在怎么办?主线程终止?不,不应该。它应该等到用户做某事,对吗?但是,我们如何实现这种行为呢?好了,我们可以尝试Object.wait()Thread.sleep()。例如,主线程完成其初始工作以显示第一个屏幕,然后进入睡眠状态。唤醒后,这意味着在获取新工作时被打断。到目前为止,一切都很好,但是目前,我们需要一个类似队列的数据结构来容纳多个作业。考虑一种情况,当用户串行触摸屏幕并且任务需要较长时间才能完成。因此,我们需要一个数据结构来容纳要以先进先出方式完成的工作。同样,您可能会想到,使用中断来实现到达线程时总是运行和处理作业并不容易,并且会导致复杂且通常无法维护的代码。我们宁愿为此目的创建一种新的机制,这就是Looper的全部意义所在Looper类正式文件说,“默认情况下,线程没有与之关联的消息循环”,而Looper是“用于为线程运行消息循环的类”。现在您可以理解它的含义了。

让我们转到Handler和MessageQueue。首先,MessageQueue是我上面提到的队列。它位于Looper内部,仅此而已。您可以使用Looper类的源代码进行检查。Looper类具有MessageQueue的成员变量。

那么,Handler是什么?如果有队列,那么应该有一种方法可以使我们将新任务加入队列,对吗?那就是Handler所做的。我们可以使用各种post(Runnable r)方法将新任务排入队列(MessageQueue)。而已。这一切都与Looper,Handler和MessageQueue有关。

我的最后一句话是,因此Looper基本上是用来解决GUI框架中出现的问题的类。但是这种需求在其他情况下也可能发生。实际上,对于多线程应用程序来说,这是一个非常著名的模式,您可以在Doug Lea的“ Java并发编程”中了解有关它的更多信息(特别是在第4.1.4节“工作线程”中会有所帮助)。另外,您可以想象这种机制在Android框架中并不是唯一的,但是所有GUI框架可能都需要与此类似。您可以在Java Swing框架中找到几乎相同的机制。


4
最佳答案。从此详细说明中了解到更多信息。我想知道是否有一些更详细的博客文章。
上尉赃物

可以在不使用Handler的情况下将消息添加到MessageQueue吗?
CopsOnRoad '18

@CopsOnRoad否,不能直接添加它们。
Faisal Naseer

让我开心...对你有很多爱:)
Rahul Matte

26

MessageQueue:这是一个低级类,其中包含要由调度的消息列表Looper。消息不是直接添加到MessageQueue,而是通过HandlerLooper。[ 3 ] 关联的对象添加。

Looper:循环遍历MessageQueue包含要分派的消息的。管理队列的实际任务是由Handler负责完成的,后者负责处理(添加,删除,分派)消息队列中的消息。[ 2 ]

Handler:它允许您发送和处理MessageRunnable线程关联的对象MessageQueue。每个Handler实例都与一个线程和该线程的消息队列关联。[ 4 ]

当创建一个new时Handler,它将绑定到创建它的线程的线程/消息队列上-从那时起,它将把消息和可运行对象传递到该消息队列,在它们从消息队列中出来时执行它们

请通过下面的图片[ 2 ]更好地理解。

在此处输入图片说明


0

如前所述,用@K_Anas扩展答案,并举例说明

众所周知,直接从android中的主线程之外的线程直接更新UI组件是非法的。

例如,如果您尝试使用Thread更新UI。

    int count = 0;
    new Thread(new Runnable(){
        @Override
        public void run() {
            try {
                while(true) {
                    sleep(1000);
                    count++;
                    textView.setText(String.valueOf(count));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


   ).start();

您的应用将崩溃,并异常。

android.view.ViewRoot $ CalledFromWrongThreadException:只有创建视图层次结构的原始线程才能触摸其视图。

换句话说,您需要使用Handler保留对MainLooper ie Main Thread或的引用UI Thread,并将任务传递为Runnable

  Handler handler = new Handler(getApplicationContext().getMainLooper);
        int count = 0;
        new Thread(new Runnable(){
            @Override
            public void run() {
                try {
                    while(true) {
                        sleep(1000);
                        count++;
                        handler.post(new Runnable() {
                           @Override
                           public void run() {
                                 textView.setText(String.valueOf(count));
                           }
                         });

                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    ).start() ;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.