復盤 Handler 中各式 Message 的使用和原理

我們會經(jīng)常使用 Handlersendpost 一個延時、非延時插隊執(zhí)行的 Message,但對于這個 Message 到底什么時候執(zhí)行以及為什么是這樣,鮮少細究過。本文將一 一盤點,并起底個中原理!

同時針對大家疏于了解的異步執(zhí)行 Message 和空閑執(zhí)行 IdleHandler,進行演示和原理普及。篇幅較大,搭配收藏食用更佳~

目錄一覽:

  • 非延時執(zhí)行 Message
  • 延時執(zhí)行 Message
  • 插隊執(zhí)行 Message
  • 異步執(zhí)行 Message
  • 空閑執(zhí)行 “Message”
  • 名詞回顧

非延時執(zhí)行 Message

先在主線程創(chuàng)建一個 Handler 并復寫 Callback 處理。

    private val mainHandler = Handler(Looper.getMainLooper()) { msg ->
        Log.d(
            "MainActivity",
            "Main thread message occurred & what:${msg.what}"
        )
        true
    }

不斷地發(fā)送期望即刻執(zhí)行的 Message 和 Runnable 給主線程的 Handler。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        testSendNoDelayedMessages()
    }

    private fun testSendNoDelayedMessages() {
        Log.d("MainActivity","testSendNoDelayedMessages() start")
        testSendMessages()
        testPostRunnable()
        Log.d("MainActivity","testSendNoDelayedMessages() end ")
    }

    private fun testSendMessages() {
        Log.d("MainActivity","startSendMessage() start")
        for (i in 1..10) {
            sendMessageRightNow(mainHandler, i)
        }
        Log.d("MainActivity","startSendMessage() end ")
    }

    private fun testPostRunnable() {
        Log.d("MainActivity","testPostRunnable() start")
        for (i in 11..20) {
            mainHandler.post { Log.d("MainActivity", "testPostRunnable() run & i:${i}") }
        }
        Log.d("MainActivity","testPostRunnable() end ")
    }

什么時候執(zhí)行?

公布下日志前,大家可以猜測下運行的結果,Message 或 Runnable 在 send 或 post 之后會否立即執(zhí)行。不是的話,什么時候執(zhí)行?

 D MainActivity: testSendNoDelayedMessages() start
 D MainActivity: startSendMessage() start
 D MainActivity: startSendMessage() end
 D MainActivity: testPostRunnable() start
 D MainActivity: testPostRunnable() end
 D MainActivity: testSendNoDelayedMessages() end
 D MainActivity: Main thread message occurred & what:1
 ...
 D MainActivity: Main thread message occurred & what:10
 D MainActivity: testPostRunnable() run & i:11
 ...
 D MainActivity: testPostRunnable() run & i:20

答案可能跟預想的略有出入,但一細想好像又是合理的:發(fā)送完的 Message 或 Runnable 不會立即執(zhí)行,MessageQueue 的喚醒和回調(diào)需要等主線程的其他工作完成之后才能執(zhí)行。

為什么?

非延時的 sendMessage()post() 最終仍然是調(diào)用 sendMessageAtTime() 將 Message 放入了 MessageQueue。只不過它的期待執(zhí)行時間 when 變成了 SystemClock.uptimeMillis(),即調(diào)用的時刻。

// Handler.java
    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }
    
    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); // when 等于當前時刻
    }

    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        return enqueueMessage(queue, msg, uptimeMillis);
    }

這些 Message 會按照 when 的先后排隊進入 MessageQueue 中,當 Message 滿足了條件會立即調(diào)用 wake,反之只是插入隊列而已。所以,上述的 send 或 post 循環(huán),會按照調(diào)用的先后挨個進入隊列,第一個 Message 會觸發(fā) wake。

