Handler、Looper与MessageQueue分析

本文内容节选自《Android从小工到专家——第三章:App流畅度——多线程》,内容稍有修改

我们知道在Android应用启动时,会默认有一个主线程(UI线程),在这个线程中会关联一个消息队列,所有的操作都会被封装成消息然后交给主线程来处理。
为了保证主线程不会主动退出,会将获取消息的操作放在一个死循环中,这样,程序就不会退出.

Handler、Looper、Message有啥关系?
在子线程中完成耗时操作,很多情况下需要更新UI,最常用的就是通过Handler将一个消息Post到UI线程中,然后再在Handler的handlerMessage方法中进行处理。而每个Handler都会关联一个消息队列(MessageQueue),Looper负责的就是创建一个MessageQueue,而每个Looper又会关联一个线程(Looper通过ThreadLocal封装)。默认情况下,MessageQueue只有一个,即主线程的消息队列。

源码解读

ActivityThread主线程中启动启动消息循环Looper

在ActivityThread的main方法中:

public static void main(String[] args) {
// 代码省略
Process.setArgV0("<pre-initialized>");

Looper.prepareMainLooper(); //1.创建消息循环Looper

ActivityThread thread = new ActivityThread();
thread.attach(false);

if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler(); // UI线程的Handler
}

if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}

Looper.loop(); // 2.执行消息循环

throw new RuntimeException("Main thread loop unexpectedly exited");
}

ActivityThread通过Looper.prepareMainLooper()创建主线程的消息队列,最后执行Looper.loop()来启动消息队列。Handler关联消息队列和线程。

在 API 22 之后,Google 删除了 AsyncTask.init() 方法,main 函数中也不再调用它。

具体可以看这篇文章:AsyncTask 解析

执行ActivityThread.main方法后,应用程序就启动了,并且会一直从消息队列中取消息,然后处理消息,使得系统运转起来。
那么系统是如何将消息投递到消息队列,又是如何从消息中获取消息并处理的呢?答案就是Handler

Handler关联消息队列和线程

消息队列被封装在Looper中,而每个Looper又会关联一个线程(Looper通过ThreadLocal封装),最终就等于每个消息队列会关联一个线程。

那Handler是如何关联到消息队列以及线程的呢?请看下面的源代码:

public Handler() {
//代码省略
mLooper = Looper.myLooper(); //获取Looper
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}

mQueue = mLooper.mQueue; //获取消息队列
mCallback = null;
}

Handler会在内部通过Looper.getLooper()方法来获取Looper对象,并且与之关联,并获取消息队列。那么Looper.getLooper()如何工作的呢?

public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}

public static @NonNull MessageQueue myQueue() {
return myLooper().mQueue;
}
public static void prepare() {
prepare(true);
}

//为当前线程设置一个Looper
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}

//设置UI线程的Looper
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}

// Looper的私有构造方法
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}

在Looper类中,myLooper()方法,通过sThreadLocal.get()来获取的,在prepareMainLooper()中调用prepare()方法,在这个方法中创建了一个Looper对象,并将对象设置了sThreadLocal()。这样队列就和线程关联起来了。通过sThreadLocal.get()方法,保证不同的线程不能访问对方的消息队列。

再回到Handler,消息队列通过Looper与线程关联上,而Handler又与Looper关联,因此,Handler最终就和线程、线程的消息队列关联上了。

为什么要更新UI的Handler必须在主线程中创建?
因为Handler要与主线程的消息队列关联上,这样handlerMessage才会执行在UI线程,此时UI线程才是安全的。

消息循环,消息处理

消息循环的建立就是通过Looper.loop()方法。源代码如下:

/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue; //1.获取消息队列

// 代码省略
for (;;) { //2.死循环,即消息循环
//3.获取消息,可能阻塞
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}

// 代码省略
try {
msg.target.dispatchMessage(msg); //4.处理消息
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
// 代码省略
msg.recycleUnchecked(); //回收消息
}
}

从上述程序我们可以看出,loop()方法的实质上是建立一个死循环,然后通过从消息队列中逐个取出消息,最后处理消息。对于Looper:通过Looper.prepare()来创建Looper对象(消息队列封装在Looper对象中),并且保存在sThreadLocal中,然后通过通过Looper.loop()进行消息循环,这两步通常成对出现。

消息处理机制

