Android Choreographer 源码分析

Choreographer 的做用主要是配合 Vsync ,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 Vsync 到来的时候 ,系统经过对 Vsync 信号周期的调整,来控制每一帧绘制操做的时机。目前大部分手机都是 60Hz 的刷新率,也就是 16.6ms 刷新一次,系统为了配合屏幕的刷新频率,将 Vsync 的周期也设置为 16.6 ms,每一个 16.6 ms , Vsync 信号唤醒 Choreographer 来作 App 的绘制操做,这就是引入 Choreographer 的主要做用。了解 Choreographer 还能够帮助 App 开发者知道程序每一帧运行的基本原理,也能够加深对 Message、Handler、Looper、MessageQueue、Measure、Layout、Draw 的理解html

主线程运行机制的本质

在介绍 Choreographer 以前,咱们先理一下 Android 主线程运行的本质,其实就是 Message 的处理过程,咱们的各类操做,包括每一帧的渲染,手势操做 ,都是经过 Message 的形式发给主线程的 MessageQueue ,MessageQueue 处理完消息继续等下一个消息,以下图所示java

MethodTrace 图示android

Systrace 图示并发

能够发现,每一帧时间都是固定的。因此一旦一个 Message 的处理时间超过了 16.6ms 就会引发卡顿。关于如何发现卡顿,能够参考文章:app

Choreographer 简介

Choreographer 扮演 Android 渲染链路中承上启下的角色less

  1. 承上:负责接收和处理 App 的各类更新消息和回调,等到 Vsync 到来的时候统一处理。好比集中处理 Input(主要是 Input 事件的处理) 、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操做) ,判断卡顿掉帧状况,记录 CallBack 耗时等socket

  2. 启下:负责请求和接收 Vsync 信号。接收 Vsync 事件回调(经过 FrameDisplayEventReceiver.onVsync );请求 Vsync(FrameDisplayEventReceiver.scheduleVsync) .ide

从上面能够看出来, Choreographer 担任的是一个工具人的角色,他之因此重要,是由于经过 Choreographer + SurfaceFlinger + Vsync + TripleBuffer 这一套从上到下的机制,保证了 Android App 能够以一个稳定的帧率运行(目前大部分是 60fps),减小帧率波动带来的不适感。函数

Choreographer 的工做流程

  • Choreographer 初始化SurfaceFlinger 的 appEventThread 唤醒发送 Vsync ,Choreographer 回调 FrameDisplayEventReceiver.onVsync , 进入 Choreographer 的主处理函数 doFrame工具

    • 初始化 FrameHandler ,绑定 Looper

    • 初始化 FrameDisplayEventReceiver ,与 SurfaceFlinger 创建通讯用于接收和请求 Vsync

    • 初始化 CallBackQueues

  • Choreographer.doFrame 计算掉帧逻辑

  • Choreographer.doFrame 处理 Choreographer 的第一个 callback : input

  • Choreographer.doFrame 处理 Choreographer 的第二个 callback : animation

  • Choreographer.doFrame 处理 Choreographer 的第三个 callback : insets animation

  • Choreographer.doFrame 处理 Choreographer 的第四个 callback : traversalChoreographer.doFrame 处理 Choreographer 的第五个 callback : commit ?

    • traversal-draw 中 UIThread 与 RenderThread 同步数据

  • RenderThread 处理绘制数据,真正进行渲染

  • 将渲染好的 Buffer swap 给 SurfaceFlinger 进行合成

Choreographer 源码分析

Choreographer 的单例初始化

    // Thread local storage for the choreographer.
    private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
            if (looper == Looper.getMainLooper()) {
                mMainInstance = choreographer;
            }
            return choreographer;
        }
    };

这里采用的是 ThreadLocal 来构造单例,这样每一个线程都会有一个属于本身的 choreographer 实例。