// MessageQueue.java
    boolean enqueueMessage(Message msg, long when) {
        ...
        // 鑒于多線程往 Handler 里發(fā)送 Message 的情況
        // 在向隊列插入 Message 前需要上鎖
        synchronized (this) {
            ...
            msg.markInUse(); // Message 標記正在使用
            msg.when = when; // 更新 when 屬性
            Message p = mMessages; // 拿到隊列的 Head 
            boolean needWake;
            // 如果隊列為空
            // 或者 Message 需要插隊(sendMessageAtFrontOfQueue)
            // 又或者 Message 執(zhí)行時刻比 Head 的更早
            // 該 Message 插入到隊首
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                // 線程是否因為沒有可執(zhí)行的 Message 正在 block 或 wait
                // 是的話,喚醒
                needWake = mBlocked;
            } else {
                // 如果隊列已有 Message,Message 優(yōu)先級又不高,同時執(zhí)行時刻并不早于隊首的 Message
                // 如果線程正在 block 或 wait,或建立了同步屏障(target 為空),并且 Message 是異步的,則喚醒
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                // 遍歷隊列,找到 Message 目標插入位置
                for (;;) {
                    prev = p;
                    p = p.next;
                    // 如果已經(jīng)遍歷到隊尾了,或 Message 的時刻比當前 Message 要早
                    // 找到位置了,退出遍歷
                    if (p == null || when < p.when) {
                        break;
                    }
                    
                    // 如果前面決定需要喚醒,但隊列已有執(zhí)行時刻更早的異步 Message 的話,先不喚醒
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                // 將 Message 插入隊列的目標位置
                msg.next = p;
                prev.next = msg;
            }

            // 需要喚醒的話,喚醒 Native 側(cè)的 MessageQueue
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

總結來講:

  • 第一次 send 的 Message 在 enqueue 進 MessageQueue 的隊首后,通知 Native 側(cè) wake
  • 后續(xù)發(fā)送的其他 Message 或 Runnable 挨個 enqueue 進隊列
  • 接著執(zhí)行主線程的其他 Message,比如日志的打印
  • 空閑后 wake 完畢并在 next() 的下一次循環(huán)里將隊首 Message 移除和返回給 Looper 去回調(diào)和執(zhí)行
  • 之后 loop() 開始讀取 MessageQueue 當前隊首 Message 的下一次循環(huán),當前時刻必然晚于 send 時候設置的when,所以隊列里的 Message 挨個出隊和回調(diào)

結論

非延時 Message 并非立即執(zhí)行,只是放入 MessageQueue 等待調(diào)度而已,執(zhí)行時刻不確定。

MessageQueue 會記錄請求的時刻,按照時刻的先后順序進行排隊。如果 MessageQueue 中積攢了很多 Message,或主線程被占用的話,Message 的執(zhí)行會明顯晚于請求的時刻。

比如在 onCreate() 里發(fā)送 message 的話,你會發(fā)現(xiàn)當 onResume() 執(zhí)行完才會回調(diào)你的 Message。原因在于 onCreate() 等操作也是由 Message 觸發(fā),其同步處理完 onResume() 之后,才有機會進入下一次循環(huán)去讀取你的 Message。

延時執(zhí)行 Message

延時執(zhí)行的 Message 使用更為常見,那它又是何時執(zhí)行呢?

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        testSendDelayedMessages()
    }

    private fun testSendDelayedMessages() {
        Log.d("MainActivity","testSendDelayedMessages() start")
        // 發(fā)送 Delay 2500 ms 的 Message
        sendDelayedMessage(mainHandler, 1)
        Log.d("MainActivity","testSendDelayedMessages() end ")
    }

28:58.186 發(fā)送 Message,29:00.690 Message 執(zhí)行,時間差為 2504ms,并非準確的 2500ms。

09-22 22:28:57.964 24980 24980 D MainActivity: onCreate()
09-22 22:28:58.186 24980 24980 D MainActivity: testSendDelayedMessages() start
// 發(fā)送 Message
09-22 22:28:58.186 24980 24980 D MainActivity: testSendDelayedMessages() end
// Message 執(zhí)行
09-22 22:29:00.690 24980 24980 D MainActivity: Main thread message occurred & what:1

如果連續(xù)發(fā)送 10 個均延時 2500ms 的 Message 會怎么樣?

    private fun testSendDelayedMessages() {
        Log.d("MainActivity","testSendDelayedMessages() start")
        // 連續(xù)發(fā)送 10 個 Delay 2500 ms 的 Message
        for (i in 1..10) {
            sendDelayedMessage(mainHandler, i)
        }
        Log.d("MainActivity","testSendDelayedMessages() end ")
    }

第 1 個 Message 執(zhí)行的時間差為 2505ms(39:56.841 - 39:54.336),第 10 個 Message 執(zhí)行的時間差已經(jīng)達到了 2508ms(39:56.844 - 39:54.336)。

09-22 22:39:54.116 25104 25104 D MainActivity: onCreate()
09-22 22:39:54.336 25104 25104 D MainActivity: testSendDelayedMessages() start
09-22 22:39:54.337 25104 25104 D MainActivity: testSendDelayedMessages() end
09-22 22:39:56.841 25104 25104 D MainActivity: Main thread message occurred & what:1
09-22 22:39:56.842 25104 25104 D MainActivity: Main thread message occurred & what:2
09-22 22:39:56.842 25104 25104 D MainActivity: Main thread message occurred & what:3
..
09-22 22:39:56.842 25104 25104 D MainActivity: Main thread message occurred & what:8
09-22 22:39:56.842 25104 25104 D MainActivity: Main thread message occurred & what:9
09-22 22:39:56.844 25104 25104 D MainActivity: Main thread message occurred & what:10

為什么?

延時 Message 執(zhí)行的時刻 when 采用的是發(fā)送的時刻和 Delay 時長的累加,基于此排隊進 MessageQueue。

// Handler.java
    public final boolean postDelayed(Runnable r, int what, long delayMillis) {
        return sendMessageDelayed(getPostMessage(r).setWhat(what), delayMillis);
    }

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {  
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); 
    }

    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        return enqueueMessage(queue, msg, uptimeMillis); 
    }

