【问题标题】:What is the relationship between Looper, Handler and MessageQueue in Android?Android中Looper、Handler和MessageQueue是什么关系?
【发布时间】:2012-10-04 09:12:02
【问题描述】:

我已经查看了LooperHandlerMessageQueue 的官方 Android 文档/指南。但我无法得到它。我是android新手,对这些概念很困惑。

【问题讨论】:

    标签: android handler message-queue looper


    【解决方案1】:

    Looper 是一个消息处理循环:它从MessageQueue 读取和处理项目。 Looper 类通常与HandlerThreadThread 的子类)结合使用。

    Handler 是一个实用类,它有助于与Looper 进行交互——主要是通过将消息和Runnable 对象发布到线程的MessageQueue。在创建 Handler 时,它会绑定到特定的 Looper(以及关联的线程和消息队列)。

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

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

    除了课程文档,还有一个很好的讨论here

    附:上面提到的所有类都在包android.os中。

    【讨论】:

    • @Ted Hopp - Looper 的消息队列与 Thread 的消息队列不同吗?
    • @Jack - 他们是一回事。 Android API docs for MessageQueue 声明 MessageQueue 是一个“低级类,包含由 Looper 发送的消息列表。
    【解决方案2】:

    众所周知,直接从android中的主线程以外的线程更新UI组件是违法的。如果我们需要启动一个单独的线程来完成一些昂贵的工作并在完成后更新 UI,这个 android 文档 (Handling Expensive Operations in the UI Thread) 建议了要遵循的步骤。这个想法是创建一个与主线程关联的Handler对象,并在适当的时候向它发布一个Runnable。这个Runnable 将在主线程 上调用。这种机制是通过LooperHandler 类实现的。

    Looper 类维护一个MessageQueue,其中包含一个列表messages。 Looper 的一个重要特征是它与 Looper 在其中创建的线程关联。此关联永久保留,无法中断或更改。另请注意,线程不能与多个Looper 关联。为了保证这种关联,Looper 存储在线程本地存储中,不能直接通过其构造函数创建。创建它的唯一方法是在Looper 上调用prepare 静态方法。 prepare 方法首先检查当前线程的ThreadLocal 以确保没有与该线程关联的 Looper。检查后,会创建一个新的Looper,并保存在ThreadLocal 中。准备好Looper之后,我们可以调用loop方法来检查新消息并让Handler处理它们。

    顾名思义,Handler类主要负责处理(添加、删除、调度)当前线程的MessageQueue的消息。 Handler 实例也绑定到线程。 Handler和Thread之间的绑定是通过LooperMessageQueue实现的。 Handler总是绑定到Looper,然后绑定到与Looper 关联的线程。与Looper 不同,多个 Handler 实例可以绑定到同一个线程。每当我们调用postHandler 上的任何类似方法时,都会向关联的MessageQueue 添加一条新消息。消息的目标字段设置为当前Handler 实例。当Looper 收到此消息时,它会在消息的目标字段上调用dispatchMessage,以便消息路由回要处理的Handler 实例,但在正确的线程上。 LooperHandlerMessageQueue之间的关系如下图所示:

    【讨论】:

    • 谢谢!但是,如果处理程序首先将消息发布到消息队列,然后处理来自同一队列的消息,那又有什么意义呢?为什么不直接处理消息?
    • @Blake b/c 您从一个线程(非循环线程)发帖,但在另一个线程(循环线程)中处理消息
    • developer.android.com 上记录的要好得多 - 但很高兴看到您提供的图表的代码。
    • @numansalati - Can't Handler post messages from the looper thread?
    【解决方案3】:

    让我们从 Looper 开始。了解了 Looper 是什么,就更容易理解 Looper、Handler 和 MessageQueue 的关系了。您还可以更好地理解 Looper 在 GUI 框架的上下文中是什么。 Looper 是用来做两件事的。

    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()。例如,主线程完成其初始工作以显示第一个屏幕,然后进入睡眠状态。当一个新的工作被获取时,它会被唤醒,这意味着被中断。到目前为止一切顺利,但此时我们需要一个类似队列的数据结构来保存多个作业。考虑一个用户连续触摸屏幕的情况,一项任务需要更长的时间才能完成。因此,我们需要一个数据结构来保存要以先进先出方式完成的工作。此外,您可能会想象,使用中断实现 everrunning-and-process-job-when-arrived 线程并不容易,并且会导致复杂且通常无法维护的代码。我们宁愿为此目的创建一个新机制,这就是 Looper 的全部意义official document of Looper class 说,“线程默认情况下没有与之关联的消息循环”,而 Looper 是一个“用于为线程运行消息循环”的类。现在你可以理解它的意思了。

    让我们转到 Handler 和 MessageQueue。首先,MessageQueue 就是我上面提到的队列。它位于 Looper 中,仅此而已。您可以使用Looper class's source code 进行检查。 Looper 类有一个MessageQueue 的成员变量。

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

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

    【讨论】:

    • 最佳答案。从这个详细的解释中学到了更多。我想知道是否有一些更详细的博客文章。
    • 不使用Handler可以将消息加入MessageQueue吗?
    • @CopsOnRoad 不,它们不能直接添加。
    • 让我度过了美好的一天...非常爱你 :)
    【解决方案4】:

    MessageQueue:它是一个低级类,保存由Looper 发送的消息列表。消息不是直接添加到MessageQueue,而是通过与Looper 关联的Handler 对象。[3]

    Looper:它循环遍历包含要发送的消息的MessageQueue。管理队列的实际任务由Handler完成,它负责处理(添加、删除、调度)消息队列中的消息。[2]

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

    当您创建一个新的Handler 时,它会绑定到创建它的线程的线程/消息队列——从那时起,它将向该消息队列传递消息和可运行文件在它们从消息队列中出来时执行它们

    请浏览下图[2] 以便更好地理解。

    【讨论】:

      【解决方案5】:

      通过@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 的引用,即Main 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() ;
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-08-05
        • 2014-09-02
        • 2011-07-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多