Android Choreographer

引言

之前其实并未关注过Choreographer,在一次调试App demo的过程中,偶然发现出现了一条这样的日志:

I/Choreographer: Skipped 1201 frames! The application may be doing too much work on its main thread.

这是一条系统日志,意思很明确:主线程的工作可能过多,导致了掉帧。突然发现Choreographer很有用,可以用来监控App性能、卡顿、帧率等,于是决定花点时间学习一下。

简介

Choreographer类位于android.view包下,是一个final类,是从Android 4.1(API level=16)开始加入的一种机制。Choreographer字面意为“编舞、编导”,从官方文档可以得到以下重要信息:

  • 协调动画、输入和绘图的时间。
  • Choreographer从显示子系统接收定时脉冲,如VSYNC信号,然后调度安排下一帧的渲染工作。
  • 应用程序一般不与Choreographer直接交互,而是在动画框架或视图层次架构中使用更高层的抽象API来调用。
  • 也有一些在App中直接使用Choreographer的场景,如App在不同的线程进行渲染,可能使用GL,没有使用动画框架或视图层次架构,当需要确保能够和显示同步的时候,可以调用Choreographer的 postFrameCallback(Choreographer.FrameCallback)方法。
  • 每一个Looper线程都有自己的Choreographer,其他线程发送的回调只能运行在对应Choreographer所属的Looper线程上。

源码分析