Delay Message 尚未抵達的時候,MessageQueue#next() 會將讀取隊列的時刻與 when 的差值,作為下一次通知 Native 休眠的時長。進行下一次循環(huán)前,next() 還存在其他邏輯,導致 wake up 的時刻存在滯后。此外由于 wake up 后線程存在其他 Message 占用,導致執(zhí)行更加延后。

// MessageQueue.java
    Message next() {
        ...
        for (;;) {
            ...
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                ...
                
                if (msg != null) {
                    // 計算下一次循環(huán)應當休眠的時長
                    if (now < msg.when) {
                        nextPollTimeoutMillis
                            = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        ...
                    }
                } else {
                    ...
                }
                ...
            }
            ...
        }
    }

結論

由于喚醒時長的計算誤差和回調(diào)的任務可能占用線程,導致延時執(zhí)行 Message 不是時間到了就會執(zhí)行,其執(zhí)行的時刻必然晚于 Delay 的時刻。

插隊執(zhí)行 Message

Handler 還提供了 Message 插隊的 API:sendMessageAtFrontOfQueue()postAtFrontOfQueue()。

在上述的 send 和 post 之后同時調(diào)用 xxxFrontOfQueue 的方法,Message 的執(zhí)行結果會怎么樣?

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        testSendNoDelayedMessages()
        testFrontMessages() // 立馬調(diào)用 FrontOfQueue 的方法
    }

分別調(diào)用 sendMessageAtFrontOfQueue() 和 postAtFrontOfQueue() 的 API。

    private fun testFrontMessages() {
        Log.d("MainActivity","testFrontMessages() start")
        testSendFrontMessages()
        testPostFrontRunnable()
        Log.d("MainActivity","testFrontMessages() end ")
    }

    private fun testSendFrontMessages() {
        Log.d("MainActivity","testSendFrontMessages() start")
        for (i in 21..30) {
            sendMessageFront(mainHandler, i)
        }
        Log.d("MainActivity","testSendFrontMessages() end ")
    }

    private fun testPostFrontRunnable() {
        Log.d("MainActivity","testPostFrontRunnable() start")
        for (i in 31..40) {
            mainHandler.postAtFrontOfQueue() { Log.d("MainActivity", "testPostFrontRunnable() run & i:${i}") }
        }
        Log.d("MainActivity","testPostFrontRunnable() end ")
    }

當主線程的打印日志按序輸出后,Message 開始逐個執(zhí)行。按照預想的一樣,F(xiàn)rontOfQueue 的 Message 會先執(zhí)行,也就是最后一次調(diào)用這個 API 的最早回調(diào)。

Front 的 Message 逆序執(zhí)行完畢之后,普通的 Message 才按照請求的順序執(zhí)行。

 D MainActivity: testSendNoDelayedMessages() start
 D MainActivity: startSendMessage() start
 D MainActivity: startSendMessage() end
 D MainActivity: testPostRunnable() start
 D MainActivity: testPostRunnable() end
 D MainActivity: testSendNoDelayedMessages() end
 D MainActivity: testFrontMessages() start
 D MainActivity: testSendFrontMessages() start
 D MainActivity: testSendFrontMessages() end
 D MainActivity: testPostFrontRunnable() start
 D MainActivity: testPostFrontRunnable() end
 D MainActivity: testFrontMessages() end
 D MainActivity: testPostFrontRunnable() run & i:40
 ...
 D MainActivity: testPostFrontRunnable() run & i:31
 D MainActivity: Main thread message occurred & what:30
 ...
 D MainActivity: Main thread message occurred & what:21
 D MainActivity: Main thread message occurred & what:1
 ...
 D MainActivity: Main thread message occurred & what:10
 D MainActivity: testPostRunnable() run & i:11
 ...
 D MainActivity: testPostRunnable() run & i:20

怎么實現(xiàn)的?

原理在于 sendMessageAtFrontOfQueue() 或 postAtFrontOfQueue() 發(fā)送的 Mesage 被記錄的 when 屬性被固定為 0。

// Handler.java
    public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
        return enqueueMessage(queue, msg, 0); // 發(fā)送的 when 等于 0
    }

    public final boolean postAtFrontOfQueue(@NonNull Runnable r) {
        return sendMessageAtFrontOfQueue(getPostMessage(r));
    }

從入隊函數(shù)可以看出,when 為 0 的 Message 會立即插入隊首,所以總會先得到執(zhí)行。

// MessageQueue.java
    enqueueMessage(Message msg, long when) {
        ...
        synchronized (this) {
            ...
            // 如果 Message 需要插隊(sendMessageAtFrontOfQueue)
            // 則插入隊首
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
            } else {
                ...
            }
            ...
        }
        return true;
    }

