美文网首页
Handler 同步屏障造成ANR分析

Handler 同步屏障造成ANR分析

作者: 馒Care | 来源:发表于2022-09-20 16:03 被阅读0次

    问题

    Handler同步屏障是否会导致ANR?

    结论

    同步屏障的使用有可能会导致ANR

    分析

    这里简单理解就是Choreographer每16ms回调doFrame,内部原理参考屏幕刷新机制

    源码分析 ViewRootImpl#invalidate#scheduleTraversals

    ViewRootImpl#invalidate#scheduleTraversals
    
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
        void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
                //通过Handler发送一个同步屏障到消息队列中
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                //通过Choreographer发送callBack,等待垂直同步信号vsync回调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回调后,调用doTraversal方法
        void doTraversal() {
            if (mTraversalScheduled) {
                mTraversalScheduled = false;
                //移除同步屏障消息
                mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
    
                if (mProfile) {
                    Debug.startMethodTracing("ViewAncestor");
                }
    
                performTraversals();
    
                if (mProfile) {
                    Debug.stopMethodTracing();
                    mProfile = false;
                }
            }
        }
    

    从上述可以源码可以分析得知,我们在调用View刷新的时候,会调用到View#invalid,从而触发到ViewRootImpl#scheduleTraversals,继而会添加同步屏障到消息队列,来阻塞MessageQueue的普通消息,等待mTraversalRunnable#Choreographer#doFrame()回调后,移除掉当前的同步屏障,让其他普通消息,能够正常消费。

    那么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在轮训消息的时候,执行next()方法,会不断去获取msg对象,如果msg.target ==null,那么它就是屏障,需要循环遍历,一直往后找到第一个异步的消息。同样的,当我们移除掉同步屏障之后,也就不会执行当前判断体内,如下源码分析

    //移除同步屏障消息
     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;
    } 。
    到此基础的同步屏障理论基本串联起来,我们正常在使用View更新的时候,都会走到同步屏障里面来,为了保障消息的优先级。

    • 到此也可以分析一个问题。如果同步屏障的消息没有及时处理掉,是因为Choreographer#doFrame没有及时回调,导致后续的普通消息被阻塞,没有在5S内消费掉,从而产生ANR。

    需要注意的地方

    1. 不要在子线程做view#invalid的操作,根据上述源码,我们可以知道,控制同步屏障的添加跟移除是通过mTraversalScheduled这个变量来控制的,会出现多线程的异常,严重的可能导致,多线程多次调用iew#invalid,导致同步屏蔽被多次添加到队列中,但是确没有完全移除。这就是谷歌推荐我们如果需要在子线程更新UI。需要使用postInvalidate。

    相关文章

      网友评论

          本文标题:Handler 同步屏障造成ANR分析

          本文链接:https://www.haomeiwen.com/subject/nhzmortx.html