我查看了官方Android文档/指南中关于Looper
、Handler
和MessageQueue
的内容,但我并没有理解透彻。作为一个Android新手,我对这些概念感到很困惑。
我查看了官方Android文档/指南中关于Looper
、Handler
和MessageQueue
的内容,但我并没有理解透彻。作为一个Android新手,我对这些概念感到很困惑。
Looper
是一个消息处理循环:它从 MessageQueue
中读取并处理项目。通常情况下,Looper
类与 HandlerThread
(Thread
的子类)一起使用。
一个 Handler
是一个实用程序类,它简化了与 Looper
交互的过程,主要是通过将消息和 Runnable
对象发布到线程的 MessageQueue
中。当创建一个 Handler
时,它会绑定到特定的 Looper
上(以及相关联的线程和消息队列)。
在典型的使用中,您可以创建和启动一个 HandlerThread
,然后通过其他线程创建一个 Handler
对象(或多个对象)来与 HandlerThread
实例进行交互。必须在运行 HandlerThread
时创建 Handler
,但一旦创建,对于使用 Handler
的调度方法(例如 post(Runnable)
)的线程没有限制。
在 Android 应用程序中,主线程(也称为 UI 线程)在创建应用程序实例之前设置为处理程序线程。
除了类文档之外,这里 有一段很好的讨论所有这些内容。
P.S. 上述所有类都在包 android.os
中。
让我们从 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是“用于运行线程的消息循环”的类。现在您可以理解它的含义了。post(Runnable r)
方法将新任务排入队列(MessageQueue)。这就是关于Looper、Handler和MessageQueue的全部内容。众所周知,在Android中,除了主线程以外的其他线程直接更新UI组件是非法的。这份Android文档(处理UI线程中的昂贵操作)建议我们遵循一些步骤,如果我们需要启动一个单独的线程来执行一些昂贵的工作,并在完成后更新UI。这个想法是创建一个与主线程相关联的Handler对象,并在适当的时间将一个Runnable发布到它上面。这个Runnable
将在主线程上被调用。这个机制是使用Looper和Handler类实现的。
Looper
类维护一个包含消息列表messages的MessageQueue。Looper的一个重要特点是它与创建它的线程相关联。这种关联是永久保持的,不能被打破或更改。还要注意的是,一个线程不能与多个Looper相关联。为了保证这种关联,Looper存储在线程本地存储中,并且不能直接通过其构造函数创建。唯一创建它的方法是调用Looper
上的prepare静态方法。prepare方法首先检查当前线程的ThreadLocal,以确保没有已经与线程关联的Looper。检查后,将创建一个新的Looper
并保存在ThreadLocal中。准备好Looper
后,我们可以调用loop方法来检查新的消息,并让Handler
处理它们。Handler
类主要负责处理(添加、删除、分发)当前线程的 MessageQueue
中的消息。一个 Handler
实例也与线程绑定。Handler 和线程之间的绑定是通过 Looper
和 MessageQueue
实现的。一个 Handler
总是绑定到一个 Looper
上,并随后绑定到与 Looper
相关联的 线程。与 Looper
不同,多个 Handler 实例可以绑定到同一线程上。每当我们在 Handler
上调用 post 或任何类似方法时,都会向关联的 MessageQueue
添加一个新消息。消息的目标字段设置为当前 Handler
实例。当 Looper
接收到此消息时,它会在消息的目标字段上调用 dispatchMessage,以便将消息路由回到 Handler 实例进行处理,但在正确的线程上。下面显示了 Looper
、Handler
和 MessageQueue
之间的关系:
MessageQueue
(消息队列):它是一个低级别的类,保存着要由Looper
(循环器)派遣的消息列表。消息不是直接添加到MessageQueue
中,而是通过与Looper
相关联的Handler
对象进行添加。3
Looper
(循环器):它循环遍历包含要派发的消息的MessageQueue
。实际管理队列的任务由Handler
执行,它负责在消息队列中处理(添加、删除、派发)消息。2
Handler
(处理器):它允许您发送和处理与线程的MessageQueue
关联的Message
(消息)和Runnable
(可运行对象)。每个Handler实例都与单个线程及其消息队列相关联。4
当您创建新的Handler
时,它会绑定到创建它的线程/消息队列上--从那时起,它将向该消息队列传递消息和可运行对象,并按照它们从消息队列出来的顺序执行它们。
为了更好地理解,请查看下面的图片2。
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 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() ;
MessageQueue
指出,MessageQueue
是“_一个低级类,它持有要由Looper
分派的消息列表_”。 - Ted Hopp