結論

sendMessageAtFrontOfQueue() 和 postAtFrontOfQueue() 的 API 通過將 when 預設為 0 進而插入 Message 至隊首,最終達到 Message 先得到執(zhí)行的目的。

但需要注意的是,這將造成本來先該執(zhí)行的 Message 被延后調(diào)度,對于存在先后關系的業(yè)務邏輯來說將可能造成順序問題,謹慎使用!

異步執(zhí)行 Message

Handler 發(fā)送的 Message 都是同步的,意味著大家都按照 when 的先后進行排序,誰先到誰執(zhí)行。

如果遇到優(yōu)先級高的 Message 可以通過 FrontQueue 發(fā)送插隊 Message即可。但如果是希望同步的隊列停滯只執(zhí)行指定 Message 的話,即 Message 異步執(zhí)行,現(xiàn)有的 API 是不夠的。

事實上 Android 提供了同步屏障的機制來實現(xiàn)這一需求,不過主要面向的是系統(tǒng) App 或 系統(tǒng),App 可以通過反射來使用。

通過異步 Handler 實現(xiàn)

除了一般使用的 Handler 構造函數(shù)以外,Handler 還提供了創(chuàng)建發(fā)送異步 Message 的專用構造函數(shù)。通過該 Handler 發(fā)送的 Message 或 Runnable 都是異步的。我們將其稱為異步 Handler。

// Handler.java
    @UnsupportedAppUsage
    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

    @NonNull
    public static Handler createAsync(@NonNull Looper looper, @NonNull Callback callback) {
        if (looper == null) throw new NullPointerException("looper must not be null");
        if (callback == null) throw new NullPointerException("callback must not be null");
        return new Handler(looper, callback, true);
    }

我們啟動一個 HandlerThread 來測試一下同步屏障的使用:分別構建一個普通 Handler異步 Handler。

    private fun startBarrierThread() {
        val handlerThread = HandlerThread("Test barrier thread")
        handlerThread.start()

        normalHandler = Handler(handlerThread.looper) { msg ->
            Log.d(...)
            true
        }

        barrierHandler = Handler.createAsync(handlerThread.looper) { msg ->
            Log.d(...)
            true
        }
    }

啟動 HandlerThread 并向兩個 Handler 各發(fā)送一個 Message。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        startBarrierThread()
        testNormalMessage()
        testSyncBarrierByHandler()
    }
    
    private fun testNormalMessage() {
        sendMessageRightNow(normalHandler, 1)
    }

    private fun testSyncBarrierByHandler() {
        sendMessageRightNow(barrierHandler, 2)
    }

是異步 Handler 的 Message 先執(zhí)行嗎?非也,因為我們還沒有通知 MessageQueue 建立同步屏障!

09-24 23:02:19.032 28113 28113 D MainActivity: onCreate()
09-24 23:02:19.150 28113 28141 D MainActivity: Normal handler message occurred & what:1
09-24 23:02:19.150 28113 28141 D MainActivity: Barrier handler message occurred & what:2

除了發(fā)送異步Handler 發(fā)送異步 Message 以外,需要通過反射事先建立起同步屏障。

注意:建立同步屏障必須早于需要屏蔽的同步 Message,否則無效,后面的原理會提及。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        startBarrierThread()
        // 建立一個同步屏障
        postSyncBarrier(barrierHandler.looper)
        testNormalMessage()
        testSyncBarrierByHandler()
    }

    private fun postSyncBarrier(looper: Looper) {
        Log.d(...)
        val method: Method = MessageQueue::class.java.getDeclaredMethod("postSyncBarrier")
        barrierToken = method.invoke(looper.queue) as Int
    }

這樣子便可以看到,異步 Message 執(zhí)行了,而且同步 Message 永遠得不到執(zhí)行。

09-24 23:11:36.176 28600 28600 D MainActivity: onCreate()
09-24 23:11:36.296 28600 28600 D MainActivity: Add sync barrier
09-24 23:11:36.300 28600 28629 D MainActivity: Barrier handler message occurred & what:2

原因在于建立的同步屏障尚未移除,永遠只處理隊列里的異步 Message。想要讓同步 Message 恢復執(zhí)行的話 remove 同步屏障即可,同樣也需要反射!

我們在異步 Handler 執(zhí)行結束后移除同步屏障。

    private fun startBarrierThread() {
        ...
        barrierHandler = Handler.createAsync(handlerThread.looper) { msg ->
            Log.d(...)
            // 移除同步屏障
            removeSyncBarrier(barrierHandler.looper)
            true
        }
    }

    fun removeSyncBarrier(looper: Looper) {
        Log.d(...)
        val method = MessageQueue::class.java
            .getDeclaredMethod("removeSyncBarrier", Int::class.javaPrimitiveType)
        method.invoke(looper.queue, barrierToken)
    }

