咱們知道,Android的消息機制就是Handler、Looper、Message、MessageQueue之間的運做機制。本文假設你們對 它們都已經有所瞭解,因此並不打算介紹它們之間千絲萬縷的聯繫,不瞭解的同窗能夠參考網上其餘博文~java
這其中有個小細節,估計不少人沒有注意到,那就是消息機制的同步屏障是什麼? 同步屏障與target == null
有什麼關係?與 target 不爲 null 的區別在哪裏?這篇文章就是要揭露同步屏障與 target 之間的微妙關係。bash
首先,看下 Message 類中 target 的定義出處:markdown
//Message.java
Handler target;
複製代碼
從這裏能夠知道,Message 是持有 Handler 的, 所謂的 target
即爲 Handler 對象。併發
讓咱們再看看 target
是哪裏出現的?異步
咱們知道,經過 Handle 發送消息的時候(如調用Handler#sendMessage()
等 ),最終都是會調用 Handler#enqueueMessage()
讓消息入隊,以下:async
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); } 複製代碼
看到沒,當咱們發送一個消息的時候,msg.target
就會被賦值爲this
, 而 this
即爲咱們的 Handler 對象。所以,經過這種方式傳進來的消息的 target
確定也就不爲 null
,而且 mAsynchronous
默認爲 false
,也就是說咱們通常發送的消息都爲同步消息。相對地,也應該有異步消息吧?的確,還有一種很容易被忽略的 異步消息,由於除了系統的源碼外,咱們通常不多會使用異步消息。那麼什麼是異步消息呢?這裏先說一下結論:知足target == null
的消息就是異步消息。那麼,如何發送一個異步消息呢?ide
簡單來講有兩種方式。oop
一種是直接設置消息爲異步的:post
Message msg = mMyHandler.obtainMessage(); msg.setAsynchronous(true); mMyHandler.sendMessage(msg); 複製代碼
還有一個須要用到 Handler 的一個構造方法,不過該方法已被標記爲@Hide
了:this
/**
*
* @hide
*/
public Handler(boolean async) {
this(null, async);
}
複製代碼
使用以下:
Handler mMyHandler = new Handler(true); Message msg = mHandler.obtainMessage(); mMyHandler.sendMessage(msg); 複製代碼
參數 async
傳 true
即爲異步消息。
但須要注意的是,經過上面兩種方式來發送的消息還不是異步消息,由於它們最終仍是會進入 enqueueMessage()
,仍然會給 target
賦值 ,致使 target
不爲null
。這與前面所說的同步消息無異。那麼什麼狀況下會知足target == null
這個條件呢?
我們今天的主角,同步屏障 (Sync Barrier) 就要登場啦。
沒錯,發送異步消息的關鍵就是要消息開啓一個同步屏障。屏障的意思即爲阻礙,顧名思義,同步屏障就是阻礙同步消息,只讓異步消息經過。如何開啓同步屏障呢?以下而已:
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(); //就是這裏!!!初始化Message對象的時候,並無給target賦值,所以 target==null msg.when = when; msg.arg1 = token; Message prev = null; Message p = mMessages; if (when != 0) { while (p != null && p.when <= when) { //若是開啓同步屏障的時間(假設記爲T)T不爲0,且當前的同步消息裏有時間小於T,則prev也不爲null prev = p; p = p.next; } } /根據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
的 來源就找到了。上面消息的插入也作了相應的註釋。這樣,一條target == null
的消息就進入了消息隊列。
那麼,開啓同步屏障後,所謂的異步消息又是如何被處理的呢?
若是對消息機制有所瞭解的話,應該知道消息的最終處理是在消息輪詢器Looper#loop()
中,而loop()
循環中會調用MessageQueue#next()
從消息隊列中進行取消息,來看看關鍵代碼:
//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()也是一個無限循環 for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { //獲取系統開機到如今的時間 final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; //當前鏈表的頭結點 //關鍵!!! //若是target==null,那麼它就是屏障,須要循環遍歷,一直日後找到第一個異步的消息 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) { //若是有消息須要處理,先判斷時間有沒有到,若是沒到的話設置一下阻塞時間, //場景如經常使用的postDelay if (now < msg.when) { //計算出離執行時間還有多久賦值給nextPollTimeoutMillis, //表示nativePollOnce方法要等待nextPollTimeoutMillis時長後返回 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // 獲取到消息 mBlocked = false; //鏈表操做,獲取msg而且刪除該節點 if (prevMsg != null) prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; msg.markInUse(); //返回拿到的消息 return msg; } } else { //沒有消息,nextPollTimeoutMillis復位 nextPollTimeoutMillis = -1; } .....//省略 } 複製代碼
從上面能夠看出,當消息隊列開啓同步屏障的時候(即標識爲msg.target == null
),消息機制在處理消息的時候,優先處理異步消息。這樣,同步屏障就起到了一種過濾和優先級的做用。
下面用示意圖簡單說明:
如上圖所示,在消息隊列中有同步消息和異步消息(黃色部分)以及一道牆----同步屏障(紅色部分)。有了同步屏障的存在,msg_2 和 msg_M 這兩個異步消息能夠被優先處理,然後面的 msg_3 等同步消息則不會被處理。那麼這些同步消息何時能夠被處理呢?那就須要先移除這個同步屏障,即調用removeSyncBarrier()
。
舉個生活中的栗子哈。開演唱會的時候,觀衆們須要在體育館門口排隊依次檢票入場(這些排隊的觀衆至關於消息隊列中的普通同步消息),但這個時候演唱會的嘉賓來了(至關於異步消息,優先級高於觀衆),若是他們出示證件(不出示證件,就至關於普通觀衆入場,也仍是須要排隊,這種情形就是最前面所說的僅僅設置了msg.setAsynchronous(true)
),保安立馬攔住進場的觀衆(保安攔住普通觀衆就至關於開啓了同步屏障,阻止同步消息經過),讓嘉賓先進去(只處理異步消息,而阻擋同步消息)。等工做人員所有進去了,若是保安再也不阻攔觀衆(即移除同步屏障),這樣觀衆又能夠進場了(又能夠處理同步消息)。只要保安不解除攔截,那麼後面的觀衆就永遠不可能進場(不移除同步屏障,同步消息就不會獲得處理)。
彷佛在平常的應用開發中,不多會用到同步屏障。那麼,同步屏障在系統源碼中有哪些使用場景呢?Android 系統中的 UI 更新相關的消息即爲異步消息,須要優先處理。
好比,在 View 更新時,draw、requestLayout、invalidate 等不少地方都調用了ViewRootImpl#scheduleTraversals()
,以下:
//ViewRootImpl.java void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; //開啓同步屏障 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); //發送異步消息 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } } 複製代碼
postCallback()
最終走到了ChoreographerpostCallbackDelayedInternal()
:
最後,當要移除同步屏障的時候須要調用ViewRootImpl#unscheduleTraversals()
。
void unscheduleTraversals() { if (mTraversalScheduled) { mTraversalScheduled = false; //移除同步屏障 mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); mChoreographer.removeCallbacks( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); } } 複製代碼
同步屏障的設置能夠方便地處理那些優先級較高的異步消息。當咱們調用Handler.getLooper().getQueue().postSyncBarrier()
並設置消息的setAsynchronous(true)
時,target 即爲 null ,也就開啓了同步屏障。當在消息輪詢器 Looper 在loop()
中循環處理消息時,如若開啓了同步屏障,會優先處理其中的異步消息,而阻礙同步消息。