网上有很多高手分析Handler、Looper、MessageQueue、Message的文章,之前看过好多但是实话实说不是自己亲身看源码,很多东西都是走马观花、一知半解。
今天有空自己从数据流转的起始来简单分析下这三个东西是怎样的关系,是怎么组织起来来运转的。这样学到的东西才真正是自己的,从这个角度分析感觉思路更清楚。
废话不多说这就开始。
首先我们都知道Handler的最典型和最重要的使用场景。那其实就是子线程做耗时操作,然后在主线程中更新UI数据。那你使用Handler的时候一般都是先在子线程中做完了耗时的操作调用sendMessage()把结果发出去,最终再在handleMessage()中拿到该结果去更新UI。
那我们今天的分析就紧紧围绕着“入口”sendMessage()和“出口”handleMessage()展开。来学习一下执行的中间环节都发生了什么。
经过这一套分析,你一定能清清楚楚地回答好下述问题:
1.MessageQueue的数据结构是什么?
2.什么情况下Handler会造成内存泄漏?
3.为什么子线程中直接创建Handler会崩溃?
4.为什么异步Message会优先执行?
5.什么是同步屏障机制?
6.一个线程最多可以有几个Looper?
我们先从sendMessage()开始
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
该方法的注释非常关键
Pushes a message onto the end of the message queue after all pending messages before the current time. It will be received in handleMessage, in the thread attached to this handler.
Returns:
Returns true if the message was successfully placed in to the message queue. Returns false on failure, usually because the looper processing the message queue is exiting.
意思是说,将一个Message塞入MessageQueue,塞入的位置在所有待处理的消息之后,但是在当前时间前(就是说该消息不能插队到前面待处理的消息前,但是会放到当前时间前的位置)。该消息最终会被handleMessage()方法接到,当线程已经被附加到Handler上的时候。
这里你可以知道消息队列并不是入队到队尾,因为sendMessage()方法实际上调用的是sendMessageDelayed()方法,有的人认为该方法是发送延迟消息,这样理解不对。应该说发送消息且该消息延迟执行。那你大胆猜测消息肯定是有个时间标记的,根据时间的先后来管理,时间在前的肯定在MessageQueue的前面,时间在后的肯定在MessageQueue的后面。
那有些人就说了,我在调用最外面调用sendMessageDelayed()传一个负的延迟时间不就可以往前插队了吗。那不行的,往下看。
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
延迟时间设置为负数跟设置为0的效果一样。接着往sendMessageAtTime()中走。
public boolean sendMessageAtTime(@NonNull 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);
}
注意这里的mQueue,说明Handler是持有MessageQueue这个成员变量的,没有mQueue的Handler是无法分发消息的。再往enqueueMessage()走。
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
发现根据mAsynchronous是否异步的标记给msg设置了是否异步的属性。然后执行MessageQueue的enqueueMessage()方法。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
首先msg.target为空的话就报错了,这个target大家可以进去Message看,其实就是Message持有的Handler。这里也好理解,你要是msg没有target那你最终让谁来接消息并handleMessage()呢。
如果mQuitting为true的时候代表线程正在退出,那此时一样没法分发消息,那就回收消息并返回false告诉消息分发失败。
上面阻断语句都没走到就接着往下走,可以看到
msg.when = when;
这里像我们之前猜测的一样,给msg打上了时间标记when。
接下来如果当前消息队列头的消息mMessages为空,或者msg的when时间时间是0,或者当前我们分发的msg消息的when比mMessages的when小(msg应该先执行,插入到mMessages前),那队头消息就变成了我们的msg消息。
假如不是上述的情况走到else里,第一句就很关键
needWake = mBlocked && p.target == null && msg.isAsynchronous();
是否唤醒的标记needWake要是为true的话就必须同时满足mBlocked为true(当前阻塞)且p.target == null(队头消息为空)且我们待处理的msg消息为异步消息。其他情况的时候needWake都会为false。
接下来走进一个无参的for循环中
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
简单看下上面的代码,你是不是想到了什么?
消息队列存消息肯定是用的链表结构,然后链表中的节点位置还是根据when来决定的,哪个消息的when小哪个排在前面。
仔细看下for里面的内容,什么时候break跳出循环呢。当链表遍历的时候,没有节点了或者遍历到某个节点的when比当前我们待分发地msg的when大了,就跳出循环了。啥意思呢,就是说我现在把链表遍历一遍,找到第一个比msg的when的大的这个节点就结束循环。那找这个节点干啥呢?因为跳出循环后,就把msg插入到第一个比msg的when大的节点前了。这样其实就根据when的比较把msg放到了消息队列中的正确位置。
假如前面遍历的节点的when比msg的when小(msg只能插入这些节点之后),那肯定接着循环,那就要判断
if (needWake && p.isAsynchronous()) {
needWake = false;
}
这句是说如果当前的msg是异步消息,前面判断出来的needWake为true,但是此时比它早执行的消息p也是异步消息,那此时needWake就设置为false。
这里我这样理解,假如比msg早执行的消息中已经有了异步消息,那他就会唤醒消息队列的处理,这里就不需要再唤醒了。后面就根据needWake决定是否唤醒MessageQueue,因为是native方法无法再跟进去看。线索就断了。
但是我们都知道Looper又是持有MessageQueue的,会不会是msg放到MessageQueue中的正确位置后后面就是由Looper唤醒来处理MessageQueue中的消息呢。我们调到Looper中去看看。看Looper还是先看注释,非常关键。
Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call prepare in the thread that is to run the loop, and then loop to have it process messages until the loop is stopped.
Most interaction with a message loop is through the Handler class.
意思是该类是给线程来执行消息循环的。默认的一些普通线程是没有关联的Looper的,要给这些线程创建一个Looper的话就要在调用线程run()方法体的最前面调用Looper的prepare()方法,然后接着调用Looper的loop()方法来让Looper处理那些消息,这个处理过程当Looper被停止的时候才会停止。消息loop处理的大部分交互式是通过Handler类来做的。
这里官方还在注释中给了个示例写法
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler(Looper.myLooper()) {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
进到Looper.prepare()中看看。
public static void prepare() {
prepare(true);
}
再进去看看
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));
}
又有大发现,首先你发现了你prepare()创建Looper的时候只能执行一次,进一步说一个Thread只能有一个Looper,再进一步说Looper.prepare()出来的Looper是和当前的Thread关联的。如果你针对特定Thread创建了多个Looper,因为该Thread的ThreadLocal中已经有了Looper,那就一定报以下错误。
throw new RuntimeException("Only one Looper may be created per thread")
同时注意下线程中绑定的Looper是允许退出的。
(这里注意应用的UI线程或者说主线程是不需要我们手动创建Looper的,因为Android在设计时已经为应用的UI线程创建了一个Looper,并且那个Looper是无法手动关闭的。这里很好理解,如果UI线程的Looper被关闭了肯定是应用进程已经死了。否则UI线程的Looper假如被关闭的话那你让它怎么响应和处理众多的Message。这块大家可以再去网上搜搜其他高手的分析,这里不再展开。))
那我们目前有两个疑问还没解决。创建并塞进ThreadLocal中的Looper后面肯定是要用的,它是怎么用的?前面分析过的异步消息在消息处理中有什么特殊的地方?
那我们接着看下Looper关键的loop()方法。
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
@SuppressWarnings("AndroidFrameworkBinderIdentity")
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
if (me.mInLoop) {
Slog.w(TAG, "Loop again would have the queued messages be executed"
+ " before this one completed.");
}
me.mInLoop = true;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);
me.mSlowDeliveryDetected = false;
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
final Looper me = myLooper();
这一句实际是从ThreadLocal把Looper对象给拿出来了。
如果拿出来为null,也就是你自己创建的子线程不主动调用Looper.prepare()就开始调用Looper.loop()方法的话这里就会报
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
略过中间步骤,再往下看又遇到一个所谓的死循环
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
那我们进去一探究竟,这里只保留关键代码。
/**
* Poll and deliver single message, return true if the outer loop should continue.
*/
@SuppressWarnings("AndroidFrameworkBinderIdentity")
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return false;
}
...
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;
...
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
...
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
...
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
...
}
...
msg.recycleUnchecked();
return true;
}
首先拿到me(Looper)的MessageQueue并且取到下一个需要处理的Message赋值给msg(这里的MessageQueue的next()方法也很关键,我们放在后面分析)。
如果此时msg为空那说明没有要处理的消息,那就不再往下走了,返回的false也会结束掉外部的死循环。
否则往下走,后面的代码大意就是将该msg分发出去处理一下。其中最关键的一句就是。
msg.target.dispatchMessage(msg);
msg.target就是Message持有的Handler,原来这里最终还是利用Handler来分发和处理了msg。进dispatchMessage()看看。
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
这里其实最终可能就就走到了handleMessage()只不过你最初“入口”调用发送消息的时候Handler也提供了多种方式,比如还有post()方式,不同的调用方式这里最终触发的处理也不一样而已。但是总体来看我们已经从“入口”sendMessage()走到了“出口”handleMessage()。
但是还没完哦!!!
此时我们还欠了一个MessageQueue的next()方法没分析。接着进去看这个超长的方法。
Message next() {
...
for (;;) {
...
synchronized (this) {
// Try to retrieve the next message. Return if found.
...
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
...
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
...
}
...
}
}
该方法do..while..就是先遍历找到异步消息来返回,没有异步消息再找到下一个非异步消息(普通的同步消息)返回。我们如果深入了解就知道这里有个所谓的sync barrier同步屏障机制。可以理解为MessageQueue中的异步消息是优先级最高的,Looper在loop()方法中每次从MessageQueue的next()方法来拿下一条要dispatch的Message时都要优先先拿出异步消息,此时已经和消息队列MessageQueue中Message的when关系不大了。可以理解为异步消息有“特权”,就是会优先被拿出来处理,那普通的同步消息不就好像被“阻塞”住了。
那有人就要问了,什么样的消息是异步消息。这个建议大家深入地去看下Message类中什么时候会调用setAsynchronous(true)。全局搜一下其实挺多的,特别是一些比如点击等的UI操作吧。
至此,我们就从“入口”sendMessage()完全走到了“出口”handleMessage()。
由于分析过程实在是太长了,因此也不好总结,但我觉得我的分析思路是对的,从“入口”进得去,也从“出口”出得来。
接下来通过上面的分析,我们一一解决一下文首的几个问题,检验下我们读源码的成果。
MessageQueue的数据结构是什么?
答:MessageQueue的数据结构类似单向链表,每个节点都是一个Message,这里的Message有可能是普通的同步消息,也可能是异步消息。根据Message的when时间字段来决定Message在MessageQueue中的位置,when越小的在MessageQueue中越靠前,一般会越早分发,但是异步消息由于有同步屏障机制会最先被分发。
什么情况下Handler会造成内存泄漏?
答:首先内存泄漏指的是本应该回收掉的内存没有被及时回收导致这部分内存无法释放。当内部类Handler发送消息且消息有延迟的时候,如果此时Activity关闭了,就会导致内存泄漏。因为消息有延迟的,它又持有target,这个target就是Handler,那也就导致Handler被引用从而回收不了。那因为Handler又是普通的内部类(非静态内部类),那内部类因为持有外部类的引用,因此Activity又被Handler引用,因此虽然Activity被关闭了理应回收内存,但是实际回收不掉。处理的方式就是Handler使用静态内部类的写法,对Activity持有一个WeakedReferences弱引用。
为什么子线程中直接创建Handler会崩溃?
答:子线程中直接创建Handler,因为该子线程没有手动调用Looper.prepare()那就没有绑定Looper,Handler也就没有Looper。系统机制为了安全在Handler构造器中做了Looper的非空校验保护。
public Handler(@Nullable Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
为什么异步Message会优先执行?
答:因为Android有同步屏障机制。MessageQueue中的消息分为普通同步消息和异步消息,Looper的loop()方法中拿MessageQueue中的下一条消息是调用next()方法,该方法会先把对消息队列中的异步消息拿出来优先分发执行,没有异步消息的时候才逐一处理同步消息。此时和消息在消息队列中的顺序无关,异步消息就是身份特殊,可以先同步消息处理。
什么是同步屏障机制?
答:见上个问题。一些面试官在你简单答了Message放入消息队列后依次取出dispatch的时候,喜欢问你为啥动画正在执行或者MessageQueue队列中还有未执行的Message,但是你点了按钮依然可以执行点击事件呢,那你就知道了这个所谓的点击事件对应的消息一定是异步消息,此时触发了同步屏障,异步消息当然优先执行了。
一个线程最多可以有几个Looper?
答:一个线程有且只有一个Looper。主线程的Looper不用手动添加和关闭。子线程的Looper一定要手动添加和关闭。