可以看到同步 Message 恢復了。

09-24 23:10:31.533 28539 28539 D MainActivity: onCreate()
09-24 23:10:31.652 28539 28568 D MainActivity: Barrier handler message occurred & what:2
09-24 23:10:31.652 28539 28568 D MainActivity: Remove sync barrier
09-24 23:10:31.653 28539 28568 D MainActivity: Normal handler message occurred & what:1

通過異步 Message 實現(xiàn)

沒有專用的異步 Handler 的時候,可以向普通 Handler 發(fā)送一個 isAsync 屬性為 true 的 message,效果和異步 Handler 是一樣的。當然這種方式仍舊需要建立同步屏障。

在原有的發(fā)送 Message 的函數(shù)里加入 isAsync 的重載參數(shù)。

    private fun sendMessageRightNow(handler: Handler, what: Int, isAsync: Boolean = false) {
        Message.obtain().let {
            it.what = what
            it.isAsynchronous = isAsync
            handler.sendMessage(it)
        }
    }

向普通 Handler 發(fā)送異步 Message。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        testNormalMessage()
        // 改用 Message 方式發(fā)送異步 Message
        testSyncBarrierByMessage()
    }

    private fun testSyncBarrierByMessage() {
        sendMessageRightNow(normalHandler, 2, true)
    }

同樣記得在異步 Message 收到后移除同步屏障。

    private fun startBarrierThread() {
        ...
        normalHandler = Handler(handlerThread.looper) { msg ->
            Log.d(...)
            if (2 == msg.what) removeSyncBarrier(barrierHandler.looper)
            true
        }
    }

結果和異步 Handler 的方式一致。

09-24 23:58:05.801 29040 29040 D MainActivity: onCreate()
09-24 23:58:05.923 29040 29040 D MainActivity: Add sync barrier
09-24 23:58:05.923 29040 29070 D MainActivity: Normal handler message occurred & what:2
09-24 23:58:05.924 29040 29070 D MainActivity: Remove sync barrier
09-24 23:58:05.924 29040 29070 D MainActivity: Normal handler message occurred & what:1

原理

先來看一下同步屏障是怎么建立的。

// MessageQueue.java
    // 默認是調(diào)用的時刻開始建立屏障
    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    // 同步屏障支持指定開始的時刻
    // 默認是調(diào)用的時刻,而 0 表示?
    private int postSyncBarrier(long when) {
        synchronized (this) {
            // 同步屏障可以建立多個,用計數(shù)的 Token 變量識別
            final int token = mNextBarrierToken++;

            // 獲取一個屏障 Message
            // 其 target 屬性為空
            // 指定 when 屬性為屏障的開始時刻
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            // 將 Token 存入屏障 Message
            // 用以識別對應的同步屏障
            msg.arg1 = token;

            // 按照 when 的先后
            // 找到屏障 Message 插入隊列的適當位置
            // 所以,如果同步屏障的建立調(diào)用得晚
            // 那么在它之前的 Message 無法阻攔
            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }

            // 將屏障 Message 插入
            if (prev != null) {
                msg.next = p;
                prev.next = msg;
            } else {
                // 如果隊列尚無 Message
                // 或隊首的 Message 時刻
                // 都比屏障 Message 要晚的話
                // 將屏障 Message 插入隊首
                msg.next = p;
                mMessages = msg;
            }
            
            // 返回上面的 Token 給調(diào)用端
            // 主要用于移除對應的屏障
            return token;
        }
    }

再來看下異步 Message 如何執(zhí)行。

// MessageQueue.java
    Message next() {
        ...
        for (;;) {
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                // 隊首是屏障 Message 的話
                // 遍歷找到下一個異步 Message
                if (msg != null && msg.target == null) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                
                // 沒有建立同步屏障且隊里有 Message
                // 或者
                // 建立了同步屏障下且找到了異步 Message
                if (msg != null) {
                    // 如果當前時間尚早于目標執(zhí)行時刻
                    if (now < msg.when) {
                        // 更新下次循環(huán)應當休眠的超時時間
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        mBlocked = false;
                        
                        // Message 找到了并出隊
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }

                        // Message 返回
                        msg.next = null;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // 隊里尚無 Message
                    // 或建立了同步屏障,但尚無異步 Message
                    // 無限休眠
                    nextPollTimeoutMillis = -1;
                }
                ...
            }
            ...
            pendingIdleHandlerCount = 0;
            nextPollTimeoutMillis = 0;
        }
    }

最后看一下同步屏障如何移除。

