[轉(zhuǎn)]Android 消息機制中的同步屏障機制

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ù) asynctrue 即為異步消息。

但需要注意的是,通過上面兩種方式來發(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)在消息輪詢器 Looperloop()中循環(huán)處理消息時,如若開啟了同步屏障,會優(yōu)先處理其中的異步消息,而阻礙同步消息。

轉(zhuǎn)自 https://juejin.cn/post/6844903910113705998

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容