接下去看 choreographer 的构造函数

    private Choreographer(Looper looper, int vsyncSource) {
        mLooper = looper;
        mHandler = new FrameHandler(looper);
     // 这里能够发现只有在为 true 的时候才会使用 vsync mDisplayEventReceiver
= USE_VSYNC ? new FrameDisplayEventReceiver(looper, vsyncSource) : null; mLastFrameTimeNanos = Long.MIN_VALUE;      // 每一帧的间隔是根据刷新频率来的 mFrameIntervalNanos = (long)(1000000000 / getRefreshRate()); mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
     // 给每一种回调类型都建立了一个队列
for (int i = 0; i <= CALLBACK_LAST; i++) { mCallbackQueues[i] = new CallbackQueue(); } // b/68769804: For low FPS experiments. setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1)); }

这里作了几个初始化操做,根据Looper对象生成,Looper和线程是一对一的关系,对应上面说明里的每一个线程对应一个Choreographer。

  • 初始化FrameHandler。接收处理消息。

  • 初始化FrameDisplayEventReceiver。FrameDisplayEventReceiver用来接收垂直同步脉冲,就是VSync信号,VSync信号是一个时间脉冲,通常为60HZ,用来控制系统同步操做,怎么同ChoreoGrapher一块儿工做的,将在下文介绍。

  • 初始化mLastFrameTimeNanos(标记上一个frame的渲染时间)以及mFrameIntervalNanos(帧率,fps,通常手机上为1s/60)。

  • 初始化CallbackQueue,callback队列,将在下一帧开始渲染时回调。

接下去看看 FrameHandler 和 FrameDisplayEventReceiver 的结构。

    private final class FrameHandler extends Handler {
        public FrameHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_DO_FRAME:
                    doFrame(System.nanoTime(), 0);
                    break;
                case MSG_DO_SCHEDULE_VSYNC:
                    doScheduleVsync();
                    break;
                case MSG_DO_SCHEDULE_CALLBACK:
                    doScheduleCallback(msg.arg1);
                    break;
            }
        }
    }

看上面的代码,就是一个简单的Handler。处理3个类型的消息。

  • MSG_DO_FRAME:开始渲染下一帧的操做

  • MSG_DO_SCHEDULE_VSYNC:请求 Vsync 信号

  • MSG_DO_SCHEDULE_CALLBACK:请求执行 callback

下面再细分一下,分别详细看一下这三个步骤是怎么实现的。

FrameDisplayEventReceiver

    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);
        }

        // TODO(b/116025192): physicalDisplayId is ignored because SF only emits VSYNC events for
        // the internal display and DisplayEventReceiver#scheduleVsync only allows requesting VSYNC
        // for the internal display implicitly.
        @Override
        public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
            // Post the vsync event to the Handler.
            // The idea is to prevent incoming vsync events from completely starving
            // the message queue.  If there are no messages in the queue with timestamps
            // earlier than the frame time, then the vsync event will be processed immediately.
            // Otherwise, messages that predate the vsync event will be handled first.
            long now = System.nanoTime();
            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;
            }

            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;
            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);
        }
    }
FrameDisplayEventReceiver 继承自 DisplayEventReceiver,同时也实现了Runnable 接口,是处于 Choreographer 中的私有内部类。当接收到底层的 VSync 信号开始处理 UI 过程。VSync 信号由 SurfaceFlinger 实现并定时发送。FrameDisplayEventReceiver 收到信号后,调用 onVsync 方法组织消息发送到主线程处理。这个消息主要内容就是 run 方法里面的 doFrame 了,这里 mTimestampNanos 是信号到来的时间参数。

那么 FrameDisplayEventReceiver 是经过什么方式在 Vsync 信号到来的时候回调 onVsync 呢?答案是 FrameDisplayEventReceiver 的初始化的时候,最终经过监听文件句柄的形式,其对应的初始化流程以下

// android/view/Choreographer.java
private Choreographer(Looper looper, int vsyncSource) {
    mLooper = looper;
    mDisplayEventReceiver = USE_VSYNC
            ? new FrameDisplayEventReceiver(looper, vsyncSource)
            : null;
    ......
}
// android/view/Choreographer.java public FrameDisplayEventReceiver(Looper looper, int vsyncSource) { super(looper, vsyncSource); }
// android/view/DisplayEventReceiver.java public DisplayEventReceiver(Looper looper, int vsyncSource) { ...... mMessageQueue = looper.getQueue(); mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue, vsyncSource); }

