在Android中,Looper、Handler和MessageQueue三者之间有什么关系?

110

我查看了官方Android文档/指南中关于LooperHandlerMessageQueue的内容,但我并没有理解透彻。作为一个Android新手,我对这些概念感到很困惑。

5个回答

114
一个 Looper 是一个消息处理循环:它从 MessageQueue 中读取并处理项目。通常情况下,Looper 类与 HandlerThreadThread 的子类)一起使用。

一个 Handler 是一个实用程序类,它简化了与 Looper 交互的过程,主要是通过将消息和 Runnable 对象发布到线程的 MessageQueue 中。当创建一个 Handler 时,它会绑定到特定的 Looper 上(以及相关联的线程和消息队列)。

在典型的使用中,您可以创建和启动一个 HandlerThread,然后通过其他线程创建一个 Handler 对象(或多个对象)来与 HandlerThread 实例进行交互。必须在运行 HandlerThread 时创建 Handler,但一旦创建,对于使用 Handler 的调度方法(例如 post(Runnable))的线程没有限制。

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

除了类文档之外,这里 有一段很好的讨论所有这些内容。

P.S. 上述所有类都在包 android.os 中。


@Ted Hopp - Looper的消息队列和Thread的消息队列有什么不同吗? - CopsOnRoad
2
@Jack - 它们是同一件事。Android API文档中的MessageQueue指出,MessageQueue是“_一个低级类,它持有要由Looper分派的消息列表_”。 - Ted Hopp

99

让我们从 Looper 开始。当您了解了 Looper 是什么时,就可以更轻松地理解 Looper、Handler 和 MessageQueue 之间的关系。在 GUI 框架的上下文中,您也可以更好地理解 Looper 的含义。Looper 的作用主要有两点:

1)Looper将一个正常的线程(其run()方法返回时终止)转换为一种在 Android 应用程序运行时持续运行的线程,这在 GUI 框架中是必需的(严格来说,在run()方法返回时它仍将终止。但是我会在下面澄清我的意思)。

2)Looper提供了一个队列,其中排队执行的任务,在 GUI 框架中也是必需的。

当应用程序启动时,系统为应用程序创建一个执行线程,称为“main”,而 Android 应用程序通常默认在单个线程上完全运行,“main”线程就是这样的一个线程。但是“main”线程不是某个秘密的特殊线程。实际上,它只是一个普通的线程,您也可以使用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框架中找到几乎相同的机制。

6
最佳答案。通过这个详细的解释我学到了更多。我想知道是否有一篇博客文章更详细地探讨了这个问题。 - capt.swag
消息队列是否可以在不使用Handler的情况下添加消息? - CopsOnRoad
@CopsOnRoad 不,它们不能直接添加。 - Faisal Naseer
让我开心了一整天...给你很多爱 :) - Rahul Matte

99

众所周知,在Android中,除了主线程以外的其他线程直接更新UI组件是非法的。这份Android文档(处理UI线程中的昂贵操作)建议我们遵循一些步骤,如果我们需要启动一个单独的线程来执行一些昂贵的工作,并在完成后更新UI。这个想法是创建一个与主线程相关联的Handler对象,并在适当的时间将一个Runnable发布到它上面。这个Runnable将在主线程上被调用。这个机制是使用LooperHandler类实现的。

Looper类维护一个包含消息列表messagesMessageQueue。Looper的一个重要特点是它与创建它的线程相关联。这种关联是永久保持的,不能被打破或更改。还要注意的是,一个线程不能与多个Looper相关联。为了保证这种关联,Looper存储在线程本地存储中,并且不能直接通过其构造函数创建。唯一创建它的方法是调用Looper上的prepare静态方法。prepare方法首先检查当前线程的ThreadLocal,以确保没有已经与线程关联的Looper。检查后,将创建一个新的Looper并保存在ThreadLocal中。准备好Looper后,我们可以调用loop方法来检查新的消息,并让Handler处理它们。
如其名称所示,Handler 类主要负责处理(添加、删除、分发)当前线程的 MessageQueue 中的消息。一个 Handler 实例也与线程绑定。Handler 和线程之间的绑定是通过 LooperMessageQueue 实现的。一个 Handler 总是绑定到一个 Looper 上,并随后绑定到与 Looper 相关联的 线程。与 Looper 不同,多个 Handler 实例可以绑定到同一线程上。每当我们在 Handler 上调用 post 或任何类似方法时,都会向关联的 MessageQueue 添加一个新消息。消息的目标字段设置为当前 Handler 实例。当 Looper 接收到此消息时,它会在消息的目标字段上调用 dispatchMessage,以便将消息路由回到 Handler 实例进行处理,但在正确的线程上。下面显示了 LooperHandlerMessageQueue 之间的关系:

enter image description here


7
谢谢!但如果处理程序首先将消息发布到消息队列,然后再从同一队列处理消息,这样做的意义是什么?为什么不直接处理消息呢? - Blake
4
因为你正在一个线程(非Looper线程)发布内容,但是在另一个线程(Looper线程)处理该消息。 - numan salati
比https://developer.android.com上记录的要好得多-但能够看到您提供的图表的代码会很不错。 - tim-montague
@numansalati - 无法从循环线程处理发布消息? - CopsOnRoad
一张票无法体现这个答案应得的赞赏 :( - hardik9850

28

MessageQueue(消息队列):它是一个低级别的类,保存着要由Looper(循环器)派遣的消息列表。消息不是直接添加到MessageQueue中,而是通过与Looper相关联的Handler对象进行添加。3

Looper(循环器):它循环遍历包含要派发的消息的MessageQueue。实际管理队列的任务由Handler执行,它负责在消息队列中处理(添加、删除、派发)消息。2

Handler(处理器):它允许您发送和处理与线程的MessageQueue关联的Message(消息)和Runnable(可运行对象)。每个Handler实例都与单个线程及其消息队列相关联。4

当您创建新的Handler时,它会绑定到创建它的线程/消息队列上--从那时起,它将向该消息队列传递消息和可运行对象,并按照它们从消息队列出来的顺序执行它们

为了更好地理解,请查看下面的图片2

enter image description here


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,它保持对 MainLooperMain ThreadUI 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() ;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接