@JeromeLiee
2020-01-22T23:32:22.000000Z
字数 19232
阅读 1353
在Android应用中,消息机制可谓是处于举足轻重的地步,因为UI是Android的整个门面展示,而UI的展示是交由消息机制来处理。Android不允许在子线程中进行UI处理,因为这样会引发多线程的安全问题,而解决这个问题则需要做加锁等操作,这样会导致效率低下,造成UI不流畅等问题,这是万万不可接受的。
说到Android消息机制的用途,你可能会想到子线程和主线程的通信、延迟发送一个消息或执行一个Runnable
等,但你有没有想过,它是如何实现子线程和主线程的通信?子线程和子线程之间是否能通过消息机制来进行通信?延迟发送或执行的内部原理又是如何实现的?另外你可能听过这样的问题——主线程在Looper.loop()
中开启了一个死循环,为什么不会造成ANR(Application Not Responding)
?从MessageQueue
中取出消息时可能会阻塞,为什么该阻塞也不会造成ANR?这些问题归根结底就是原理问题,在看完本篇文章后都会茅塞顿开,so follow me!
消息的发送到处理可以大致分为5个步骤,分别是初始化准备工作、发送消息、消息入队、Looper
循环和消息出队,以及消息处理,我们一步一步来看。
平时我们在使用Handler
发送消息时,只需要创建一个Handler
对象,然后调用相应的发送方法即可,使用起来特别简单。但其实在创建Handler
对象之前,主线程已经做了一些准备工作,其中就有MessageQueue
和Looper
的创建初始化,并且将它们存放在主线程的私有内存中。接下来从源码中分析,首先来看Handler
的构造方法:
// 构造方法1
public Handler() {
this(null, false);
}
// 构造方法2
public Handler(Callback callback) {
this(callback, false);
}
// 构造方法3
public Handler(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;
}
// 构造方法4
public Handler(Looper looper) {
this(looper, null, false);
}
// 构造方法5
public Handler(Looper looper, Callback callback) {
this(looper, callback, false);
}
// 构造方法6
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
有6个构造方法,我们先主要看构造方法1
和构造方法3
,其余构造方法会在后面讲解。其中,构造方法1
调用了构造方法3
,然后在构造方法3
中,注意mLooper = Looper.myLooper()
这行代码,获取了一个Looper
对象,然后接下来就对该Looper
对象进行了null
判断,如果为null
则抛出RunTime异常
:
Can't create handler inside thread xxx that has not called Looper.prepare()
因为没有调用Looper.preapre()
方法,所以在xxx
这个线程中不能创建Handler
对象
你会想哎这不对啊?我平时创建Handler
时也没遇到过啊。其实前面说过了,主线程早已帮我们做了这些初始化的准备工作了,具体的代码需要去Looper
类里看看。
首先看下Looper
类的构造方法
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
在Looper
的构造方法里,创建了一个MessageQueue
对象,获取了当前的Thread
对象。但该构造方法是私有的,如何创建Looper
对象呢?其实在上一小结中的Runtime异常
中已经告诉了答案,即调用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()
方法调用了prepare(boolean quitAllowed)
方法,而该方法里也只有3行代码。首先判断当前线程是否已经创建了Looper
对象,如果是则抛异常:
Only one Looper may be created per thread
否则创建一个,并且将其存放到当前线程的私有内存中。如果你对ThreadLocal
不太熟悉且想进一步了解的话,可以阅读 Java之ThreadLocal详解 这篇文章。
prepare()
方法的作用就是在当前线程中创建一个Looper
对象,并且创建关联一个MessageQueue
对象,然后通过ThreadLocal
将这个关联了MessageQueue
对象的Looper
对象存放到当前线程的私有内存中,请记住,这是实现线程间通信的根本。文章后面会将这块同整个消息机制串联起来,届时就会很清楚地理解了整个消息机制逻辑。
另外,主线程的初始化Looper
对象的方法如下,基本上和prepare()
方法大同小异:
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
该方法是在ActivityThread
类中的main()
方法中调用,这是应用的入口方法,启动时便会调用。所以说主线程的消息发送不需要手动调用Looper.prepare()
方法,因为主线程早就做了这些准备工作。
// ActivityThread类,此方法为应用程序的入口方法
public static void main(String[] args) {
...省略部分代码
// 创建初始化Looper
Looper.prepareMainLooper();
...省略部分代码
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...省略部分代码
// 开启消息循环
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
注意到该方法中倒数第二行调用了Looper.loop()
方法,它是一个死循环,会一直调用消息队列MessageQueue
的next()
方法获取Message
,然后交由Handler
处理。此处先知道其作用即可,后面第4章节会详细介绍Looper.loop()
方法。
现在我们来整理下,一个完整的消息机制的初始化准备工作基本上有以下3个步骤:
- 调用
Looper.prepare()
方法,创建一个关联了MessageQueue
的Looper
对象,并通过ThreadLocal
将其存放在当前线程的私有内存中,这是保证多线程间通信的根本;- 创建一个
Handler
对象;- 调用
Looper.loop()
方法,开启死循环从MessageQueue
中获取消息,该方法的调用时机也可以放在步骤2之前。
以上便是消息机制的初始化准备工作,接下来便可以进行发送消息的操作了。
初始化准备过程已经完成了,接下来就可以发送消息。在发送一条消息时,我们可以调用Handler
的以下send
方法来实现:
// 发送方法1.发送一条消息
public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}
// 发送方法2.发送一条延迟处理的消息
public final boolean sendMessageDelayed(Message msg, long delayMillis){
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// 发送方法3.发送一条空消息
public final boolean sendEmptyMessage(int what){
return sendEmptyMessageDelayed(what, 0);
}
// 发送方法4.发送一条延迟处理的空消息
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
也可以调用post
方法来投递一个Runnable
,但其本质上也是发送了一条消息:
// 发送方法5.投递一个Runnable
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
// 发送方法6.投递一个延迟处理的Runnable
public final boolean postDelayed(Runnable r, long delayMillis){
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
// 将Runnable转为Message
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
方法5
和方法6
虽然是投递一个Runnable
,但实质上是通过getPostMessage(Runnable r)
方法,将Runnable
封装到了Message
的callback
变量中,最终也是发送了一个Message
。
上面6种发送消息的方法,其中
方法1
内部调用了方法2
;
方法3
调用了方法4
,而方法4
内部也调用了方法2
;
方法5
和方法6
内部也是调用了方法2
;
可以看到send方法或post方法最终都指向了方法2
,那么接下来就分析方法2——sendMessageDelayed(Message msg, long delayMillis)
:
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;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// MessageQueue的消息入队
return queue.enqueueMessage(msg, uptimeMillis);
}
可以看到,sendMessageDelayed(Message msg, long delayMillis)
方法内部调用了sendMessageAtTime(Message msg, long uptimeMillis)
方法,其中参数uptimeMillis
是一个时间参考,用来表示什么时候该Message
会被执行。
一条延迟处理的消息,其对应的执行时间uptimeMillis
等于开机运行时间SystemClock.uptimeMillis()
加上延迟执行的时间delayMillis
(非延迟消息的delayMillis
值为0),最终调用enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)
方法。
接下来在enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)
方法中,会将当前Handler
对象封装至Message
的target
变量,注意此处,后面在第五章节消息处理时会再回顾这行代码。最后调用MessageQueue
的enqueueMessage(Message msg, long when)
方法中,进行消息入队操作。至此,Handler
中的消息发送过程已经完成了。
发送消息的过程还是比较简单的,简单整理如下:
- 通过
post
系列方法或send
系列方法发送一个消息Message
;- 如果是延迟消息,则该消息的
执行时间=开机运行时间+延迟执行时间
,否则执行时间=开机运行时间
;- 最后将当前
Handler
对象封装至Message
的target
中,再调用MessageQueue
的enqueueMessage(Message msg, long when)
方法进行入队操作。
消息发送完毕,接下来就是消息入队操作,对应的代码是MessageQueue
的enqueueMessage()
方法:
boolean enqueueMessage(Message msg, long when) {
...省略部分代码
synchronized (this) {
...省略部分代码
msg.markInUse();
msg.when = when;
// 获取Message队列的头部
// 注意:此队列实质上是一个单向链表,目的是为了更方便地插入和移除消息
Message p = mMessages;
boolean needWake;
// 满足以下3个条件之一,便会将当前Message设为队列头:
// 1.队列头为空,即该队列为空;
// 2.when为0,该值可以手动赋值,一般我们用不到;
// 3.当前要入队的消息执行的时间早于队列头
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
// 一个新的队列头,如果当前队列阻塞则唤醒,mBocked为true表示队列是阻塞状态
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.
// 一般来说不需要唤醒队列的阻塞状态,除非队列头是一个同步屏障(barrier),且当前的Message是异步的,则根据阻塞状态决定是否需要唤醒队列
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 该循环的目的是按照when从小到大的顺序,找到Message的位置
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.
// mPtr是native层的MessageQueue的引用地址,是在MessageQueue的构造方法里初始化的
// 这样便可以将native层和java层的对象关联起来
// 如果needWake=true,则通过nativeWake(mPtr)方法唤醒阻塞中的队列,唤醒之后的操作,将在下节消息出队中讲解
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
消息入队的操作还是相对来说比较简单的,即:
如果当前消息队列为空,或插入的
Message
执行时间when
早于队列头的Message
,则将其置为消息队列首部,并且将队列从阻塞状态中唤醒;
否则按照Message
的执行时间排序,将该Message
插入到队列中。
注意到needWake = mBlocked && p.target == null && msg.isAsynchronous()
这行代码,涉及到消息机制的同步屏障,这里简单讲解一下。
在UI线程中,其主要目的就是保证及时有效地刷新UI。假设现在需要刷新UI,但主线程的消息队列中还存在其它的消息,那么就需要保证优先执行UI刷新的消息,屏蔽其它非UI相关的,同步屏障就起到了这样的作用。
一般来说我们发送消息时,最终会在Handler
的enqueueMessage()
方法中将当前Handler对象封装至Message
的target
中,但同步屏障消息是没有Handler
的,可以调用MessageQueue
的postSyncBarrier()
来发送一个消息屏障:
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
可以看到内部并没有设置给Message
设置Handler
,而且依旧是按照消息的执行时间when
来排序插入到队列中,默认是插入队列头部。移除同步屏障调用MessageQueue
的removeSyncBarrier(int token)
方法即可,其内部源码就不贴出来了,感兴趣可自行查看。
一般我们发送的消息是同步(synchronous)的,有两种方式可以设置发送异步消息:
Handler
的构造方法3
、构造方法6
,将构造参数async
设为true
即可。通过这种方式,发送的所有消息都是异步的。Message
的setAsynchronous(boolean async)
方法设置为true
。通过这种方式,当前发送的消息是异步的。同步屏障的作用就是屏蔽消息队列中该同步屏障之后的所有同步消息,只处理异步消息,保证异步消息优先执行,其具体代码逻辑见4.2 消息出队。
同步屏障用于UI绘制,在ViewRootImpl
类的scheduleTraversals()
方法中调用:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// UI绘制之前设置一个同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 发送绘制的消息,保证优先执行mTraversalRunnable
// 最终会将该Runnable对象封装至Message中,并设置该Message为异步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
当优先执行了mTraversalRunnable
,调用其run()
方法后,run()
方法内部会调用doTraversal()
方法,该方法内移除了之前设置的同步屏障,然后执行UI绘制操作方法performTraversals()
:
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除之前设置的同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// 进行UI绘制
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
在1.3小节的消息机制初始化准备小节中,我们提到了Looper.loop()
调用,其作用是开启一个消息循环,然后从MessageQueue
队列中取出消息交由Handler
处理。把它放到现在来讲是因为loop()
方法和消息出队next()
操作紧密相连,我们先看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;
...省略部分代码
for (;;) {
// 当消息队列中没有消息或延迟执行消息时,MessageQueue的next()方法会阻塞
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...省略部分代码
try {
// 进行消息处理
// 此target便是Handler#enqueueMessage(MessageQueue, Message, long)方法中第一行代码 msg.target = this
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...省略部分代码
// Message回收
msg.recycleUnchecked();
}
}
该方法内部实现还是比较简单的:首先做了一些检验工作,然后开启一个死循环。在死循环中调用MessageQueue
的next()
方法获取消息,如果有则交由其封装的Handler
处理(其处理逻辑见5. 消息处理),没有则阻塞。具体的阻塞和消息出队,都在MessageQueue
的next()
方法实现,进去看一看吧。
Message next() {
第一部分:查找获取消息
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
// 在3.1 消息入队源码分析章节中,我们知道了mPtr是native层的MessageQueue的引用地址
// 通过这个引用地址,可以将native层和java层关联起来
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
// 用于统计当前闲置Handler数量
int pendingIdleHandlerCount = -1; // -1 only during first iteration
// 阻塞的时长
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 实现阻塞,阻塞时长为nextPollTimeoutMillis,Looper.loop()方法中的might block就是来自这里
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// msg.target == null表示该Message是一个屏障(barrier)。
// 如果是屏障,则跳过该屏障之后所有的同步消息,只执行异步消息
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
// 从队列中找出下一个异步Message
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.
// 该Message执行时间还未到,所以需要设置阻塞时长
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
// 取出需要执行的Message
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
// 如果当前队列没有消息,则将nextPollTimeoutMillis设为-1
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
第二部分:处理IdleHandler
// 消息队列为空或Message未到执行时间时,则开始处理IdleHandler
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
// 执行IdleHandler中的queueIdle()方法
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
next()
方法代码内部也是一个死循环,代码比较长,我们分成两部分逻辑来分析:一部分是前半段查找获取消息的逻辑,另一部分是为后半段处理IdleHandler
的逻辑。
在死循环内,首先判断队列头是否为消息屏障,是则找出下一个异步的消息,否则取队列头消息。然后判断取出的消息执行时间when
:
如果执行时间没到,则设置阻塞时长,等下次循环时进行阻塞,否则取出该消息并立刻返回。
阻塞的代码为nativePollOnce(ptr, nextPollTimeoutMillis)
,这是一个native
方法,nextPollTimeoutMillis
表示延迟时长:
nativePollOnce(ptr, nextPollTimeoutMillis)
方法,会立刻返回不会阻塞,然后继续执行后面的代码;nativePollOnce(ptr, nextPollTimeoutMillis)
会一直阻塞,除非有消息入队则触发唤醒;nextPollTimeoutMillis
毫秒,在这期间如果有新的消息入队则可能触发唤醒(新的消息执行时间早于nextPollTimeoutMillis
则会唤醒)。唤醒的操作由第3节消息入队的nativeWake(mPtr)
方法实现。入队唤醒和出队阻塞的方法都是native
方法,由Linux
的epoll
机制实现,感兴趣可阅读《深入理解Android 卷III》第二章 深入理解Java Binder和MessageQueue 这篇文章中的2.3小节。
当消息队列为空或Message
未到执行时间时,则处理IdleHandler
。IdleHandler
可用于消息队列闲置时的处理,例如ActivityThread
中的GcIdler
,用于触发主线程中的GC
垃圾回收,当主线程没有消息处理时,就会有可能触发GC
。
// ActivityThread类中的GcIdler内部类
final class GcIdler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
doGcIfNeeded();
return false;
}
}
在这一章节中,Looper.loop()
循环方法主要是调用MessageQueue
的next()
方法获取Message
,然后交由对应的Hanlder
处理。
MessageQueue
队列如果为空,则一直阻塞,等待下次消息入队唤醒队列;不为空时,当消息的执行时间未到,则进行nextPollTimeoutMillis>0
时长的阻塞,直到阻塞时间结束,或有新的消息入队,且其执行时间早于当前阻塞的消息执行时间,则唤醒队列。
接下来则看最后一个步骤,关于消息的处理逻辑。
在Looper.loop()
方法中,从MessageQueue
中获取到一条不为空的消息时,调用了msg.target.dispatchMessage(msg)
进行消息分发处理,此时又回到了Handler
中,看下dispatchMessage(Message msg)
方法:
// Handler.java
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
首先会判断msg.callback
是否为null
,这个callback
就是封装的Runnable
对象,即Hanlder.post
系列方法投递的Runnable
。如果不为空,则执行Runnable
的run()
方法。
否则,则判断mCallback
是否为null
。这个mCallback
是什么东西呢?可以回顾下Handler
的构造方法,其中构造方法2、3、5、6
都有一个构造参数Callback
,这个一个接口:
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public boolean handleMessage(Message msg);
}
如果Callback
接口的方法handleMessage(Message msg)
返回为true
,则不再继续分发消息,否则调用Handler
的handlerMessage(Message msg)
方法,这是一个空方法,一般选择在Handler
的子类实现:
public void handleMessage(Message msg) {
}
一句话总结消息处理的逻辑:
post
系列方法,则执行其Runnable
的run()
方法;Handler
构造方法里传入的Callback
是否返回为ture
: true
则消息处理结束;false
则继续分发给Handler
的handleMessage(Message msg)
,然后结束。当需要移除一个Message
或Runnable
时,调用Handler
对应的remove
方法即可,其内部调用的是MessageQueue
对应的remove
方法。我们选择Handler.removeMessages(int what)
这个方法来分析,其它移除逻辑基本一致。
// Handler.java
// 移除消息队列中所有满足Message.what=what的消息
public final void removeMessages(int what) {
mQueue.removeMessages(this, what, null);
}
// MessageQueue.java
// 上面Handler的remove方法调用的是该方法
void removeMessages(Handler h, int what, Object object) {
if (h == null) {
return;
}
synchronized (this) {
Message p = mMessages;
// Remove all messages at front.
// 此while循环移除回收了从队列头开始,连续满足移除条件的消息
while (p != null && p.target == h && p.what == what
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
// Remove all messages after front.
// 否则在此while循环中移除回收之后的消息
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && n.what == what
&& (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
主要是用了两个while
循环来移除消息,第一个移除前面连续满足移除条件的消息,后面则依次判断移除满足条件的消息。
注意这里面有个暗坑,如果队列里有延迟执行的消息,其中有通过sendDelay
发送的what=0
的消息,也有通过postDelay
投递的Runnable
,如果调用Handler.removeMessages(0)
方法来移除what=0
的所有消息,很不幸你会发现,队列中的所有Runnable
封装的消息也会被移除。原因是封装Runnable
的Message
,其what
默认为0,正好满足移除what=0
消息的逻辑,所以定义what
时需要注意,避免定义为0。
一个完整的消息机制从开始到结束的细节差不多就分析完了,现在我们将整个过程串起来,简要回顾一番:
Looper.prepare()
方法初始化创建一个Looper
对象,其内部会同时创建了一个与之关联的MessageQueue
对象,然后通过ThreadLocal
将该Looper
对象存放至当前线程的私有内存中。接着手动创建一个Handler
,用于发送和处理消息,可以通过构造方法传入之前创建的Looper
;也可以不传,则会使用当前线程私有内存中存放的Looper
对象。接着手动调用Looper.loop()
方法,开启一个死循环,会一直调用MessageQueue
的next()
方法获取消息。send
系列方法或post
方法,最终会将消息的延迟时间加上当前开机后的时长,作为该消息的执行时间;进入sendMessageAtTime()
方法,将当前Handler
封装至Message
中,然后调用MessageQueue
的enqueueMessage()
方法进行入队操作。MessageQueue
队列中,如果队列为空,或者该消息的执行时间早于队列中的所有消息执行时间,则唤醒队列的阻塞状态Looper
循环,所以当MessageQueue
中有消息到了需要执行的时候,则会通过next()
方法返回一个Message
进行消息分发。在next()
方法中,如果队列为空或者队列中的消息执行时间都未到,则会导致死循环进入阻塞状态。post
系列的方法,则调用其Runnable
对象的run()
方法,否则判断Handler
构造方法传入的Callback
接口实现方法handleMessage()
是否返回true
,是则结束消息处理,否则再交由Handler
的dispatchMessage()
方法进行最后的处理。现在可以回答文章开头的问题了:
Q: 主线程和子线程之间是如何实现通信的?
A: 在主线程创建的Handler
关联了主线程私有的Looper
和MessageQueue
,然后Handler
在子线程发送的Message
进入到了主线程的MessageQueue
,最终在主线程里通过Looper.loop()
方法从MessageQueue
中获取Message
,交由Handler
处理。
Q: 子线程和子线程之间能否通过消息机制来通信?
A: 能。需要在接收消息的子线程里,创建Handler
之前需要手动调用Looper.prepare()
,之后调用Looper.loop()
方法,这样便可以在另一个子线程中发送消息到该子线程了。
Q: 延迟发送或执行的内部原理又是如何实现的?
A: 延迟的消息会将开机运行时间加上延迟时间所得到的时间作为消息的执行时间,进入消息队列后按照执行时间来排序插入队列中,出队时会通过nativePollOnce()
方法在底层实现阻塞状态,阻塞时长为消息执行时间减去当前开机时长的差值,待阻塞状态结束后便会让该消息出队,并且交由Handler
来分发处理。
Q: 主线程在Looper.loop()
中开启了一个死循环,为什么不会造成ANR
?从MessageQueue
中取出消息时可能会阻塞,为什么该阻塞也不会造成ANR
?
A: 首先,ANR
是因为输入事件得不到及时处理,此外还有Serveice
、Broadcast
等,我们统一称之为消息事件。当消息事件发送了却在规定的时间内无法得到处理,就会产生ANR
现象。主线程调用Looper.loop()
方法开启一个死循环,其目的就是用于分发处理这些消息事件,所以自然不会造成ANR
,除非有其它消息事件做了耗时操作,才会有可能导致ANR
发生。
从MessageQueue
中取出消息时可能会阻塞,什么情况下会阻塞呢?队列为空或没有需要及时处理的消息时,才会发生阻塞,这是为了节约CUP
资源不让它空转。如果你此时输入一个消息时间,阻塞状态就会被唤醒,该事件会进行入队出队分发处理操作,也就谈不上不及时处理,自然不会导致ANR
发生。
Android消息机制源码分析基本上已经结束了,由于技术水平有限及时间仓促,难免会有错误之处,还恳请指点出来,共同学习进步!