1、引言
我們知道,Android的消息機制就是Handler、Looper、Message、MessageQueue之間的運作機制。本文假設(shè)大家對 它們都已經(jīng)有所了解,所以并不打算介紹它們之間千絲萬縷的聯(lián)系,不了解的同學(xué)可以參考網(wǎng)上其他博文~
這其中有個小細(xì)節(jié),估計很多人沒有注意到,那就是消息機制的同步屏障是什么? 同步屏障與target == null有什么關(guān)系?與 target 不為 null 的區(qū)別在哪里?這篇文章就是要揭露同步屏障與 target 之間的微妙關(guān)系。
2、 target 為何物
首先,看下 Message 類中 target 的定義出處:
//Message.java
Handler target;
從這里可以知道,Message 是持有 Handler 的, 所謂的 target 即為 Handler 對象。
讓我們再看看 target 是哪里出現(xiàn)的?
我們知道,通過 Handle 發(fā)送消息的時候(如調(diào)用Handler#sendMessage()等 ),最終都是會調(diào)用 Handler#enqueueMessage()讓消息入隊,如下:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
看到?jīng)],當(dāng)我們發(fā)送一個消息的時候,msg.target就會被賦值為this, 而 this 即為我們的 Handler 對象。因此,通過這種方式傳進來的消息的 target 肯定也就不為 null,并且 mAsynchronous 默認(rèn)為 false,也就是說我們一般發(fā)送的消息都為同步消息。相對地,也應(yīng)該有異步消息吧?的確,還有一種很容易被忽略的 異步消息,因為除了系統(tǒng)的源碼外,我們一般很少會使用異步消息。那么什么是異步消息呢?這里先說一下結(jié)論:滿足target == null的消息就是異步消息。那么,如何發(fā)送一個異步消息呢?
簡單來說有兩種方式。
- 一種是直接設(shè)置消息為異步的:
Message msg = mMyHandler.obtainMessage();
msg.setAsynchronous(true);
mMyHandler.sendMessage(msg);
- 還有一個需要用到 Handler 的一個構(gòu)造方法,不過該方法已被標(biāo)記為@Hide了:
/**
* @hide
*/
public Handler(boolean async) {
this(null, async);
}
使用如下:
Handler mMyHandler = new Handler(true);
Message msg = mHandler.obtainMessage();
mMyHandler.sendMessage(msg);
參數(shù) async 傳 true 即為異步消息。
但需要注意的是,通過上面兩種方式來發(fā)送的消息還不是異步消息,因為它們最終還是會進入 enqueueMessage(),仍然會給 target 賦值 ,導(dǎo)致 target 不為null。這與前面所說的同步消息無異。那么什么情況下會滿足target == null 這個條件呢?
咱們今天的主角,同步屏障 (Sync Barrier) 就要登場啦。
3、同步屏障是什么
沒錯,發(fā)送異步消息的關(guān)鍵就是要消息開啟一個同步屏障。屏障的意思即為阻礙,顧名思義,同步屏障就是阻礙同步消息,只讓異步消息通過。如何開啟同步屏障呢?如下而已:
MessageQueue#postSyncBarrier()
我們看看這行代碼面里蘊藏著什么樣的黑科技:
/**
* @hide
*/
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token
synchronized (this) {
final int token = mNextBarrierToken++;
//從消息池中獲取Message
final Message msg = Message.obtain();
msg.markInUse();
//就是這里?。。〕跏蓟疢essage對象的時候,并沒有給target賦值,因此 target==null
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
//如果開啟同步屏障的時間(假設(shè)記為T)T不為0,且當(dāng)前的同步消息里有時間小于T,則prev也不為null
prev = p;
p = p.next;
}
}
/根據(jù)prev是不是為null,將 msg 按照時間順序插入到 消息隊列(鏈表)的合適位置
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
可以看到,Message 對象初始化的時候并沒有給 target 賦值,因此,target == null的 來源就找到了。上面消息的插入也做了相應(yīng)的注釋。這樣,一條target == null 的消息就進入了消息隊列。
那么,開啟同步屏障后,所謂的異步消息又是如何被處理的呢?
如果對消息機制有所了解的話,應(yīng)該知道消息的最終處理是在消息輪詢器Looper#loop()中,而loop()循環(huán)中會調(diào)用MessageQueue#next()從消息隊列中進行取消息,來看看關(guān)鍵代碼:
//MessageQueue.java
Message next()
.....//省略一些代碼
int pendingIdleHandlerCount = -1; // -1 only during first iteration
// 1.如果nextPollTimeoutMillis=-1,一直阻塞不會超時。
// 2.如果nextPollTimeoutMillis=0,不會阻塞,立即返回。
// 3.如果nextPollTimeoutMillis>0,最長阻塞nextPollTimeoutMillis毫秒(超時)
// 如果期間有程序喚醒會立即返回。
int nextPollTimeoutMillis = 0;
//next()也是一個無限循環(huán)
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
//獲取系統(tǒng)開機到現(xiàn)在的時間
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages; //當(dāng)前鏈表的頭結(jié)點
//關(guān)鍵?。?!
//如果target==null,那么它就是屏障,需要循環(huán)遍歷,一直往后找到第一個異步的消息
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) {
//如果有消息需要處理,先判斷時間有沒有到,如果沒到的話設(shè)置一下阻塞時間,
//場景如常用的postDelay
if (now < msg.when) {
//計算出離執(zhí)行時間還有多久賦值給nextPollTimeoutMillis,
//表示nativePollOnce方法要等待nextPollTimeoutMillis時長后返回
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 獲取到消息
mBlocked = false;
//鏈表操作,獲取msg并且刪除該節(jié)點
if (prevMsg != null)
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
//返回拿到的消息
return msg;
}
} else {
//沒有消息,nextPollTimeoutMillis復(fù)位
nextPollTimeoutMillis = -1;
}
.....//省略
}
從上面可以看出,當(dāng)消息隊列開啟同步屏障的時候(即標(biāo)識為msg.target == null),消息機制在處理消息的時候,優(yōu)先處理異步消息。這樣,同步屏障就起到了一種過濾和優(yōu)先級的作用。
下面用示意圖簡單說明:

