问:大家知道Handler机制中发送的Message分为几种吗?
答:分为 同步消息、屏障消息(消息屏障)、异步消息 这三种
同步消息
就是我们平时最常用的消息。
屏障消息
Message.target = null 的消息
理论依据:
MessageQueue.java
//发送消息屏障
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++;
//从message的复用池中获得一个message
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
//mMessages 就是消息队列,单向链表结构
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;
}
//可以看到将msg放入队列的过程中并没有给msg.target赋值即:msg.target = null
return token;
}
}
而同步消息入队列时:
Handler.java
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
//将发送消息的handler对象赋值给了msg.target
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
那么为什么这么设计呢?因为对于屏障消息来说这个消息本身没有任何意义,发送这个消息不是用来跨线程通信的,它是不会像同步消息一样被分发的,它的作用是用来标识说我(此屏障消息)后面有异步消息需要尽快处理。所以它的使用场景是在发出异步消息前先发一个屏障消息,至于为什么要这样我们下面会分析。
异步消息
Message调用了setAsynchronous(true),即设置了Message.flag = FLAG_ASYNCHRONOUS的消息
那么问题来了,同步消息我们可以用来进行线程间通信,异步消息被设计出来是干什么的呢?
了解过android屏幕刷新机制的小伙伴会知道,屏幕的刷新频率一般是60Hz,即1秒中刷新60次约16.7ms刷新一次,以保证视觉上的连续性,人眼就不会感觉到卡顿。UI刷新也是通过handler机制来完成的。及系统会每隔16.7ms发送一个异步消息用于屏幕UI刷新。
空口无凭我们来看下源码:
研究过view的绘制流程的小伙伴知道,view刷新最终会调用到ViewRootImpl的scheduleTraversals():
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//这里先发送了一个屏障消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//继续进入这个方法
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
最终来到这个方法:
Choreographer.java
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
...
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
//这里发送了一个异步消息 (action就是上面传入的mTraversalRunnable)
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);//设置这个消息为异步消息
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
OK,到此三种类型消息的差异及用途我们就分析清楚了。
在此也需要提及一下的是,虽然构建三种类型的消息的细节上有一点差异,但三种类型的消息在进入队列的逻辑是一致的,都是根据比较时间来讲message插入到合适的位置,如下图:

但在取消息时有一定的差异,如下图:

队头是同步消息时,就是正常取出,比如将m1取出了,m2就成了队头,就这样取当取到m3时发现这个msg.target=null,就知道m3是一个屏障消息,取到它代表后面跟着一个急需处理的异步消息(UI刷新消息),那么就会一直往后找直到遍历到m5时发现它是异步消息,那就赶紧把m5先取出并分发它去执行UI刷新的逻辑(这里对应MessageQueue的next()方法中的逻辑),还记得上面mTraversalRunnable吗?它被赋值给了异步消息中obj这个成员变量随着Message一起被分发出去,当执行到这个Runnable任务时:
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
//走到这里
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//在这里移除了屏障消息
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
//这个方法非常眼熟,是开始View绘制三部曲(测量、布局、绘制)的入口
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
所以,当分发了异步消息后,在取下一个消息前,这个屏障消息就被移除了。