在上面代码中通过 msg.target.dispatchMessage(msg) 来处理消息,其中msg是Message类型,源码如下:

public final class Message implements Parcelable {
//target处理
Handler target;
//Runnable类型的callback
Runnable callback;
//下一条消息,消息队列是链式存储的
Message next;

//代码省略
}

从源码中可以看出,target是Handler类型。实际上就是转了一圈,通过Handler发送消息给消息队列,消息队列又将消息分发给Handler处理。在Handler类中:

//消息处理函数,子类覆写
public void handleMessage(Message msg) {
}

private static void handleCallback(Message message) {
message.callback.run();
}

//分发消息
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

从上述程序可以看出,dispatchMessage只是一个分发的方法,如果Run nable类型的callback为空,则执行handleMessage来处理消息,该方法为空,我们会将更新UI的代码写在该函数中;
如果callback不为空,则执行handleCallback来处理,该方法会调用callback (Runnable对象) 的run方法。

其实这是Handler分发的两种类型,比如post(Runnable callback)则callback就不为空,当我们使用Handler来sendMessage时通常不设置callback,因此,执行handlerMessage。

我们看看这两种实现:

public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis); // 将消息插入到消息队列
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; // Message的target就是该Handler本身
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);// 调用MessageQueue的加入消息方法
}

从上述程序可以看到,在post(Runnable r)时,会将Runnable包装成Message对象,并且将Runnable对象设置给Message对象的callback,最后会将该对象插入消息队列。sendMessage也是类似实现:

public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}

不管是post一个Runnable还是Message,都会调用sendMessageDelayed(msg, time)方法。Handler最终将消息追加到MessageQueue中,而Looper不断地从MessageQueue中读取消息,并且调用Handler的dispatchMessage分发消息,这样消息就源源不断地被产生、添加到MessageQueue、被Handler处理,Android应用就运转起来了。

子线程正确创建Handler

在上面Handler的构造方法中看到,如果mLooper = Looper.myLooper(),mLooper为空,则会抛出异常。正确使用方法应该是:

new Thread(){
Handler handler = null;
public void run () {
//为当前线程创建Looper,并且绑定到ThreadLocal中
Looper.prepare()
handler = new Handler();
//启动消息循环
Looper.loop();
};
}.start();

如果只创建Looper不启动消息循环,虽然不抛出异常,但是通过handler来post或者sendMessage()也不会有效。因为虽然消息会被追加到消息队列,但是并没有启动消息循环,也就不会从消息队列中获取消息并且执行了。

为什么说MessageQueue 和 Looper 是一对一关系,Handler 和 Looper 是多对一?

首先,在上面Looper的私有构造方法,每一个Looper对象都会new MessageQueue()来创建一个MessageQueue,所以是一对一关系没错。
为什么Handler和Looper可以是多对一呢?这是因为Handler的构造方法中,会获取当前线程的Looper对象(由ThreadLocal保存的),一个线程Looper对象只有一个,但是可以创建多个Handler对象,所以是多对一

Toast在子线程的用法

Toast在子线程直接使用,会报错:

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:200)
at android.os.Handler.<init>(Handler.java:114)
at android.widget.Toast$TN$2.<init>(Toast.java:345)
at android.widget.Toast$TN.<init>(Toast.java:345)
at android.widget.Toast.<init>(Toast.java:103)
at android.widget.Toast.makeText(Toast.java:262)
at com.agehua.loveprints.view.main.MainActivity$ShowExceptionThread.run(MainActivity.java:127)

回到前面看下Handler的构造函数,可以发现报这个错误的原因是 mLooper 为null。所以正确的用法应该是:

private class ShowExceptionThread extends Thread {
private String ex;

ShowExceptionThread(String ex) {
this.ex = ex;
}

@Override
public void run() {
Looper.prepare();
Toast.makeText(AndroidApplication.getInstance().getApplicationContext(), "Error Encountered: " + ex, Toast.LENGTH_LONG).show();
Looper.loop();
}
}

关于为什么Toast里面会需要Handler,可以看这篇博客:Android Toast源码分析



本文采用知识共享署名 2.5 中国大陆许可协议进行许可,欢迎转载,但转载请注明来自Agehua’s Blog,并保持转载后文章内容的完整。本人保留所有版权相关权利。

本文链接:http://agehua.github.io/2018/08/22/Handler-Looper-MessageQueue/

Share Comments