// MessageQueue.java
    // 需要傳入 add 時返回的 Token
    public void removeSyncBarrier(int token) {
        synchronized (this) {
            Message prev = null;
            Message p = mMessages;
            // 遍歷隊列直到找到 token 吻合的屏障 Message
            while (p != null && (p.target != null || p.arg1 != token)) {
                prev = p;
                p = p.next;
            }
            
            // 如果沒找到會拋出異常
            if (p == null) {
                throw new IllegalStateException("The specified message queue synchronization "
                        + " barrier token has not been posted or has already been removed.");
            }
            final boolean needWake;
            
            // 將屏障 Message 移除
            
            // 如果屏障 Message 不在隊首的話
            // 無需喚醒
            if (prev != null) {
                prev.next = p.next;
                needWake = false;
            } else {
                // 屏障 Message 在隊首
                // 且新的隊首存在且不是另一個屏障的話
                // 需要立即喚醒
                mMessages = p.next;
                needWake = mMessages == null || mMessages.target != null;
            }
            p.recycleUnchecked();

            // 喚醒以立即處理后面的 Message
            if (needWake && !mQuitting) {
                nativeWake(mPtr);
            }
        }
    }

對原理進行簡單的總結:

  1. 同步屏障的建立:按照調(diào)用的時刻 when 在合適的位置放入一個屏障 Message(target 屬性為 null)來實現(xiàn),同時得到標識屏障的計數(shù) token 存入屏障 Message
  2. 讀取隊列的時候發(fā)現(xiàn)存在屏障 Message 的話,會遍歷隊列并返回最早執(zhí)行的異步 Message
  3. 同步屏障的移除:按照 token 去隊列里找到匹配的屏障 Message 進行出隊操作,如果出隊后隊首存在 Message 且非另一個同步屏障的話,立即喚醒 looper 線程

結論和應用

結論:

  • 可以通過異步 Handler,也可以通過異步 Message 兩種方式向 MessageQueue 添加異步 Message
  • 但都需要事先建立同步屏障,屏障的建立時間必須在阻攔的 Message 發(fā)出之前
  • 可以建立多個同步屏障,將按照指定的時刻排隊,通過計數(shù) Token 進行識別
  • 同步屏障使用完之后記得移除,否則后續(xù)的 Message 永遠阻塞

和插隊執(zhí)行 Message 的區(qū)別:

  • 插隊 Message 只能確保先執(zhí)行,完了后續(xù)的 Message 還得執(zhí)行
  • 異步 Message 則不同,同步屏障一旦建立將保持休眠,直到異步 Message 抵達。只有同步屏障被撤銷,后續(xù) Message 才可恢復執(zhí)行

應用:

AOSP 系統(tǒng)中使用異步 Message 最典型的地方要屬屏幕刷新,刷新任務的 Message 不希望被主線程的 Message 隊列阻塞,所以在發(fā)送刷新 Message 之前都會建立一個同步屏障,確保刷新任務優(yōu)先執(zhí)行。

// ViewRootImpl.java
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

屏障建立之后發(fā)送異步 Message。

// Choreographer.java
    private void postCallbackDelayedInternal(...) {
        synchronized (mLock) {
            ...

            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

空閑執(zhí)行 “Message”

MessageQueue 提供的 IdleHandler 可以讓隊列在空閑的時候回調(diào)(queueIdle())指定的邏輯,它本質(zhì)上不是 Message 類型,但它在 MessageQueue 里調(diào)度的時候類似于 Message 的邏輯,姑且將它也理解成一種特殊的 ”Message“。

使用上很簡單,調(diào)用 MessageQueue 的 addIdleHandler() 添加實現(xiàn)即可,執(zhí)行完之后無需再度執(zhí)行的話需要調(diào)用 removeIdleHandler() 移除,或在回調(diào)里返回 false。

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        testIdleHandler()
    }

    private fun testIdleHandler() {
        Log.d("MainActivity","testIdleHandler() start")
        mainHandler.looper.queue.addIdleHandler {
            Log.d("MainActivity", "testIdleHandler() queueIdle callback")
            false
        }
        Log.d("MainActivity","testIdleHandler() end ")
    }

可以看到 addIdleHandler 調(diào)用之后并沒有立即執(zhí)行,而是過了幾百 ms,queueIdle() 才得到了執(zhí)行。

09-23 22:56:46.130  7732  7732 D MainActivity: onCreate()
09-23 22:56:46.281  7732  7732 D MainActivity: testIdleHandler() start
09-23 22:56:46.281  7732  7732 D MainActivity: testIdleHandler() end
09-23 22:56:46.598  7732  7732 D MainActivity: testIdleHandler() queueIdle callback

如果在 addIdleHandler 調(diào)用之后接著發(fā)送一串非延時 Message,queueIdle() 是先執(zhí)行還是后執(zhí)行呢?

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        testIdleHandler()
        testSendMessages()
    }

結果顯示一堆 Message 執(zhí)行完了之后,仍舊過了幾百 ms,queueIdle() 才得到了執(zhí)行。

