Handler 同步屏障造成ANR分析

問題

Handler同步屏障是否會導致ANR?

結論

同步屏障的使用有可能會導致ANR

分析

這里簡單理解就是Choreographer每16ms回調(diào)doFrame,內(nèi)部原理參考屏幕刷新機制

源碼分析 ViewRootImpl#invalidate#scheduleTraversals

ViewRootImpl#invalidate#scheduleTraversals

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //通過Handler發(fā)送一個同步屏障到消息隊列中
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //通過Choreographer發(fā)送callBack,等待垂直同步信號vsync回調(diào)doFrame
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

 void unscheduleTraversals() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            //移除同步屏障消息
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            mChoreographer.removeCallbacks(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }
    //doFrame回調(diào)后,調(diào)用doTraversal方法
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            //移除同步屏障消息
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

從上述可以源碼可以分析得知,我們在調(diào)用View刷新的時候,會調(diào)用到View#invalid,從而觸發(fā)到ViewRootImpl#scheduleTraversals,繼而會添加同步屏障到消息隊列,來阻塞MessageQueue的普通消息,等待mTraversalRunnable#Choreographer#doFrame()回調(diào)后,移除掉當前的同步屏障,讓其他普通消息,能夠正常消費。

那么MessageQueue是如何判斷是否需要阻塞的?MessageQueue#next

  Message next() {
          nativePollOnce(ptr, nextPollTimeoutMillis);

            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在輪訓消息的時候,執(zhí)行next()方法,會不斷去獲取msg對象,如果msg.target ==null,那么它就是屏障,需要循環(huán)遍歷,一直往后找到第一個異步的消息。同樣的,當我們移除掉同步屏障之后,也就不會執(zhí)行當前判斷體內(nèi),如下源碼分析

//移除同步屏障消息
 mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
MessageQueue####
public void removeSyncBarrier(int token) {
        // Remove a sync barrier token from the queue.
        // If the queue is no longer stalled by a barrier then wake it.
        synchronized (this) {
          //從消息鏈表中移除
            if (prev != null) {
                prev.next = p.next;
                needWake = false;
            } else {
                mMessages = p.next;
                needWake = mMessages == null || mMessages.target != null;
            }
            p.recycleUnchecked();

    }
Message##### 回收這個Message到對象池中。
void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

重點看這里,移除屏障消息。
if (prev != null) {
prev.next = p.next;
needWake = false;
} 。
到此基礎的同步屏障理論基本串聯(lián)起來,我們正常在使用View更新的時候,都會走到同步屏障里面來,為了保障消息的優(yōu)先級。

  • 到此也可以分析一個問題。如果同步屏障的消息沒有及時處理掉,是因為Choreographer#doFrame沒有及時回調(diào),導致后續(xù)的普通消息被阻塞,沒有在5S內(nèi)消費掉,從而產(chǎn)生ANR。

需要注意的地方

  1. 不要在子線程做view#invalid的操作,根據(jù)上述源碼,我們可以知道,控制同步屏障的添加跟移除是通過mTraversalScheduled這個變量來控制的,會出現(xiàn)多線程的異常,嚴重的可能導致,多線程多次調(diào)用iew#invalid,導致同步屏蔽被多次添加到隊列中,但是確沒有完全移除。這就是谷歌推薦我們?nèi)绻枰谧泳€程更新UI。需要使用postInvalidate。
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

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