美文网首页
ViewRootImpl的scheduleTraversals如

ViewRootImpl的scheduleTraversals如

作者: super可乐 | 来源:发表于2022-08-10 14:03 被阅读0次

    从问题出发🤔

    我们知道UI绘制的主要入口是ViewRootImpl.scheduleTraversals,在这个方法中会向Choreographer注册一个回调,在回调方法中进行UI绘制操作,那么问题来了,在调用scheduleTraversals之后如果有大量的消息进入消息队列中,是否会卡住UI绘制?它能否保证UI绘制优先?带着问题读一下源码(Android-31)

    阅读源码🧐

    首先看scheduleTraversals方法

    ViewRootImpl.java
    
        void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
              //往MessageQueue发送一个同步屏障
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
              //向Choreographer注册CALLBACK_TRAVERSAL类型的回调:mTraversalRunnable,在其run方法中会调用doTraversal进行UI绘制
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
                notifyRendererOfFramePending();
                pokeDrawLockIfNeeded();
            }
        }
    
        void doTraversal() {
            if (mTraversalScheduled) {
                mTraversalScheduled = false;
                //移除同步屏障
                mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            .....
                //UI绘制
                performTraversals();
            .....
            }
        }
    

    在scheduleTraversals和doTraversal中出现了消息队列中同步屏障的概念,后面会讲到,这里先看Choreographer.postCallback做了什么

    Choreographer.java
    
        public void postCallback(int callbackType, Runnable action, Object token) {
            postCallbackDelayed(callbackType, action, token, 0);
        }
        
        private void postCallbackDelayedInternal(int callbackType,
                Object action, Object token, long delayMillis) {
                ......
            synchronized (mLock) {
                final long now = SystemClock.uptimeMillis();
                final long dueTime = now + delayMillis;
                //根据callbackType的类型添加到不同的回调队列
                mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
            //当delayMillis为0时(上文传递的为0)
                if (dueTime <= now) {
                    scheduleFrameLocked(now);
                }
                //当delayMillis不为0时,实际上也是走到scheduleFrameLocked方法中去
                else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                    msg.arg1 = callbackType;
                    //为message设置异步标志
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtTime(msg, dueTime);
                }
            }
        }
    
        private void scheduleFrameLocked(long now) {
            if (!mFrameScheduled) {
                mFrameScheduled = true;
                if (USE_VSYNC) {
                    //判断当前线程是否为Choreographer的工作线程,实际上也就是判断是否为主线程,这里为true
                    if (isRunningOnLooperThreadLocked()) {
                        scheduleVsyncLocked();
                    } 
                    //线程不对应,实际上也是走到scheduleVsyncLocked方法中去
                    else {
                        Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    //为message设置异步标志
                        msg.setAsynchronous(true);
                        //将消息插入到消息队列的队首,优先执行
                        mHandler.sendMessageAtFrontOfQueue(msg);
                    }
                } else {
                    final long nextFrameTime = Math.max(
                            mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
                    if (DEBUG_FRAMES) {
                        Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
                    }
                    Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                    //为message设置异步标志
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtTime(msg, nextFrameTime);
                }
            }
        }
    
        private void scheduleVsyncLocked() {
            ......
            mDisplayEventReceiver.scheduleVsync();
            ......
        }
    
    

    mDisplayEventReceiver的类型是定义在Choreographer中的内部类FrameDisplayEventReceiver,继承自DisplayEventReceiver,并且实现了Runnable接口,其scheduleVsync方法定义在父类DisplayEventReceiver中

    DisplayEventReceiver.java
    
        public void scheduleVsync() {
            ......
        nativeScheduleVsync(mReceiverPtr);
        }
    
        @FastNative
        private static native void nativeScheduleVsync(long receiverPtr);
    
        public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
                VsyncEventData vsyncEventData) {
        }
    

    nativeScheduleVsync是个native方法,用于向SurfaceFlinger注册下一次Vsync信号的监听,在信号到来的时候native会调用onVsync方法,具体实现看FrameDisplayEventReceiver

        private final class FrameDisplayEventReceiver extends DisplayEventReceiver
                implements Runnable {
            private boolean mHavePendingVsync;
            private long mTimestampNanos;
            private int mFrame;
            private VsyncEventData mLastVsyncEventData = new VsyncEventData();
    
            public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
                super(looper, vsyncSource, 0);
            }
           
            @Override
            public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
                    VsyncEventData vsyncEventData) {
            .......
                   long now = System.nanoTime();
                   //Vsync时间戳大与当前时间,即信号在未来的时间,打印日志
                   if (timestampNanos > now) {
                       Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
                               + " ms in the future!  Check that graphics HAL is generating vsync "
                               + "timestamps using the correct timebase.");
                       timestampNanos = now;
                   }
                    //是否有正在处理的Vsync信号
                   if (mHavePendingVsync) {
                       Log.w(TAG, "Already have a pending vsync event.  There should only be "
                               + "one at a time.");
                   } else {
                       mHavePendingVsync = true;
                   }
             //记录此次信号的信息
                   mTimestampNanos = timestampNanos;
                   mFrame = frame;
                   mLastVsyncEventData = vsyncEventData;
                //创建消息,callback为this,也即回调run方法
                   Message msg = Message.obtain(mHandler, this);
                //将消息标记为异步
                   msg.setAsynchronous(true);
                //发送消息,指定时间为Vsync信号的时间
                   mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
                  ......
            }
    
            @Override
            public void run() {
                mHavePendingVsync = false;
                  //调用Choreographer的doFrame方法
                doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
            }
        }
    

    在onVsync通过向mHandler发送消息确保run方法中的代码运行在mHandler绑定的Looper对应的线程,也即主线程中,接下来看Choreographer的doFrame方法

    Choreographer.java
    
        void doFrame(long frameTimeNanos, int frame,
                DisplayEventReceiver.VsyncEventData vsyncEventData) {
            final long startNanos;
            final long frameIntervalNanos = vsyncEventData.frameInterval;
            try {
                .......
                synchronized (mLock) {
                    if (!mFrameScheduled) {
                        traceMessage("Frame not scheduled");
                        return; // no work to do
                    }
            .......
                    long intendedFrameTimeNanos = frameTimeNanos;
                    startNanos = System.nanoTime();
                    final long jitterNanos = startNanos - frameTimeNanos;
                    //判断(当前时间 - vsync信号时间)是否大于一帧间隔,也即是否丢帧了
                    if (jitterNanos >= frameIntervalNanos) {
                        final long skippedFrames = jitterNanos / frameIntervalNanos;
                        //丢的帧数达到限制了就打印日志,老熟人了
                        if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                            Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                                    + "The application may be doing too much work on its main thread.");
                        }
                        final long lastFrameOffset = jitterNanos % frameIntervalNanos;
                        .......
                        //将frameTimeNanos修正到离当前时间最近一帧的时间戳
                        frameTimeNanos = startNanos - lastFrameOffset;
                    }
            //当前帧时间戳比上一帧的时间戳小
                    //说明当前的Vsync信号比上一个记录的Vsync信号要旧,重新注册一个Vsync信号监听
                    if (frameTimeNanos < mLastFrameTimeNanos) {
                        ......
                        scheduleVsyncLocked();
                        return;
                    }
            //mFPSDivisor用来放大帧间隔时间,跳过部分帧
                    //假设frameIntervalNanos为1秒,则每分钟60帧,
                    //当mFPSDivisor设置为2之后,就成了2秒响应一帧,每分钟成了30帧
                    if (mFPSDivisor > 1) {
                        long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
                        if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                            traceMessage("Frame skipped due to FPSDivisor");
                            scheduleVsyncLocked();
                            return;
                        }
                    }
            //记录信息
                    mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
                            vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
                    mFrameScheduled = false;
                    mLastFrameTimeNanos = frameTimeNanos;
                    mLastFrameIntervalNanos = frameIntervalNanos;
                    mLastVsyncEventData = vsyncEventData;
                }
            //通知各种类型的监听者
                AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
    
                mFrameInfo.markInputHandlingStart();
                doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);
    
                mFrameInfo.markAnimationsStart();
                doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
                doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,
                        frameIntervalNanos);
    
                mFrameInfo.markPerformTraversalsStart();
                doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
    
                doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
            } finally {
                AnimationUtils.unlockAnimationClock();
            }
                .......
        }
    
       //根据callbackType通知对应的监听者
        void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) {
            CallbackRecord callbacks;
            synchronized (mLock) {
                final long now = System.nanoTime();
                callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                        now / TimeUtils.NANOS_PER_MS);
                if (callbacks == null) {
                    return;
                }
            .......
                for (CallbackRecord c = callbacks; c != null; c = c.next) {
                    c.run(frameTimeNanos);
                }
            }
        }
    

    至此流程就串联起来了

    1. ViewRootImpl#scheduleTraversals 向消息队列发送一个同步屏障,然后向Choreographer注册CALLBACK_TRAVERSAL类型的监听
    2. Choreographer向FrameDisplayEventReceiver注册下一次Vsync信号的监听
    3. FrameDisplayEventReceiver向SurfaceFlinger注册下一次Vsync信号的监听,在信号到来时native回调其onVsync方法
    4. FrameDisplayEventReceiver的onVsync中向Handler发送一个异步消息,在消息回调中将事件通知给Choreographer的各个类型的监听者
    5. ViewRootImpl的mTraversalRunnable的run方法被调用,方法内会调用doTraversal方法,移除消息队列中的同步屏障,触发布局绘制流程

    从代码中可以看到,doTraversal并不是在scheduleTraversals之后立刻执行,属于异步回调,那么在回调之前往主线程的消息队列发送大量消息完全有卡住UI绘制的条件,事实是这样吗?

    同步屏障和异步消息

    ViewRootImpl.scheduleTraversals 向消息队列发送一个同步屏障,看看这个同步屏障有什么作用

    MessageQueue.java
    
        public int postSyncBarrier() {
            return postSyncBarrier(SystemClock.uptimeMillis());
        }
    
        private int postSyncBarrier(long when) {
            synchronized (this) {
                final int token = mNextBarrierToken++;
                //创建同步屏障并插入到队列中,消息的target为null表示这是一个消息屏障
                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) {
                    msg.next = p;
                    prev.next = msg;
                } else {
                    msg.next = p;
                    mMessages = msg;
                }
                return token;
            }
        }
    
        Message next() {
            .......
            for (;;) {
                .......
                synchronized (this) {
                    .......
                    Message prevMsg = null;
                    Message msg = mMessages;
                    //如果队首是同步屏障,则只从队列中取标记为异步的消息
                    if (msg != null && msg.target == null) {
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while (msg != null && !msg.isAsynchronous());
                    }
                    if (msg != null) {
                        ......
                        return msg;
                    }
              ......
            }
        }
    

    当消息队列的队首是一个同步屏障时,只会从队列中取出异步消息。也就是说当ViewRootImpl.scheduleTraversals向消息队列中插入同步屏障之后,插入消息队列的消息需要是异步的才有机会执行,直到ViewRootImpl.doTraversal移除同步屏障才恢复正常。可以看到Choreographer中的消息都通过Message.setAsynchronous(true)标记为了异步消息,则这些与触发UI绘制息息相关的消息是会得到执行的,从而保证了UI绘制的优先

    结论🏄♂️

    可以得出结论,即使ViewRootImpl.scheduleTraversals执行之后我们往消息队列中插入大量消息也不会影响UI绘制,除非我们也把消息设置成异步

    思考💭

    我们在开发中可以利用同步屏障这个特性吗?
    很遗憾MessageQueue.postSyncBarrier是个hide方法,不过如果把方法公开,这里来个屏障那里也来个屏障,不就全乱套了吗
    那在开发过程中有什么方法可以提升消息的优先级吗?
    Handler中有个sendMessageAtFrontOfQueue(message)方法可以将消息插入队列的头部优先执行,对应的api还有postAtFrontOfQueue(Runnable)

    作者:今天想吃什么
    链接:https://juejin.cn/post/7124690172217655304

    相关文章

      网友评论

          本文标题:ViewRootImpl的scheduleTraversals如

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