09-23 23:07:50.639  7926  7926 D MainActivity: onCreate()
09-23 23:07:50.856  7926  7926 D MainActivity: testIdleHandler() start
09-23 23:07:50.856  7926  7926 D MainActivity: testIdleHandler() end
09-23 23:07:50.856  7926  7926 D MainActivity: startSendMessage() start
09-23 23:07:50.857  7926  7926 D MainActivity: startSendMessage() end
09-23 23:07:50.914  7926  7926 D MainActivity: Main thread message occurred & what:1
...
09-23 23:07:50.916  7926  7926 D MainActivity: Main thread message occurred & what:10
09-23 23:07:51.132  7926  7926 D MainActivity: testIdleHandler() queueIdle callback

上述結果也可以理解,MessageQueue 里仍有一堆 Message 等待處理,并非空閑狀態(tài)。所以需要執(zhí)行完之后才有機會回調(diào) queueIdle() 。

那如果發(fā)送的是延時 Message 呢?

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    testIdleHandler()
    testSendDelayedMessages()
}

因為發(fā)送的是延時 Message,MessageQueue 暫時是空閑的,會先將 IdleHandler 取出來處理。

09-23 23:21:36.135  8161  8161 D MainActivity: onCreate()
09-23 23:21:36.339  8161  8161 D MainActivity: testIdleHandler() start
09-23 23:21:36.340  8161  8161 D MainActivity: testIdleHandler() end
09-23 23:21:36.340  8161  8161 D MainActivity: testSendDelayedMessages() start
09-23 23:21:36.340  8161  8161 D MainActivity: testSendDelayedMessages() end
09-23 23:21:36.729  8161  8161 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:21:38.844  8161  8161 D MainActivity: Main thread message occurred & what:1
...
09-23 23:21:38.845  8161  8161 D MainActivity: Main thread message occurred & what:10

上面的 queueIdle() 返回了 false 確保處理后 Handler 得到了移除。

但如果返回 true 且沒有調(diào)用 removeIdleHandler() 的話,后續(xù)空閑的時候 Handler 還會被執(zhí)行,這點需要留意!

    private fun testIdleHandler() {
        mainHandler.looper.queue.addIdleHandler {
            ...
            true // false
        }
    }

queueIdle() 因為沒被移除的緣故被回調(diào)了多次,源自于 Looper 沒執(zhí)行完一次 Message 后發(fā)現(xiàn)尚無 Message 的時候都會回調(diào)一遍 IdleHandler,直到隊列一直沒有 Message 到來。

09-23 23:24:04.765  8226  8226 D MainActivity: onCreate()
09-23 23:24:05.010  8226  8226 D MainActivity: testIdleHandler() start
09-23 23:24:05.011  8226  8226 D MainActivity: testIdleHandler() end
09-23 23:24:05.368  8226  8226 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:24:05.370  8226  8226 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:24:05.378  8226  8226 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:24:05.381  8226  8226 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:24:05.459  8226  8226 D MainActivity: testIdleHandler() queueIdle callback

那如果 add 完不移除的 IdleHandler 之后,發(fā)送一個延時 Message,那便會導致空閑消息多執(zhí)行一遍。

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    testIdleHandler()
    sendDelayedMessage(mainHandler, 1)
}

09-23 23:31:53.928  8620  8620 D MainActivity: onCreate()
09-23 23:31:54.042  8620  8620 D MainActivity: testIdleHandler() start
09-23 23:31:54.042  8620  8620 D MainActivity: testIdleHandler() end
09-23 23:31:54.272  8620  8620 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:31:54.273  8620  8620 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:31:54.278  8620  8620 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:31:54.307  8620  8620 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:31:54.733  8620  8620 D MainActivity: testIdleHandler() queueIdle callback
09-23 23:31:56.546  8620  8620 D MainActivity: Main thread message occurred & what:1
09-23 23:31:56.546  8620  8620 D MainActivity: testIdleHandler() queueIdle callback

為什么?

queueIdle() 的回調(diào)由 MessageQueue#next() 回調(diào)。

