我已经检查了官方的 Android 文档/指南中的Looper
,Handler
和MessageQueue
. 但我无法得到它。我是 android 新手,对这些概念非常困惑。
5 回答
ALooper
是一个消息处理循环:它从 a 读取和处理项目MessageQueue
。该类Looper
通常与HandlerThread
(的子类Thread
)结合使用。
AHandler
是一个实用程序类,它有助于与 a 进行交互——主要是Looper
通过将消息和Runnable
对象发布到线程的MessageQueue
. 创建a 时Handler
,它会绑定到特定的Looper
(以及关联的线程和消息队列)。
在典型用法中,您创建并启动一个HandlerThread
,然后创建一个Handler
对象(或多个对象),其他线程可以通过该对象与该HandlerThread
实例进行交互。Handler
必须在 上运行时创建,尽管一旦创建,对哪些线程可以使用的调度方法(等)HandlerThread
没有限制Handler
post(Runnable)
Android 应用程序中的主线程(也称为 UI 线程)在创建应用程序实例之前设置为处理程序线程。
除了类文档,这里有一个很好的讨论。
PS 上面提到的所有类都在包中android.os
。
众所周知,直接从android 中的主线程以外的线程更新 UI 组件是非法的。这个 android 文档(在 UI 线程中处理昂贵的操作)建议了如果我们需要启动一个单独的线程来执行一些昂贵的工作并在完成后更新 UI 时要遵循的步骤。这个想法是创建一个与主线程关联的Handler对象,并在适当的时候向它发布一个Runnable 。这将在主线程上调用。这个机制是用Looper和Handler类实现的。Runnable
该类Looper
维护一个MessageQueue,其中包含一个消息列表。Looper 的一个重要特性是它与创建的线程相关联。这种关联永远保持,不能被打破也不能改变。另请注意,一个线程不能与多个. 为了保证这种关联,存储在线程本地存储中,不能直接通过其构造函数创建。创建它的唯一方法是在. prepare 方法首先检查ThreadLocalLooper
Looper
Looper
Looper
的当前线程,以确保没有与该线程关联的 Looper。检查后,将创建一个新Looper
的并保存在ThreadLocal
. 准备好之后Looper
,我们可以在其上调用循环方法来检查新消息并Handler
进行处理。
顾名思义,Handler
该类主要负责处理(添加、删除、调度)当前线程的消息MessageQueue
。Handler
实例也绑定到线程。Handler 和 Thread 之间的绑定是通过Looper
和实现的MessageQueue
。AHandler
始终绑定到a Looper
,然后绑定到与关联的线程Looper
。与 不同Looper
的是,多个 Handler 实例可以绑定到同一个线程。每当我们在 上调用post或任何类似方法时Handler
,都会将新消息添加到关联的MessageQueue
. 消息的目标字段设置为当前Handler
实例。当。。。的时候Looper
收到此消息,它在消息的目标字段上调用dispatchMessage,以便消息路由回要处理的 Handler 实例,但在正确的线程上。和之间Looper
的关系如下图所示:Handler
MessageQueue
让我们从 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()
orThread.sleep()
. 例如,主线程完成其初始工作以显示第一个屏幕,然后进入睡眠状态。当一个新的工作被获取时,它会被唤醒,这意味着被中断。到目前为止一切顺利,但此时我们需要一个类似队列的数据结构来保存多个作业。考虑一个用户连续触摸屏幕的情况,一项任务需要更长的时间才能完成。因此,我们需要一个数据结构来保存要以先进先出方式完成的工作。此外,您可能会想象,使用中断实现 ever-running-and-process-job-when-arrived 线程并不容易,并且会导致复杂且通常无法维护的代码。我们宁愿为此目的创建一个新机制,这就是 Looper 的全部意义所在。Looper类的官方文档说,“默认情况下,线程没有与之关联的消息循环”,而 Looper 是一个“用于为线程运行消息循环”的类。现在你可以理解它的意思了。
让我们转到 Handler 和 MessageQueue。首先,MessageQueue 就是我上面提到的队列。它位于 Looper 中,仅此而已。您可以使用Looper 类的源代码检查它。Looper 类有一个MessageQueue 的成员变量。
那么,什么是Handler?如果有队列,那么应该有一种方法可以让我们将新任务排入队列,对吗?这就是处理程序所做的。post(Runnable r)
我们可以使用各种方法将新任务排入队列(MessageQueue) 。而已。这都是关于 Looper、Handler 和 MessageQueue 的。
我的最后一句话是,所以基本上 Looper 是一个用于解决 GUI 框架中出现的问题的类。但这种需求也可能发生在其他情况下。实际上,它是多线程应用程序中非常著名的模式,您可以在 Doug Lea 的“Java 中的并发编程”中了解更多信息(特别是第 4.1.4 章“工作线程”会有所帮助)。另外,你可以想象这种机制在 Android 框架中并不是唯一的,但所有的 GUI 框架都可能需要与此有点类似。您可以在 Java Swing 框架中找到几乎相同的机制。
MessageQueue
:它是一个低级类,保存要由 发送的消息列表Looper
。消息不是直接添加到 aMessageQueue
中,而是通过与.[ 3 ]Handler
关联的对象添加Looper
Looper
: 它循环遍历MessageQueue
包含要发送的消息的 a。管理队列的实际任务由Handler
负责处理(添加、删除、调度)消息队列中的消息的对象完成。 [ 2 ]
Handler
:它允许您发送和处理Message
与Runnable
线程关联的对象MessageQueue
。每个 Handler 实例都与单个线程和该线程的消息队列相关联。[ 4 ]
当您创建一个 newHandler
时,它会绑定到创建它的线程的线程/消息队列——从那时起,它会将消息和可运行对象传递到该消息队列并在它们从消息队列中出来时执行它们.
请浏览下图 [ 2 ] 以便更好地理解。
用一个例子扩展答案,@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
which 保持对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() ;