Choreographer源码基于Android 7.1.1,不同版本源码可能有所不同。

  1. 类声明

    public final class Choreographer{}
  2. 构造函数

    private Choreographer(Looper looper) {
       mLooper = looper;
       mHandler = new FrameHandler(looper);
       // 根据是否使用了VSYNC来创建一个FrameDisplayEventReceiver对象
       mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
       mLastFrameTimeNanos = Long.MIN_VALUE;
       // 1秒=1000毫秒=1000000微秒=1000000000纳秒,mFrameIntervalNanos为帧时间间隔,单位纳秒
       mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
       // CALLBACK_LAST + 1 = 4,创建一个容量为4的CallbackQueue数组,用来存放4种不同的Callback
       mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
       for (int i = 0; i <= CALLBACK_LAST; i++) {
           mCallbackQueues[i] = new CallbackQueue();
       }
    }

    Choreographer类中有一个Looper和一个FrameHandler变量。FrameHandler继承自Handler,了解Android消息机制的自然知道Handler需要Looper来调度消息进行处理。

    FrameHandler是Choreographer的一个内部类,其定义如下:

    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:
                   // 请求VSYNC信号
                   doScheduleVsync();
                   break;
               case MSG_DO_SCHEDULE_CALLBACK:
                   doScheduleCallback(msg.arg1);
                   break;
           }
       }
    }

    FrameHandler的实现非常简单,只处理三类消息,具体的常量标识为:

    private static final int MSG_DO_FRAME = 0;
    private static final int MSG_DO_SCHEDULE_VSYNC = 1;
    private static final int MSG_DO_SCHEDULE_CALLBACK = 2;

    下面代码创建一个容量为4的CallbackQueue数组,用来存放4种不同的Callback。

    mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
    for (int i = 0; i <= CALLBACK_LAST; i++) {
       mCallbackQueues[i] = new CallbackQueue();
    }

    具体是哪四种CallBack,通过如下代码可知即Input callback、Animation callback、Traversal callback、Commit callback,分别表示输入、动画、布局绘制、提交操作。

    /**
    * Callback type: Input callback.  Runs first.
    * @hide
    */
    public static final int CALLBACK_INPUT = 0;
    
    /**
    * Callback type: Animation callback.  Runs before traversals.
    * @hide
    */
    public static final int CALLBACK_ANIMATION = 1;
    
    /**
    * Callback type: Traversal callback.  Handles layout and draw.  Runs last
    * after all other asynchronous messages have been handled.
    * @hide
    */
    public static final int CALLBACK_TRAVERSAL = 2;
    /**
    * Callback type: Commit callback.  Handles post-draw operations for the frame.
    * Runs after traversal completes.  The {@link #getFrameTime() frame time} reported
    * during this callback may be updated to reflect delays that occurred while
    * traversals were in progress in case heavy layout operations caused some frames
    * to be skipped.  The frame time reported during this callback provides a better
    * estimate of the start time of the frame in which animations (and other updates
    * to the view hierarchy state) actually took effect.
    * @hide
    */
    public static final int CALLBACK_COMMIT = 3; // 这一类型是在API level=23的时候添加的
  3. 获取Choreographer实例

    /**
        * Gets the choreographer for the calling thread.  Must be called from
        * a thread that already has a {@link android.os.Looper} associated with it.
        *
        * @return The choreographer for this thread.
        * @throws IllegalStateException if the thread does not have a looper.
        */
       public static Choreographer getInstance() {
           return sThreadInstance.get();
       }

    注释写的很清楚,前文也提过,每个线程都有自己的Choreographer,因此此处使用了ThreadLocal机制,另外要求此线程必须拥有Looper,否则消息机制无法执行。

    关于ThreadLocal这块的实现,代码如下:

    // 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!");
           }
           return new Choreographer(looper);
       }
    };

    代码逻辑很清楚,就是利用Choreographer构造函数,为每个线程创建了一个不同的Choreographer对象。

  4. 内部类CallbackRecord

    private static final class CallbackRecord {
       public CallbackRecord next;
       public long dueTime;
       public Object action; // Runnable or FrameCallback
       public Object token;
    
       public void run(long frameTimeNanos) {
           if (token == FRAME_CALLBACK_TOKEN) {
               ((FrameCallback)action).doFrame(frameTimeNanos);
           } else {
               ((Runnable)action).run();
           }
       }
    }

    首先采用了链表实现来封装Callback,用作CallbackRecord的对象池,类似消息机制中Message对象的设计。另外会根据token来区分Callback是Runnable还是FrameCallback,区分条件为token == FRAME_CALLBACK_TOKEN,因为规定所有FrameCallback必须持有如下token:

    // All frame callbacks posted by applications have this token.
    private static final Object FRAME_CALLBACK_TOKEN = new Object() {
       public String toString() { return "FRAME_CALLBACK_TOKEN"; }
    };
  5. 内部类CallbackQueue

    简单来讲,CallbackQueue是一个CallbackRecord的操作类,提供读取、添加、删除CallbackRecord的系列方法,方便操作,具体如下

    • public CallbackRecord extractDueCallbacksLocked(long now)
    • public void addCallbackLocked(long dueTime, Object action, Object token)
    • public void removeCallbacksLocked(Object action, Object token)
  6. 内部类FrameDisplayEventReceiver

    在上文Choreographer的构造函数中提到过,当使用了VSYNC机制时,则会创建一个FrameDisplayEventReceiver对象,主要用来接收VSYNC信号。

    VSYNC信号频率为60HZ,即约每隔16.6ms发出一次VSYNC信号,触发UI渲染,如果每次渲染都能在这个时间间隔内完成,则帧率就能达到60FPS(Frame per second),基于人眼视觉暂留理论,60FPS能达到一种画面流畅的效果。

    当帧率正常时,如下图:

    这里写图片描述

    如果某一帧无法在16.6ms内完成渲染(通常由于布局复杂、耗时操作、过度绘制等),将导致画面无法刷新,对于用户的感觉就是卡顿,假如此帧占用了接下来的N个16.6ms的时间,则造成所谓的丢帧,这里表示丢了N帧。示意图如下:

    这里写图片描述

    回到正题,FrameDisplayEventReceiver继承自DisplayEventReceiver并实现Runnable,定义如下

    private final class FrameDisplayEventReceiver extends DisplayEventReceiver
           implements Runnable {}

    当收到VSYNC信号时,会触发如下回调

    @Override
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {}

    其中timestampNanos表示收到信号的时间,单位纳秒;frame表示帧数,随着每收到一次VSYNC信号而增加。

    在onVsync回调中最主要的逻辑就是将FrameDisplayEventReceiver封装进Message,然后通过Android消息机制发送出去。

    由于FrameDisplayEventReceiver是一个Runnable,其对应的Run实现为

    @Override
    public void run() {
       mHavePendingVsync = false;
       doFrame(mTimestampNanos, mFrame);
    }

    mHavePendingVsync是一个标志变量,标明同一时刻只能有一个VSYNC信号事件。

    doFrame(mTimestampNanos, mFrame);是收到VSYNC信号后的真正处理逻辑,后面会细说。

  7. 接口FrameCallback

    /**
    * Implement this interface to receive a callback when a new display frame is
    * being rendered.  The callback is invoked on the {@link Looper} thread to
    * which the {@link Choreographer} is attached.
    */
    public interface FrameCallback {
       /**
        * Called when a new display frame is being rendered.
        * <p>
        * This method provides the time in nanoseconds when the frame started being rendered.
        * The frame time provides a stable time base for synchronizing animations
        * and drawing.  It should be used instead of {@link SystemClock#uptimeMillis()}
        * or {@link System#nanoTime()} for animations and drawing in the UI.  Using the frame
        * time helps to reduce inter-frame jitter because the frame time is fixed at the time
        * the frame was scheduled to start, regardless of when the animations or drawing
        * callback actually runs.  All callbacks that run as part of rendering a frame will
        * observe the same frame time so using the frame time also helps to synchronize effects
        * that are performed by different callbacks.
        * </p><p>
        * Please note that the framework already takes care to process animations and
        * drawing using the frame time as a stable time base.  Most applications should
        * not need to use the frame time information directly.
        * </p>
        *
        * @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
        * in the {@link System#nanoTime()} timebase.  Divide this value by {@code 1000000}
        * to convert it to the {@link SystemClock#uptimeMillis()} time base.
        */
       public void doFrame(long frameTimeNanos);
    }

    此接口会在新的一帧渲染时回调,参数为纳秒,表示当这一帧开始渲染时的时间,注意此回调会在Choreographer所属的Looper线程上触发。

  8. 提交一个Callback,让它在下一帧执行,最简单的调用为:

    public void postCallback(int callbackType, Runnable action, Object token) {
       postCallbackDelayed(callbackType, action, token, 0);
    }

    注意这里第二个参数表示提交一个Runnable,前文CallbackRecord中分析过会根据token来区分是Runnable或FrameCallback。接下来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);
    }

    主要是对action和Callback类型做校验,callbackType前文说过只有四种类型(CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL、CALLBACK_COMMIT),常量值分别定义为0、1、2、3,其他均为非法类型。这里提前说一句,FrameCallback的回调类型使用CALLBACK_ANIMATION。

    接下来postCallbackDelayedInternal的实现为:

    private void postCallbackDelayedInternal(int callbackType,
           Object action, Object token, long delayMillis) {
       if (DEBUG) {
           Log.d(TAG, "PostCallback: type=" + callbackType
                   + ", action=" + action + ", token=" + token
                   + ", delayMillis=" + delayMillis);
       }
    
       synchronized (mLock) {
           // 当前时间
           final long now = SystemClock.uptimeMillis();
           // 回调执行时间,为当前时间加上延迟的时间
           final long dueTime = now + delayMillis;
           // obtainCallbackLocked(long dueTime, Object action, Object token)会将传入的3个参数转换为CallbackRecord(具体请看源码,非主要部分,此处略过),然后CallbackQueue根据回调类型将CallbackRecord添加到链表上。
           mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
    
           if (dueTime <= now) {
               // 如果delayMillis=0的话,dueTime=now,则会马上执行
               scheduleFrameLocked(now);
           } else {
               // 如果dueTime>now,则发送一个what为MSG_DO_SCHEDULE_CALLBACK类型的定时消息,等时间到了再处理,其最终处理也是执行scheduleFrameLocked(long now)方法
               Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
               msg.arg1 = callbackType;
               msg.setAsynchronous(true);
               mHandler.sendMessageAtTime(msg, dueTime);
           }
       }
    }

    上面主要逻辑在注释里已经写得很清楚了,来看scheduleFrameLocked的实现:

    private void scheduleFrameLocked(long now) {
       if (!mFrameScheduled) {
           mFrameScheduled = true;
           if (USE_VSYNC) {
               // 如果使用了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.
               if (isRunningOnLooperThreadLocked()) {
                   // 请求VSYNC信号,最终会调到Native层,Native处理完成后触发FrameDisplayEventReceiver的onVsync回调,回调中最后也会调用doFrame(long frameTimeNanos, int frame)方法,前文分析FrameDisplayEventReceiver时已说明
                   scheduleVsyncLocked();
               } else {
                   // 在UI线程上直接发送一个what=MSG_DO_SCHEDULE_VSYNC的消息,最终也会调到scheduleVsyncLocked()去请求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.");
               }
               // 直接发送一个what=MSG_DO_FRAME的消息,消息处理时调用doFrame(long frameTimeNanos, int frame)方法
               Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
               msg.setAsynchronous(true);
               mHandler.sendMessageAtTime(msg, nextFrameTime);
           }
       }
    }

    具体流程请看注释,接下来看doFrame的流程:

    void doFrame(long frameTimeNanos, int frame) {
       final long startNanos;
       synchronized (mLock) {
           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();
           // 计算抖动时间,startNanos为真正开始时间,frameTimeNanos为预计回调时间
           final long jitterNanos = startNanos - frameTimeNanos;
           if (jitterNanos >= mFrameIntervalNanos) {
               // 时间差除以每帧时间间隔,来计算丢掉了几帧。其中mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());一般刷新率为60,时间间隔为16.6ms
               final long skippedFrames = jitterNanos / mFrameIntervalNanos;
               // SKIPPED_FRAME_WARNING_LIMIT默认为30,如果丢帧超过30,则输出日志提醒。引言中的日志即是这里输出的
               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 % 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.");
               }
               // 减去偏移时间,来纠正帧时间,以便和VSYNC信号时间保持同步(注意之间可能丢了N个整数帧) 
               frameTimeNanos = startNanos - lastFrameOffset;
           }
    
           // 此情况可能不太常见,解释可参考源码中的Log,大意是可能由于之前的丢帧导致帧回退,继续等待下一次VSYNC信号
           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;
           }
    
           mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
           mFrameScheduled = false;
           mLastFrameTimeNanos = frameTimeNanos;
       }
       // 上面只是一些日志输出及时间纠正,doCallbacks才是真正的回调执行,注意回调是按以下顺序执行的
       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);
       }
    
       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.");
       }
    }

    再来看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.
           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));
               }
               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);
       }
    }

    大致流程是根据回调类型callbackType找到对应的CallbackQueue,然后遍历链表,取出每个CallbackRecord并执行其run方法:

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

    run中会真正执行回调处理。回调全部执行完之后,会回收CallbackRecord,具体实现在recycleCallbackLocked(callbacks)中。

    另外对于CALLBACK_COMMIT类型有一大段注释,大意是如果当前帧的渲染时间超过两帧的时间间隔(2*16.6ms),则将时间回移到上一个VSYNC信号时间。举个例子,下图表示一个时间轴,其中16、32、48、64等表示一帧的时间间隔,也是VSYNC信号的时间间隔。

    ———16————32————48—52———64————80————>

    假如某帧应该在16时处理回调,但由于渲染超时,一直延迟到52才响应。则时间间隔为36(52-16),超过了两帧的标准时间,则将此帧的时间修正为32。至于原因,据说是为了解决ValueAnimator的问题,有待深究。

  9. 前面提到Choreographer提供了一个FrameCallback接口,来看一下它是如何提交和处理的:

    首先提交一个FrameCallback:

    public void postFrameCallback(FrameCallback callback) {
       postFrameCallbackDelayed(callback, 0);
    }

    继续调用postFrameCallbackDelayed

    public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
       if (callback == null) {
           throw new IllegalArgumentException("callback must not be null");
       }
    
       postCallbackDelayedInternal(CALLBACK_ANIMATION,
               callback, FRAME_CALLBACK_TOKEN, delayMillis);
    }

    注意FrameCallback使用了回调类型为CALLBACK_ANIMATION,并使用一个特殊token(FRAME_CALLBACK_TOKEN)来标识是FrameCallback。postCallbackDelayedInternal在前文普通的Callback时已经分析过了,接下来的流程就是相同的了,只是在最终处理时区分一下。

  10. 上面从源码角度分析了一个Callback(普通Callback和FrameCallback)从提交到处理的过程,可能还是有点混乱,下面是一个流程图,可以清晰地了解其处理流程:

    这里写图片描述

    对应Choreographer还对外提供了两个移除Callback的方法:

    public void removeCallbacks(int callbackType, Runnable action, Object token)
    public void removeFrameCallback(FrameCallback callback)

