我查看了Looper
,Handler
和MessageQueue
的官方Android文档/指南。但我无法得到它。我是android新手,对这些概念非常困惑。
答案 0 :(得分:101)
Looper
是一个消息处理循环:它从MessageQueue
读取和处理项目。 Looper
类通常与HandlerThread
(Thread
的子类)一起使用。
Handler
是一个实用程序类,它有助于与Looper
进行交互 - 主要是通过将消息和Runnable
对象发布到线程的MessageQueue
。创建Handler
时,它将绑定到特定的Looper
(以及关联的线程和消息队列)。
在典型用法中,您创建并启动HandlerThread
,然后创建一个Handler
对象(或多个对象),其他线程可以通过该对象与HandlerThread
实例进行交互。必须在Handler
上运行时创建HandlerThread
,但一旦创建,对于哪些线程可以使用Handler
的调度方法(post(Runnable)
等等没有限制。 )
在创建应用程序实例之前,Android应用程序中的主线程(a.k.a. UI线程)被设置为处理程序线程。
除了课程文档之外,对所有这些here进行了很好的讨论。
P.S。上面提到的所有类都在包android.os
。
答案 1 :(得分:91)
众所周知,直接从android中的主线程以外的线程更新UI组件是违法的。如果我们需要启动一个单独的线程来执行一些昂贵的工作并在完成后更新UI,则此Android文档(Handling Expensive Operations in the UI Thread)建议遵循的步骤。我们的想法是创建一个与主线程相关联的Handler对象,并在适当的时候向其发布Runnable。此Runnable
将在主线程上调用。此机制使用Looper和Handler类实现。
Looper
班级维护MessageQueue,其中包含列表messages。 Looper的一个重要特征是关联与创建Looper
的线程。这种关联永远保持,不能破坏也不能改变。另请注意,线程无法与一个Looper
相关联。为了保证这种关联,Looper
存储在线程本地存储中,并且不能通过其构造函数直接创建它。创建它的唯一方法是在Looper
上调用prepare静态方法。 prepare方法首先检查当前线程的ThreadLocal,以确保没有与该线程关联的Looper。考试结束后,将创建一个新的Looper
并保存在ThreadLocal
中。准备好Looper
后,我们可以在其上调用loop方法来检查新邮件并让Handler
来处理它们。
如名称所示,Handler
类主要负责处理(添加,删除,调度)当前线程MessageQueue
的消息。 Handler
实例也绑定到一个线程。 Handler和Thread 之间的绑定是通过Looper
和MessageQueue
实现的。 Handler
始终绑定到 a Looper
,然后绑定到与<{1}}关联的线程。与Looper
不同,多个Handler实例可以绑定到同一个线程。每当我们在Looper
上调用post或任何方法时,都会向关联的Handler
添加新消息。消息的目标字段设置为当前MessageQueue
实例。当Handler
收到此消息时,它会在消息的目标字段上调用dispatchMessage,以便消息路由回要处理的Handler实例,但是在正确的线程上。
Looper
,Looper
和Handler
之间的关系如下所示:
答案 2 :(得分:70)
让我们从Looper开始吧。当您了解Looper是什么时,您可以更轻松地理解Looper,Handler和MessageQueue之间的关系。您还可以更好地了解Looper在GUI框架中的作用。 Looper可以做两件事。
1)Looper 转换正常线程,在其run()
方法返回时终止,终止为持续运行直到Android应用运行的内容,这是在GUI框架中需要(从技术上讲,当run()
方法返回时它仍然会终止。但是让我澄清一下我的意思,下面)。
2)Looper 提供了一个队列,其中要完成的作业被排队,这在GUI框架中也是必需的。
正如您所知,当应用程序启动时,系统会为应用程序创建一个执行线程,称为“main”,而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应用程序在普通线程上运行会发生什么?一个名为&#34; main&#34;的线程或&#34; UI&#34;或者任何启动应用程序,并绘制所有UI。因此,第一个屏幕显示给用户。所以现在怎么办?主线程终止了吗?不,它不应该。它应该等到用户做某事,对吧?但是我们怎样才能实现这种行为呢?好吧,我们可以尝试使用Object.wait()
或Thread.sleep()
。例如,主线程完成其初始作业以显示第一个屏幕,然后休眠。当需要执行新工作时,它会唤醒,这意味着中断。到目前为止一切顺利,但此时我们需要一个类似队列的数据结构来保存多个作业。想想用户连续触摸屏幕的情况,任务需要更长的时间才能完成。因此,我们需要一个数据结构来保持以先进先出的方式完成的工作。此外,您可以想象,使用中断实现永远运行和进程的作业到达线程并不容易,并导致复杂且通常无法维护的代码。我们宁愿为此目的创建一个新机制,而 就是Looper所关注的 。 official document of Looper class表示,&#34;默认情况下,线程没有与它们关联的消息循环&#34;,而Looper是一个类&#34;用于为线程&#34;运行消息循环。现在你可以理解它的含义了。
让我们转到Handler和MessageQueue。首先,MessageQueue是我上面提到的队列。它位于Looper里面,就是它。您可以使用Looper class's source code进行检查。 Looper类有一个MessageQueue的成员变量。
然后,什么是汉德勒?如果有一个队列,那么应该有一个方法可以让我们将新任务排入队列,对吗?这就是汉德勒的作用。我们可以使用各种post(Runnable r)
方法将新任务排入队列(MessageQueue)。就是这样。这完全是关于Looper,Handler和MessageQueue。
我的最后一句话是,所以基本上Looper是一个用来解决GUI框架中出现的问题的类。但是这种需求也可能在其他情况下发生。实际上它是一个非常着名的多线程应用程序模式,您可以在#34; Java中的并发编程&#34;作者:Doug Lea(尤其是第4.1.4章和第34章;工人主题&#34;会有所帮助)。此外,您可以想象这种机制在Android框架中并不是唯一的,但是所有GUI框架可能都需要与此类似。您可以在Java Swing框架中找到几乎相同的机制。
答案 3 :(得分:25)
MessageQueue
:它是一个低级类,包含要由Looper
分派的消息列表。邮件不会直接添加到MessageQueue
,而是通过与Handler
相关联的Looper
个对象。[3]
Looper
:它遍历MessageQueue
,其中包含要分派的消息。管理队列的实际任务由Handler
完成,Handler
负责处理(添加,删除,调度)消息队列中的消息。[2]
Message
:它允许您发送和处理与线程Runnable
关联的MessageQueue
和Handler
个对象。每个Handler实例都与一个线程和该线程的消息队列相关联。[4]
当你创建一个新的{{1}}时,它被绑定到正在创建它的线程的线程/消息队列 - 从那时起,它将消息和runnables传递给该消息队列和在消息队列出来时执行它们。
请注意下面的图片[2]以便更好地理解。
答案 4 :(得分:4)
MessageQueue是一个消息循环或消息队列,它基本上包含一个消息或Runnables列表(可执行代码集)。
换句话说,MessageQueue是一个队列,其中包含应该处理的称为消息的任务。
注意: Android在主线程上维护一个MessageQueue。
<强> 活套 强>
Looper是为当前线程提供MessageQueue的工作者。 Looper遍历消息队列并将消息发送给相应的处理程序进行处理。
任何线程只能有一个唯一的Looper,这个约束是通过使用ThreadLocal存储的概念来实现的。
Looper和MessageQueue的好处
使用Looper和MessageQueue有一些优点,如下所述 -
·使用MessageQueue执行是顺序的,因此在并发线程的情况下,这将避免竞争条件。
·正常情况下,一旦完成作业,线程就无法重用。但是,在调用quit方法之前,使用Looper的线程一直处于活动状态,因此每次要在后台运行作业时都不需要创建新实例。
<强>处理程序:强>
Handler允许与来自其他后台线程的UI线程进行通信。这在android中很有用,因为android不允许其他线程直接与UI线程通信。
Handler允许您发送和处理与线程的MessageQueue关联的Message和Runnable对象。每个Handler实例都与一个线程和该线程的消息队列相关联。
当你创建一个新的Handler时,它被绑定到正在创建它的线程的线程/消息队列 - 从那时起,它将消息和runnables传递给该消息队列并在它们出来时执行它们。消息队列。
Handler有两个主要用途:
(1)安排消息和runnables作为将来的某个点执行。换句话说,将来在同一个线程上执行操作。
(2)将要在不同于自己的线程上执行的操作排入队列。换句话说,将一个动作排队在不同的线程上执行。
答案 5 :(得分: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
的引用,即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() ;