ViewRootImpl的scheduleTraversals如

2022-08-10  本文已影响0人  super可乐

从问题出发🤔

我们知道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

上一篇 下一篇

猜你喜欢

热点阅读