// MessageQueue.java
    Message next() {
        ...
        // 循環(huán)的初次將待處理 IdleHandler 計數(shù)置為 -1
        // 保證第一次可以檢查 Idle Handler 的存在和調(diào)用
        int pendingIdleHandlerCount = -1;
        int nextPollTimeoutMillis = 0;
        for (;;) {
            ...
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                // 隊首的 Message 且建立了同步屏障的話,尋找下一個異步 Message
                if (msg != null && msg.target == null) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                
                // 找到了合適的 Message
                if (msg != null) {
                    // 如果當前時間尚早于目標執(zhí)行時刻
                    // 設置休眠的超時時間,即當前時間與目標時刻的差值
                    if (now < msg.when) {
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        mBlocked = false;
                        
                        // 時間條件滿足 Message 出隊
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }

                        // 并返回 Message
                        msg.next = null;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // 隊里尚無合適的 Message
                    // 進入無限休眠
                    nextPollTimeoutMillis = -1;
                }

                // 如果正在退出 Looper,結束循環(huán)并返回 null
                // 將促使 loop() 退出
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // 如果沒有合適的 Message 且 Looper 沒有退出
                // 檢查是否有 Idle Handler 需要處理

                // 讀取 Idle Handler 列表
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                
                // 如果暫時沒有 Idle Handler 需要處理,則進入下一次循環(huán)
                // 為使下次循環(huán)如果出現(xiàn)新的 Idle Handler 能有機會執(zhí)行
                // 不重置計數(shù)器,仍為初始值 -1
                if (pendingIdleHandlerCount <= 0) {
                    mBlocked = true;
                    continue;
                }

                // 如果 IdleHandler 存在則拷貝到待處理列表
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 遍歷待處理 Idle Handlers
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null;

                boolean keep = false;
                try {
                    // 逐個回調(diào) queueIdle()
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                // 回調(diào)返回 false,則將其移除出 Idle 列表
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // 處理完之后重置 IdleHandler 的計數(shù)
            // 保證下次循環(huán)不會重復處理 IdleHandler
            pendingIdleHandlerCount = 0;
            nextPollTimeoutMillis = 0;
        }
    }

有幾點細節(jié)需要留意:

  1. next() 循環(huán)的第一次將 count 置為 -1,確保隊列空閑的時候必然有機會處理 IdleHandler
  2. 如果暫無 IdleHandler 可以處理直接進入下一次循環(huán),并且保留 count 的處置,確保下次循環(huán)可以檢查是否有新的 IdleHandler 加入進來
  3. IdleHandler 正常處理結束之后,避免下次循環(huán)重復處理,會將 count 置為 0,保證下次不再檢查。注意:是下次循環(huán),不是永久不檢查

結論和應用

結論: IdleHandler 可以實現(xiàn) MessageQueue 空閑狀態(tài)下的任務執(zhí)行,比如做一些啟動時的輕量級初始化任務。但由于其執(zhí)行的時機依賴于隊列的 Message 狀態(tài),不太可控,謹慎使用!

應用:AOSP 源碼里有不少地方使用了 IdleHandler 機制,比如 ActivityThread 使用它在空閑的狀態(tài)下進行 GC 回收處理。

// ActivityThread.java
    final class GcIdler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
            doGcIfNeeded();
            purgePendingResources();
            return false;
        }
    }
    
    void scheduleGcIdler() {
        if (!mGcIdlerScheduled) {
            mGcIdlerScheduled = true;
            Looper.myQueue().addIdleHandler(mGcIdler);
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }

    void unscheduleGcIdler() {
        if (mGcIdlerScheduled) {
            mGcIdlerScheduled = false;
            Looper.myQueue().removeIdleHandler(mGcIdler);
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }

名詞回顧

非延時、延時以及插隊執(zhí)行這幾種 Message 大家使用較多,無需贅述。但其他幾個冷僻的 Message 術語需要總結一下,供大家快速對比和加深理解。

Message 術語 介紹
異步 Message isAsync 屬性為 true 需要異步執(zhí)行的 Message,需要配合同步屏障使用
異步 Handler 專門發(fā)送異步 Message 的 Handler
屏障 Message target 為空并持有 token 信息的 Message 實例放入隊列,作為同步屏障的起點
同步屏障 在 MessageQueue 指定時刻插入屏障 Message 確保只有異步 Message 執(zhí)行的機制
空閑 IdleHandler 用于在 MessageQueue 空閑的時候回調(diào)的處理接口,若不移除每次隊列空閑了均會執(zhí)行

結語

上述對于各種 Message 和 IdleHandler 做了演示和原理闡述,相信對于它的細節(jié)有了更深的了解。

下面來進行一個簡單的總結:

  • 非延時執(zhí)行 Message:并非立即執(zhí)行,而是按照請求的時刻進行排隊和調(diào)度,最終取決于隊列的順序和主線程是否空閑
  • 延時執(zhí)行 Message:也并非在 Delay 的時刻立即執(zhí)行,執(zhí)行時刻受喚醒誤差和線程任務阻塞的影響必然晚于 Delay 時刻
  • 插隊執(zhí)行 Mesage:同樣并非立即執(zhí)行,而是每次都將任務放在了隊首,達到先執(zhí)行的目的,但打亂了執(zhí)行順序存在邏輯隱患
  • 異步 Message:系統(tǒng)使用居多,App 則需反射,通過這種機制可插隊執(zhí)行同時確保其他 Message 阻塞,學習一下
  • IdleHandler “Message”:系統(tǒng)多有使用,實現(xiàn) MessageQueue 空閑狀態(tài)下的任務執(zhí)行,但執(zhí)行時機不可控,最好執(zhí)行完之后便移除,謹慎使用
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

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