Android消息循環的同步屏障機制及UI渲染性能的提升(Android Q)

我們在 Handler線程通信機制:實戰、原理、性能優化! 中已經知道了Handler的消息同步機制,在MessageQueue的next方法中,有一段邏輯是處理同步屏障的,我們在本章中將分析同步屏障是什麼?原理?以及它在Android中的使用。

同步屏障機制的原理


在Handler的介紹中,我們已經瞭解了同步屏障,但當時這塊不是重點介紹的內容,我們來複習一下它的實現原理。

MessageQueue的next方法:

    Message next() {
        ……
        for (;;) {
            ……
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                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());
                }
                ……
    }

在MessageQueue的next方法中,獲取當前執行的消息時,首先對消息進行了判斷,如果當前消息是同步屏障消息,則屏蔽後續所有同步消息的執行,異步消息不受影響。

同步屏障消息對象的target屬性是null,也就是沒有持有Handler對象的引用。

這裏我們回想一下,如果消息通過Handler的sendMessage方法添加到消息隊列時,會判斷target是否爲null,如果是null則會拋出錯誤,所以同步屏障消息的添加不是通過Handler的常用方法添加的。

同步屏障的添加和移除


既然同步屏障不是通過Handler的sendMessage等方法添加的,那麼它又是通過哪個方法進行設置的呢?

我們在MessageQueue中發現是postSyncBarrier方法添加的同步屏障消息。

同步屏障的添加

MessageQueue的postSyncBarrier方法:

    @TestApi
    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    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) {
            //獲取token值
            final int token = mNextBarrierToken++;
            //生成一個同步屏障消息
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            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;
            }
            return token;
        }
    }
邏輯解析:
  1. postSyncBarrier方法調用了有參的postSyncBarrier方法,傳遞了一個執行時間的參數。
  2. 在同步代碼塊中,執行添加邏輯。
  3. 首先通過mNextBarrierToken屬性獲取一個int類型的token,它也是該方法的返回值,用於移除屏障時使用。
  4. 獲取了一個新的Message對象,注意這裏沒有設置target屬性,正常的消息target屬性不能爲空。
  5. 根據when屬性,將同步屏障消息添加到消息隊列的合適位置。
  6. 最後返回token。

注意:添加同步屏障,不需要喚醒線程。

同步屏障的移除

同步屏障添加後,並不能自動移除,需要調用系統接口手動移除。

MessageQueue的removeSyncBarrier方法:

    public void removeSyncBarrier(int token) {//參數token就是我們添加同步屏障時的返回值。
        // Remove a sync barrier token from the queue.
        // If the queue is no longer stalled by a barrier then wake it.
        synchronized (this) {
            Message prev = null;
            Message p = mMessages;
            //如果當前消息不是同步屏障消息,則遍歷消息鏈表進行查找
            while (p != null && (p.target != null || p.arg1 != token)) {
                prev = p;
                p = p.next;
            }
            if (p == null) {//沒有找到則拋出error
                throw new IllegalStateException("The specified message queue synchronization "
                        + " barrier token has not been posted or has already been removed.");
            }
            final boolean needWake;//表示是否需要喚醒線程
            if (prev != null) {//表示同步屏障還未執行,所以不需要喚醒消息循環(喚醒線程)
                prev.next = p.next;
                needWake = false;
            } else {//同步屏障已經執行,需要喚醒消息循環(喚醒線程)
                mMessages = p.next;
                needWake = mMessages == null || mMessages.target != null;
            }
            p.recycleUnchecked();

            // If the loop is quitting then it is already awake.
            // We can assume mPtr != 0 when mQuitting is false.
            if (needWake && !mQuitting) {
                nativeWake(mPtr);
            }
        }
    }
邏輯解析:
  1. MessageQueue的removeSyncBarrier方法可以移除同步屏障,參數是添加消息屏障的返回值token,用於移除指定的消息屏障。
  2. removeSyncBarrier方法首先判斷當前消息mMessages,是否是同步屏障消息,如果當前消息不是同步屏障消息,則遍歷消息鏈表進行查找。
  3. 如果沒有找到同步屏障消息,則拋出Error,否則繼續執行。
  4. 如果同步屏障消息尚未執行,則刪除該消息即可,不用執行喚醒消息循環。
  5. 否則,當前消息既是同步屏障消息,刪除該消息後,需要根據條件,進一步判斷是否需要喚醒消息循環。
  6. 最後,如果需要喚醒消息循環,並且當前消息循環未執行退出操作,則調用nativeWake方法執行喚醒操作。

同步消息屏障在Android系統中的使用場景

在Android系統中,當應用進行視圖的刷新渲染時,用到了同步消息屏障,這裏使用的目的是:優先保障視圖的刷新邏輯的執行,避免UI線程執行其他同步任務導致的卡頓等問題。

ViewRootImpl的scheduleTraversals方法:

    @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

ViewRootImpl的scheduleTraversals方法,調用了MessageQueue的postSyncBarrier方法來添加一個同步消息屏障,然後執行視圖渲染相關邏輯。

ViewRootImpl的unscheduleTraversals方法

當系統完成UI視圖的渲染刷新後,會調用ViewRootImpl的unscheduleTraversals方法移除同步屏障。

    void unscheduleTraversals() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            mChoreographer.removeCallbacks(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }

總結


本文章我們學習了Android消息循環的同步屏障機制原理,以及Android系統中的使用,我們來簡單總結下:

  1. 在MessageQueue的next方法中,獲取當前執行的消息時,首先對消息進行了判斷,如果當前消息是同步屏障消息,則屏蔽後續所有同步消息的執行,異步消息不受影響。
  2. 同步屏障消息對象的target屬性是null,也就是沒有持有Handler對象的引用。
  3. 同步屏障的消息可以通過MessageQueue的postSyncBarrier方法進行添加,添加時,不需要進行消息循環的喚醒操作。
  4. MessageQueue的postSyncBarrier方法的返回值是一個int類型的token,用於移除屏障時使用。
  5. 同步屏障添加後,並不能自動移除,需要調用系統接口手動移除。
  6. MessageQueue的removeSyncBarrier方法可以移除同步屏障,參數是添加消息屏障的返回值token。
  7. 如果同步屏障消息尚未執行,則刪除該消息即可,不用執行喚醒消息循環。
  8. 當前消息既是同步屏障消息,刪除該消息後,需要根據條件,進一步判斷是否需要喚醒消息循環,如果需要喚醒消息循環,並且當前消息循環未執行退出操作,則調用nativeWake方法執行喚醒操作。
  9. 在Android系統中,當應用進行視圖的刷新渲染時,ViewRootImpl的scheduleTraversals方法中,用到了同步消息屏障。這裏使用的目的是:優先保障視圖的刷新邏輯的執行,避免UI線程執行其他同步任務導致的卡頓等問題。
  10. 當系統完成UI視圖的渲染刷新後,會調用ViewRootImpl的unscheduleTraversals方法移除同步屏障。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章