nativeInit 后续的代码能够本身跟一下,能够对照这篇文章和源码,因为篇幅比较多,这里就不细说了。

简单来讲,FrameDisplayEventReceiver 的初始化过程当中,经过 BitTube (本质是一个 socket pair),来传递和请求 Vsync 事件,当 SurfaceFlinger 收到 Vsync 事件以后,经过 appEventThread 将这个事件经过 BitTube 传给 DisplayEventDispatcher ,DisplayEventDispatcher 经过 BitTube 的接收端监听到 Vsync 事件以后,回调 Choreographer.FrameDisplayEventReceiver.onVsync ,触发开始一帧的绘制。

以下图

ChoreoGrapher 的整体流程

FrameHandler 和 FrameDisplayEventReceiver 是怎么工做的呢?ChoreoGrapher 的整体流程图以下图(拷贝的图片):

 以上是整体的流程图:

  1. PostCallBack or postFrameCallback发起添加回调,这个FrameCallBack将在下一帧被渲染时执行。

  2. AddToCallBackQueue,将 FrameCallBack 添加到回调队列里面,等待时机执行回调。每种类型的callback按照设置的执行时间(dueTime)顺序排序分别保存在一个单链表中。

  3. 判断 FrameCallBack设定的执行时间是否在当前时间以后,如果,发送 MSG_DO_SCHEDULE_CALLBACK 消息到主线程,安排执行doScheduleCallback,安排执行CallBack。不然直接跳到第4步。

  4. 执行 scheduleFrameLocked,安排执行下一帧。

  5. 判断上一帧是否已经执行,若未执行,当前操做直接结束。若已经执行,根据状况执行如下六、7步。

  6. 若使用垂直同步信号进行同步,则执行7.不然,直接跳到9。

  7. 若当前线程是UI线程,则经过执行scheduleVsyncLocked请求垂直同步信号。不然,送MSG_DO_SCHEDULE_VSYNC消息到主线程,安排执行doScheduleVsync,在主线程调用scheduleVsyncLocked。

  8. 收到垂直同步信号,调用FrameDisplayEventReceiver.onVsync(),发送消息到主线程,请求执行doFrame。

  9. 执行doFrame,渲染下一帧。

主要的工做在 doFrame 中,接下来咱们具体看看 doFrame 函数都干了些什么。
从名字看很容易理解 doFrame 函数就是开始进行下一帧的显示工做。好了如下源代码又来了,咱们一行一行分析一下吧。

doFrame

    @UnsupportedAppUsage
    void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
       // 为false, 说明还未开始
if (!mFrameScheduled) { return; // no work to do } if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) { mDebugPrintNextFrameTimeDelta = false; Log.d(TAG, "Frame time delta: " + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms"); } long intendedFrameTimeNanos = frameTimeNanos; startNanos = System.nanoTime();
       // 计算当前时间与 vsync 信号的时间差
final long jitterNanos = startNanos - frameTimeNanos;
       // 说明出现掉帧状况,注意只有 jitterNanos 大于 16.6 ms 才说明掉帧,不然只是轻微的延迟。
if (jitterNanos >= mFrameIntervalNanos) { final long skippedFrames = jitterNanos / mFrameIntervalNanos; if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { Log.i(TAG, "Skipped " + skippedFrames + " frames! " + "The application may be doing too much work on its main thread."); }
          // 当发生掉帧后,须要计算被耽误的时间。好比处理了 36.6ms, 一个周期是 16.6 ms, 至关于延迟了 3.4 ms 执行
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos; if (DEBUG_JANK) { Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms " + "which is more than the frame interval of " + (mFrameIntervalNanos * 0.000001f) + " ms! " + "Skipping " + skippedFrames + " frames and setting frame " + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past."); }
          // 修正当前帧的时间 = 开始时间 - 耽误时间 frameTimeNanos
= startNanos - lastFrameOffset; }        // 当前时间小于前一帧时间,不执行操做 if (frameTimeNanos < mLastFrameTimeNanos) { if (DEBUG_JANK) { Log.d(TAG, "Frame time appears to be going backwards. May be due to a " + "previously skipped frame. Waiting for next vsync."); }
          // 直接请求下一个 vsync 信号 scheduleVsyncLocked();