Choreographer跟View绘制的关联

  1. 在ViewRootImpl的构造函数中会实例化一个Choreographer对象:

    mChoreographer = Choreographer.getInstance();

    它是运行在主线程中的。

  2. scheduleTraversals()方法中Choreographer发送一个CALLBACK_TRAVERSAL类型的Callback:

    void scheduleTraversals() {
       if (!mTraversalScheduled) {
           mTraversalScheduled = true;
           mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
           mChoreographer.postCallback(
                   Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
           if (!mUnbufferedInputDispatch) {
               scheduleConsumeBatchedInput();
           }
           notifyRendererOfFramePending();
       }
    }
  3. 再来看mTraversalRunnable的实现

    final class TraversalRunnable implements Runnable {
       @Override
       public void run() {
           doTraversal();
       }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
  4. 在doTraversal()中会调用performTraversals(),在这个函数中会执行View以及子View的测量、布局、绘制流程。

  5. 因此Choreographer可以用来监控View绘制的性能。

应用场景

Choreographer的原理了解了,来说说其用途,最常见的是使用它来监控App性能、卡顿和帧率。

还记得Choreographer提供的FrameCallback接口吗?可以自定义一个类FPSMonitor继承自FrameCallback,用于监控丢帧情况。

public class FPSMonitor implements Choreographer.FrameCallback {
    @Override
    public void doFrame(long frameTimeNanos) {
        // do monitor
    }
}

然后利用Choreographer的postFrameCallback(FrameCallback callback)方法将其post出去,这样在下一帧渲染时就会回调我们自定义的FrameCallback,在doFrame就可以实现一些检测逻辑。

Github上有一个利用Choreographer原理实现的开源库TinyDancer,感兴趣的可以去学习一下。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值