美文网首页
源码分析_Android UI何时刷新_Choreographe

源码分析_Android UI何时刷新_Choreographe

作者: 拙峰朽木 | 来源:发表于2018-03-24 13:49 被阅读174次

    @(Android源码解析)
    高级UI系列:
    setContentView源码分析_看AppCompatActivity是如何实现兼容的
    源码分析_Activity是如何显示的?
    源码分析_Android UI何时刷新:Choreographer

    requestLayout和invalidate都干了些什么

    之前我们在分析Activity是如何显示的时候,看到它调用了requestLayout然后走了重新绘制流程,其实我们在自定义View时经常用到requestLayout,invalidate等方法,我们调用它们的目的就是告诉系统我们要刷新下界面,但是实际上是他们去刷新界面吗?下面我们来看下:
    我们之前分析过当我调用requestLayout的时候不管你在哪调最后都会传递到ViewRootImp中,由它来统一调用,其实invalidate也是一样。其实关于页面绘制有关的操作最后都是通过ViewRootImp来实现的。

    我们看下ViewRootImp中的requestLayout和invalidate代码:

    
     @Override
        public void requestLayout() {
            if (!mHandlingLayoutInLayoutRequest) {
                checkThread();
                mLayoutRequested = true;
                scheduleTraversals();
            }
        }
      void invalidate() {
            mDirty.set(0, 0, mWidth, mHeight);
            if (!mWillDrawSoon) {
                scheduleTraversals();
            }
        }
    

    他们两个都调用了scheduleTraversals()翻译过来的意思就是安排遍历的意思。
    其实我们只要留心会发现很多地方都调了scheduleTraversals();

     @Override
        public void requestChildFocus(View child, View focused) {
            if (DEBUG_INPUT_RESIZE) {
                Log.v(mTag, "Request child focus: focus now " + focused);
            }
            checkThread();
            scheduleTraversals();
        }
    
        @Override
        public void clearChildFocus(View child) {
            if (DEBUG_INPUT_RESIZE) {
                Log.v(mTag, "Clearing child focus");
            }
            checkThread();
            scheduleTraversals();
        }
    @Override
        public void recomputeViewAttributes(View child) {
            checkThread();
            if (mView == child) {
                mAttachInfo.mRecomputeGlobalAttributes = true;
                if (!mWillDrawSoon) {
                    scheduleTraversals();
                }
            }
        }
    
        @Override
        public void requestFitSystemWindows() {
            checkThread();
            mApplyInsetsRequested = true;
            scheduleTraversals();
        }
    

    非常多我就不一一贴出来了,大体能看出来都是些跟页面显示状态有关的。所以我们猜测下 scheduleTraversals();他可能就是我们能刷新页面的关键。
    总结下:在各处调用的invalidate和requestLayout最终都是调ViewRootImp中的 scheduleTraversals()方法

    scheduleTraversals

    下面看scheduleTraversals()的代码

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

    看到里面有个postCallback方法,并有个mTraversalRunnable看名字可以猜应该是执行遍历的一个线程。不过一看这个就是个Handler那种是等待被执行的而非立即执行。我们先看下这个TraversalRunnable

     final class TraversalRunnable implements Runnable {
            @Override
            public void run() {
                doTraversal();
            }
        }
    void doTraversal() {
            if (mTraversalScheduled) {
                mTraversalScheduled = false;
                mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
    
                if (mProfile) {
                    Debug.startMethodTracing("ViewAncestor");
                }
    
                performTraversals();
    
                if (mProfile) {
                    Debug.stopMethodTracing();
                    mProfile = false;
                }
            }
        }
    

    最终执行的performTraversals();这个方法干了些什么我们在源码分析--Activity是如何显示的?中已经分析了大约800行代码核心如下:

    
        private void performTraversals(){
            ...
            performMeasure();
            ...
            performLayout();
            ...
            performDraw();
            ...
    
        }
    

    正是我们熟悉的三步走,所以performTraversals()是实现页面刷新的逻辑,但是很明显这个刷新是等待被通知的,那么何时被刷新呢?

    mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    

    我们看到这个Choreographer.CALLBACK_TRAVERSAL,事件是被Choreographer Post的,所以Choreographer应该就是通知我们何时刷新的类。

    Choreographer

    我们可以看下这个类的注释:Coordinates the timing of animations, input and drawing
    我翻译为控制输入、动画。绘制的时机。
    可以看出这个类是告诉我们何时去执行输入、动画和绘制的。
    还有一个注释也很重要:
    The choreographer receives timing pulses (such as vertical synchronization) from the display subsystem then schedules work to occur as part of rendering the next display frame.
    下面是个人的翻译:
    Choreographer从显示的子系统中接受到类似垂直同步的时间脉冲,然后安排在下一帧中要呈现的工作。
    这个注释告诉我们Choreographer要干两件事:

    • 接收垂直同步的时间脉冲,
    • 安排在下一帧中要做的工作。

    好,我们回到之前的代码postCallback:

    public void postCallback(int callbackType, Runnable action, Object token) {
            postCallbackDelayed(callbackType, action, token, 0);
        }
        
     public void postCallbackDelayed(int callbackType,
                Runnable action, Object token, long delayMillis) {
            if (action == null) {
                throw new IllegalArgumentException("action must not be null");
            }
            if (callbackType < 0 || callbackType > CALLBACK_LAST) {
                throw new IllegalArgumentException("callbackType is invalid");
            }
    
            postCallbackDelayedInternal(callbackType, action, token, delayMillis);
        }    
    

    postCallbackDelayedInternal();

     private void postCallbackDelayedInternal(int callbackType,
                Object action, Object token, long delayMillis) {
            ...
               synchronized (mLock) {
                final long now = SystemClock.uptimeMillis();
                final long dueTime = now + delayMillis;
                mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
    
                if (dueTime <= now) {
                    scheduleFrameLocked(now);
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                    msg.arg1 = callbackType;
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtTime(msg, dueTime);
                }
            }
        }
    
    

    这里有个是否立即执行的判断,如果立即执行则走scheduleFrameLocked(now);否则使用Handler在等待执行,我们搜下MSG_DO_SCHEDULE_CALLBACK可以找到:

    
    case MSG_DO_SCHEDULE_CALLBACK:
                        doScheduleCallback(msg.arg1);
                        break;
    
     void doScheduleCallback(int callbackType) {
            synchronized (mLock) {
                if (!mFrameScheduled) {
                    final long now = SystemClock.uptimeMillis();
                    if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
                        scheduleFrameLocked(now);
                    }
                }
            }
        }
    

    所以都是走scheduleFrameLocked(now);区别在于是否立即执行。接着看scheduleFrameLocked()

      private void scheduleFrameLocked(long now) {
            if (!mFrameScheduled) {
                mFrameScheduled = true;
                if (USE_VSYNC) {
                   // If running on the Looper thread, then schedule the vsync immediately,
                    // otherwise post a message to schedule the vsync from the UI thread
                    // as soon as possible.
                    if (isRunningOnLooperThreadLocked()) {
                        scheduleVsyncLocked();
                    } else {
                        Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                        msg.setAsynchronous(true);
                        mHandler.sendMessageAtFrontOfQueue(msg);
                    }
                } else {
                    
                    Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtTime(msg, nextFrameTime);
                }
            }
        }
    

    USE_VSYNC:

      // Enable/disable vsync for animations and drawing.
        private static final boolean USE_VSYNC = SystemProperties.getBoolean(
                "debug.choreographer.vsync", true);
    

    所以if里面是true,至于false的代码我个人猜测是安排在下一帧再刷新。看还是看true都干了啥,这里跟上面一样不过是对是否为ui线程做了个判断,如果是ui线程立即执行 scheduleVsyncLocked();否则通过Handler切换回ui线程,并将这个事件放在队列最前方然后执行scheduleVsyncLocked();

    private void scheduleVsyncLocked() {
            mDisplayEventReceiver.scheduleVsync();
        }
        
        /**
         * Schedules a single vertical sync pulse to be delivered when the next
         * display frame begins.
         */
        public void scheduleVsync() {
            if (mReceiverPtr == 0) {
                Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                        + "receiver has already been disposed.");
            } else {
                nativeScheduleVsync(mReceiverPtr);
            }
        }
    
    

    nativeScheduleVsync方式是native的目前看不了,所以下面我们就不跟了。不过我们可以看下scheduleVsync()这个方法的注释:Schedules a single vertical sync pulse to be delivered when the next display frame begins.,我个人翻译为:安排一个垂直同步脉冲在下一次显示帧开始时发送。
    其实就是在下一帧的时候安排一个垂直同步脉冲(Vsync)给我,这个我是谁呢?就是这个方法所属的类啊
    DisplayEventReceiver。一看就名字:显示事件的接收者

    总结下:postCallback的核心就是让DisplayEventReceiver注册了个Vsync的通知。

    DisplayEventReceiver

    上面看到了我们注册了对Vsync的监听,那么在哪接收Vsync呢
    我们看到Choreographer中有个FrameDisplayEventReceiver里面重写了onVsync()run()方法。

      private final class FrameDisplayEventReceiver extends DisplayEventReceiver
                implements Runnable {
            private boolean mHavePendingVsync;
            private long mTimestampNanos;
            private int mFrame;
    
            public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
                super(looper, vsyncSource);
            }
    
            @Override
            public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
                .......
                Message msg = Message.obtain(mHandler, this);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
            }
    
            @Override
            public void run() {
                mHavePendingVsync = false;
                doFrame(mTimestampNanos, mFrame);
            }
        }
    

    我们可以看到当我们接收到Vsync消息后,通过Handler执行了run()里面的代码。那么我们接下来就看doFrame()中做了什么:

    
     void doFrame(long frameTimeNanos, int frame) {
            .......
            try {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
                AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
    
                mFrameInfo.markInputHandlingStart();
                doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
    
                mFrameInfo.markAnimationsStart();
                doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
    
                mFrameInfo.markPerformTraversalsStart();
                doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
    
                doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
            } finally {
                AnimationUtils.unlockAnimationClock();
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
    
            ....
        }
    
    

    除去一些计算操作,我们看到它主要执行了几个doCallBack,而里面有个Choreographer.CALLBACK_ANIMATION就是PostCallBack里面的。我们看下doCallback();

    
    void doCallbacks(int callbackType, long frameTimeNanos) {
            CallbackRecord callbacks;
            .....
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                        now / TimeUtils.NANOS_PER_MS);
            .....
            try {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
                for (CallbackRecord c = callbacks; c != null; c = c.next) {
                    ....
                    c.run(frameTimeNanos);
                }
            } finally {
                synchronized (mLock) {
                    mCallbacksRunning = false;
                    do {
                        final CallbackRecord next = callbacks.next;
                        recycleCallbackLocked(callbacks);
                        callbacks = next;
                    } while (callbacks != null);
                }
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
        }
    

    doCallbacks就是将CallbackQueues中的的CallbackRecord一个个取出来并执行run()方法,

    public void run(long frameTimeNanos) {
                if (token == FRAME_CALLBACK_TOKEN) {
                    ((FrameCallback)action).doFrame(frameTimeNanos);
                } else {
                    ((Runnable)action).run();
                }
            }
    

    run()其实就是执行了postCallback放进去的mTraversalRunnable。
    现在在回看postCallback:

     mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
                        
    
    public void postCallback(int callbackType, Runnable action, Object token) {
            postCallbackDelayed(callbackType, action, token, 0);
        }
       
    
     public void postCallbackDelayed(int callbackType,
                Runnable action, Object token, long delayMillis) {
            .....
            postCallbackDelayedInternal(callbackType, action, token, delayMillis);
        }
     private void postCallbackDelayedInternal(int callbackType,
                Object action, Object token, long delayMillis) {
            .....
                mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
    
            ....  
        }
    
    public void addCallbackLocked(long dueTime, Object action, Object token) {
                CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
                .....
            }
            
        private CallbackRecord obtainCallbackLocked(long dueTime, Object action, Object token) {
            CallbackRecord callback = mCallbackPool;
            if (callback == null) {
                callback = new CallbackRecord();
            } else {
                mCallbackPool = callback.next;
                callback.next = null;
            }
           .......
        }
    

    ViewRootImp在执行scheduleTraversals()方法时就是把包含Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable的信息封装到了CallbackRecord的对象中,并添加到CallbackQueue,现在doCallback中将其取出然后执行run()方法,它的run()就是执行 doTraversal();

        final class TraversalRunnable implements Runnable {
            @Override
            public void run() {
                doTraversal();
            }
        }
    

    至此基本就串上了。

    总结

    我们调用View的requestLayout或者invalidate时,最终都会触发ViewRootImp执行scheduleTraversals()方法。这个方法中ViewRootImp会通过Choreographer来注册个接收Vsync的监听,当接收到系统体层发送来的Vsync后我们就执行doTraversal()来重新绘制界面。通过上面的分析我们调用invalidate等刷新操作时,系统并不会立即刷新界面,而是等到Vsync消息后才会刷新页面。

    当然Choreographer还会通知动画输入等其他事件。至于关于Vsync的分析可以参考一下博客:
    破译Android性能优化中的16ms问题
    Android垂直同步信号VSync的产生及传播结构详解

    相关文章

      网友评论

          本文标题:源码分析_Android UI何时刷新_Choreographe

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