return; }        // 大于 1 说明采用的默认帧数的一半,所以须要根据时间间隔来判断是否有必要执行绘制 if (mFPSDivisor > 1) { long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos; if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
            // 时间间隔小于指定的时间,继续请求下一个 vsync 信号 scheduleVsyncLocked();
return; } }        // 保存当前帧的相关信息 mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos); mFrameScheduled = false; mLastFrameTimeNanos = frameTimeNanos; } try {
       // 执行相关 callbacks 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); doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos); mFrameInfo.markPerformTraversalsStart(); doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); } finally { AnimationUtils.unlockAnimationClock(); Trace.traceEnd(Trace.TRACE_TAG_VIEW); } if (DEBUG_FRAMES) { final long endNanos = System.nanoTime(); Log.d(TAG, "Frame " + frame + ": Finished, took " + (endNanos - startNanos) * 0.000001f + " ms, latency " + (startNanos - frameTimeNanos) * 0.000001f + " ms."); } } 

总结起来其实主要是两个操做:

设置当前 frame 的启动时间

判断是否跳帧,若跳帧修正当前 frame 的启动时间到最近的 VSync 信号时间。若是没跳帧,当前 frame 启动时间直接设置为当前 VSync 信号时间。修正完时间后,不管当前 frame 是否跳帧,使得当前 frame 的启动时间与 VSync 信号仍是在一个节奏上的,可能延后了一到几个周期,可是都是在下一个 vsync 信号到来才进行处理 。

以下图所示是时间修正的一个例子,


 
没有跳帧但延迟
 

因为第二个 frame 执行超时,第三个 frame 实际启动时间比第三个 VSync 信号到来时间要晚,由于这时候延时比较小,没有超过一个时钟周期,系统仍是将 frameTimeNanos3 传给回调,回调拿到的时间和 VSync 信号同步。

再来看看下图:

跳帧

因为第二个 frame执行时间超过 2 个时钟周期,致使第三个 frame 延后执行时间大于一个时钟周期,系统认为这时候影响较大,断定为跳帧了,将第三个 frame 的时间修正为 frameTimeNanos4,比 VSync 真正到来的时间晚了一个时钟周期。

时间修正,既保证了doFrame操做和 VSync 保持同步节奏,又保证明际启动时间与记录的时间点相差不会太大,便于同步及分析。

顺序执行callBack队列里面的callback

而后接下来看看 doCallbacks 的执行过程:

    void doCallbacks(int callbackType, long frameTimeNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {
            // We use "now" to determine when callbacks become due because it's possible
            // for earlier processing phases in a frame to post callbacks that should run
            // in a following phase, such as an input event that causes an animation to start.
            final long now = System.nanoTime();
       // callbacks
= mCallbackQueues[callbackType].extractDueCallbacksLocked( now / TimeUtils.NANOS_PER_MS); if (callbacks == null) { return; } mCallbacksRunning = true; // Update the frame time if necessary when committing the frame. // We only update the frame time if we are more than 2 frames late reaching // the commit phase. This ensures that the frame time which is observed by the // callbacks will always increase from one frame to the next and never repeat. // We never want the next frame's starting frame time to end up being less than // or equal to the previous frame's commit frame time. Keep in mind that the // next frame has most likely already been scheduled by now so we play it // safe by ensuring the commit time is always at least one frame behind.
       // commit 类型是最后执行的,若是此时发现前面处理时间过长,就会进行纠正。
if (callbackType == Choreographer.CALLBACK_COMMIT) { final long jitterNanos = now - frameTimeNanos; Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos); if (jitterNanos >= 2 * mFrameIntervalNanos) { final long lastFrameOffset = jitterNanos % mFrameIntervalNanos + mFrameIntervalNanos; if (DEBUG_JANK) { Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f) + " ms which is more than twice the frame interval of " + (mFrameIntervalNanos * 0.000001f) + " ms! " + "Setting frame time to " + (lastFrameOffset * 0.000001f) + " ms in the past."); mDebugPrintNextFrameTimeDelta = true; } frameTimeNanos = now - lastFrameOffset; mLastFrameTimeNanos = frameTimeNanos; } } } try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]); for (CallbackRecord c = callbacks; c != null; c = c.next) { if (DEBUG_FRAMES) { Log.d(TAG, "RunCallback: type=" + callbackType + ", action=" + c.action + ", token=" + c.token + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime)); }
          // 运行每个 callback 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); } }