如上圖所示,在消息隊列中有同步消息和異步消息(黃色部分)以及一道墻----同步屏障(紅色部分)。有了同步屏障的存在,msg_2 和 msg_M 這兩個異步消息可以被優(yōu)先處理,而后面的 msg_3 等同步消息則不會被處理。那么這些同步消息什么時候可以被處理呢?那就需要先移除這個同步屏障,即調(diào)用removeSyncBarrier()。
舉個生活中的栗子哈。開演唱會的時候,觀眾們需要在體育館門口排隊依次檢票入場(這些排隊的觀眾相當(dāng)于消息隊列中的普通同步消息),但這個時候演唱會的嘉賓來了(相當(dāng)于異步消息,優(yōu)先級高于觀眾),如果他們出示證件(不出示證件,就相當(dāng)于普通觀眾入場,也還是需要排隊,這種情形就是最前面所說的僅僅設(shè)置了msg.setAsynchronous(true)),保安立馬攔住進場的觀眾(保安攔住普通觀眾就相當(dāng)于開啟了同步屏障,阻止同步消息通過),讓嘉賓先進去(只處理異步消息,而阻擋同步消息)。等工作人員全部進去了,如果保安不再阻攔觀眾(即移除同步屏障),這樣觀眾又可以進場了(又可以處理同步消息)。只要保安不解除攔截,那么后面的觀眾就永遠(yuǎn)不可能進場(不移除同步屏障,同步消息就不會得到處理)。
4、同步屏障使用場景
似乎在日常的應(yīng)用開發(fā)中,很少會用到同步屏障。那么,同步屏障在系統(tǒng)源碼中有哪些使用場景呢?Android 系統(tǒng)中的 UI 更新相關(guān)的消息即為異步消息,需要優(yōu)先處理。
比如,在 View 更新時,draw、requestLayout、invalidate 等很多地方都調(diào)用了ViewRootImpl#scheduleTraversals(),如下:
//ViewRootImpl.java
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//開啟同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//發(fā)送異步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
postCallback()最終走到了ChoreographerpostCallbackDelayedInternal():

這里就開啟了同步屏障,并發(fā)送異步消息,由于 UI 更新相關(guān)的消息是優(yōu)先級最高的,這樣系統(tǒng)就會優(yōu)先處理這些異步消息。
最后,當(dāng)要移除同步屏障的時候需要調(diào)用ViewRootImpl#unscheduleTraversals()。
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
5、總結(jié)
同步屏障的設(shè)置可以方便地處理那些優(yōu)先級較高的異步消息。當(dāng)我們調(diào)用Handler.getLooper().getQueue().postSyncBarrier()并設(shè)置消息的setAsynchronous(true)時,target 即為 null ,也就開啟了同步屏障。當(dāng)在消息輪詢器 Looper 在loop()中循環(huán)處理消息時,如若開啟了同步屏障,會優(yōu)先處理其中的異步消息,而阻礙同步消息。