源码分析_Android UI何时刷新_Choreographer

@(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的产生及传播结构详解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容