callback的类型有如下 4 种,除了文章一开始提到的 3 种外,还有一个 CALLBACK_COMMIT。

  1. CALLBACK_INPUT:输入

  2. CALLBACK_ANIMATION:动画

  3. CALLBACK_TRAVERSAL:遍历,执行 measure、layout、draw

  4. CALLBACK_COMMIT:遍历完成的提交操做,用来修正动画启动时间

而后看上面的源码,分析一下每一个 callback 的执行过程:

1. callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked( now / TimeUtils.NANOS_PER_MS);

获得执行时间在当前时间以前的全部 CallBack,保存在单链表中。每种类型的 callback 按执行时间前后顺序排序分别存在一个单链表里面。为了保证当前 callback 执行时新 post 进来的 callback 在下一个 frame 时才被执行,这个地方 extractDueCallbacksLocked 会将须要执行的 callback 和之后执行的 callback 断开变成两个链表,新 post 进来的 callback 会被放到后面一个链表中。当前 frame 只会执行前一个链表中的 callback,保证了在执行 callback 时,若是callback中Post相同类型的callback,这些新加的 callback 将在下一个 frame 启动后才会被执行。

2. 接下来,看一大段注释,若是类型是 CALLBACK_COMMIT,而且当前 frame 渲染时间超过了两个时钟周期,则将当前提交时间修正为上一个垂直同步信号时间。为了保证下一个frame 的提交时间和当前 frame 时间相差为一且不重复。
这个地方注释挺难看懂,实际上这个地方 CALLBACK_COMMIT 是为了解决 ValueAnimator 的一个问题而引入的,主要是解决由于遍历时间过长致使动画时间启动过长,时间缩短,致使跳帧,这里修正动画第一个 frame 开始时间延后来改善,这时候才表示动画真正启动。为何不直接设置当前时间而是回溯一个时钟周期以前的时间呢?看注释,这里若是设置为当前 frame 时间,由于动画的第一个 frame 其实已经绘制完成,第二个 frame 这时候已经开始了,设置为当前时间会致使这两个 frame 时间同样,致使冲突。

详细状况请看官方针对这个问题的修改。Fix animation start jank due to expensive layout operations.

以下图所示:

 
修正commit时间

好比说在第二个frame开始执行时,开始渲染动画的第一个画面,第二个frame执行时间超过了两个时钟周期,Draw操做执行结束后,这时候完成了动画第一帧的渲染,动画实际上还没开始,可是时间已通过了两个时钟周期,后面动画实际执行时间将会缩短一个时钟周期。这时候系统经过修正commit时间到frameTimeNanos的上一个VSync信号时间,即完成动画第一帧渲染以前的VSync信号到来时间,修正了动画启动时间,保证动画执行时间的正确性。

调用 c.run(frameTimeNanos) 执行回调

    private static final class CallbackRecord {
        public CallbackRecord next;
        public long dueTime;
        public Object action; // Runnable or FrameCallback
        public Object token;

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

CallbackRecord 的代码如上所示。

发起绘制的请求

doFrame 的逻辑了解清楚了,可是关于发起 vsync 请求的逻辑却没有讲。

// ViewRootImpl    
 @UnsupportedAppUsage
    void scheduleTraversals() {
     // 若是已经请求绘制了,就不会再次请求,由于屡次请求,只有有一个执行就知足要求了
if (!mTraversalScheduled) { mTraversalScheduled = true;
       // 同步 mTraversalBarrier
= mHandler.getLooper().getQueue().postSyncBarrier();
       // 发送一个 callback 用于在下一帧来临时候处理 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable,
null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); }
       // 通知稍候绘制 notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }

 接着会调用 postCallbackDelayed:

    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,保存 callback,并发起请求下一帧。

    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "PostCallback: type=" + callbackType
                    + ", action=" + action + ", token=" + token
                    + ", delayMillis=" + 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 {
          // 发送一个延迟 msg Message msg
= mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); } } }

简单来讲,就是判断当前是否有必要发起一个绘制请求,好比你发了一个 500ms 后重绘的消息,对于这个消息,会在 500ms 后在进行处理。但若是不是延迟消息,那说明须要当即处理。可是对于 view 的绘制逻辑,必须得等到下一个 vsync 到来的时候才会真正进行绘制。

接下来看看 scheduleFrameLocked 的逻辑:

   private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) {
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame on 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.
         // 若是是在一个 looper 线程中,那么直接执行请求就好 if (isRunningOnLooperThreadLocked()) { scheduleVsyncLocked(); } else {
            // 若是是在主线程,那么须要发送一个请求 vsync 的消息,并插到最前面,须要确保前一个消息处理完后在开始请求 Message msg
= mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC); msg.setAsynchronous(true); mHandler.sendMessageAtFrontOfQueue(msg); } } else {
          // 若是不用 vsync 信号,那么就能够直接执行,只是须要记录每一帧的时间
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); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, nextFrameTime); } } }

这里主要是根据具体状况来判断如何发起下一帧的绘制。对于采用 vsync 信号的主线程,会发送一个 MSG_DO_SCHEDULE_VSYNC 的消息,插到最前面,确保能够最先执行。

当收到 MSG_DO_SCHEDULE_VSYNC 消息后,就会给他安排请求 vsync 信号的请求,最后会会调到 onVsync 方法。

    void doScheduleVsync() {
        synchronized (mLock) {
            if (mFrameScheduled) {
                scheduleVsyncLocked();
            }
        }
    }


    @UnsupportedAppUsage
    private void scheduleVsyncLocked() {
       // 发起获取 vsync 信号的请求
        mDisplayEventReceiver.scheduleVsync();
    }   

    /**
     * Schedules a single vertical sync pulse to be delivered when the next
     * display frame begins.
     */
    @UnsupportedAppUsage
    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);
        }
    } 
到这里,就把 Choreographer 的基本原理都讲完了。
 

源码小结

  • Choreographer 是线程单例的,并且必需要和一个 Looper 绑定,由于其内部有一个 Handler 须要和 Looper 绑定,通常是 App 主线程的 Looper 绑定

  • DisplayEventReceiver 是一个 abstract class,其 JNI 的代码部分会建立一个IDisplayEventConnection 的 Vsync 监听者对象。这样,来自 AppEventThread 的 VSYNC 中断信号就能够传递给 Choreographer 对象了。当 Vsync 信号到来时,DisplayEventReceiver 的 onVsync 函数将被调用。

  • DisplayEventReceiver 还有一个 scheduleVsync 函数。当应用须要绘制UI时,将首先申请一次 Vsync 中断,而后再在中断处理的 onVsync 函数去进行绘制。

  • Choreographer 定义了一个 FrameCallbackinterface,每当 Vsync 到来时,其 doFrame 函数将被调用。这个接口对 Android Animation 的实现起了很大的帮助做用。之前都是本身控制时间,如今终于有了固定的时间中断。

  • Choreographer 的主要功能是,当收到 Vsync 信号时,去调用使用者经过 postCallback 设置的回调函数。目前一共定义了五种类型的回调,它们分别是:ListView 的 Item 初始化(obtain\setup) 会在 input 里面也会在 animation 里面,这取决于

    • CALLBACK_INPUT : 处理输入事件处理有关

    • CALLBACK_ANIMATION : 处理 Animation 的处理有关

    • CALLBACK_INSETS_ANIMATION : 处理 Insets Animation 的相关回调

    • CALLBACK_TRAVERSAL : 处理和 UI 等控件绘制有关

    • CALLBACK_COMMIT : 处理 Commit 相关回调

  • CALLBACK_INPUT 、CALLBACK_ANIMATION 会修改 view 的属性,因此要比 CALLBACK_TRAVERSAL 先执行

 
最后附上一张时序图,来回顾一下整个流程
在这里插入图片描述
 
另外,知道源码后,也就知道如何去发现 app 卡顿状况了。 详见  Android 教你如何发现 APP 卡顿

 

参考文章

Android 基于 Choreographer 的渲染机制详解

相关文章
相关标签/搜索