【Android】从Choreographer到UI渲染(二)

发布于:2025-05-19 ⋅ 阅读:(19) ⋅ 点赞:(0)

【Android】从Choreographer到UI渲染(二)

Google 在 2012 年推出的 Project Butter(黄油计划)是 Android 系统发展史上的重要里程碑,旨在解决长期存在的 UI 卡顿、响应延迟等问题,提升用户体验。

在 Android 4.1 之前,系统缺乏统一的帧同步机制,屏幕刷新、应用渲染与用户输入之间常因时序错乱导致画面撕裂(Screen Tearing)或帧丢失(Jank)。

例如,双缓冲机制下,若 GPU 渲染超时,CPU 无法及时处理下一帧,导致后续帧被迫跳过多个刷新周期,用户会明显感知卡顿;触摸事件的处理也因未与屏幕刷新同步,出现“不跟手”的延迟。这些问题严重影响了用户体验,尤其在动画和滚动场景中尤为突出。

为此,Google 通过 VSync 垂直同步三重缓冲(Triple Buffering)Choreographer 调度框架 三管齐下重构显示系统:VSync 信号将 CPU/GPU 的渲染周期与屏幕刷新严格对齐,确保每帧的准备工作在 16ms(60Hz 屏幕)内启动;三重缓冲通过增加临时缓冲区缓解 GPU 超时导致的连续丢帧问题,牺牲少量内存换取流畅性;而 Choreographer 作为“舞蹈编导”,统一调度输入、动画、绘制等任务,在 VSync 信号到达时批量执行,避免任务碎片化。

垂直同步和三缓冲已经在之前的篇章中提到过,所以今天我们详细分析一下Choregrapher。

本文参考:

Android 之 Choreographer 详细分析 - 简书

何时调用Choreographer

在 Android 的 UI 渲染机制中,无论是 Activity 启动后的首次布局,还是后续通过动画或手动触发的界面更新,最终都会汇聚到 ViewRootImplscheduleTraversals() 方法。

我们首先来回忆一下Activity的启动与Window的添加过程。

当 Activity 完成 onResume() 后,系统会将其视图层级(DecorView)添加到 Window 中。具体步骤如下:

  1. Window 创建:Activity 的 Window(通常是 PhoneWindow)在 onCreate() 阶段初始化,并在 onResume() 后通过 WindowManager 添加到系统。
  2. ViewRootImpl 关联WindowManagerGlobal.addView() 方法会创建 ViewRootImpl 实例,并调用其 setView() 方法,将 DecorView 与 ViewRootImpl 绑定。
  3. 触发首次布局:在 ViewRootImpl.setView() 中,调用 requestLayout() 发起首次测量、布局、绘制请求。
// ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    // 关联 DecorView
    mView = view;
    // 请求布局
    requestLayout();
    // ... 其他初始化逻辑
}

requestLayout() 是 UI 更新的起点,其核心逻辑如下:

  1. 标记布局请求:通过 mLayoutRequested 标志位,确保同一帧内多次调用只触发一次布局。
  2. 调度遍历任务:调用 scheduleTraversals(),安排一次 performTraversals() 的执行。
// ViewRootImpl.java
@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

scheduleTraversals() 负责协调 UI 更新与 VSync 信号的同步:

  1. 同步屏障(Sync Barrier):通过 postSyncBarrier() 向主线程的 MessageQueue 插入一个同步屏障,屏蔽普通同步消息,确保 UI 更新任务优先执行。
  2. 提交回调到 Choreographer:将 mTraversalRunnable(最终触发 doTraversal())提交给 Choreographer,在下一个 VSync 信号到达时执行。
    //ViewRootImpl.java
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            //此字段保证同时间多次更改只会刷新一次,例如TextView连续两次setText(),也只会走一次绘制流程
            mTraversalScheduled = true;
            //添加同步屏障,屏蔽同步消息,保证VSync到来立即执行绘制
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //mTraversalRunnable是TraversalRunnable实例,最终走到run(),也即doTraversal();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

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

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            //移除同步屏障
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            ...
            //开始三大绘制流程
            performTraversals();
            ...
        }
    }

Choreographer的创建

它的实例mChoreographer,是在ViewRootImpl的构造方法内使用Choreographer.getInstance()创建:

Choreographer mChoreographer;

//ViewRootImpl实例是在添加window时创建
public ViewRootImpl(Context context, Display display) {
    ...
    mChoreographer = Choreographer.getInstance();
    ...
}

Choreographer 通过 ThreadLocal 实现线程单例,确保每个线程(尤其是主线程)拥有独立的实例。这种设计类似于 Looper 的线程绑定机制,原因如下:

  • 线程隔离性:UI 操作必须在主线程执行,Choreographer 需要与主线程的 Looper 绑定,以确保任务调度与消息循环同步。
  • 避免竞争:多线程环境下,独立实例可防止任务队列的并发冲突。
// 通过 ThreadLocal 存储每个线程的 Choreographer 实例
private static final ThreadLocal<Choreographer> sThreadInstance = 
    new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            // 必须绑定到带有 Looper 的线程
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("当前线程必须拥有 Looper!");
            }
            // 创建实例并与 Looper 关联
            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
            // 如果是主线程,记录为主实例
            if (looper == Looper.getMainLooper()) {
                mMainInstance = choreographer;
            }
            return choreographer;
        }
    };

public static Choreographer getInstance() {
    return sThreadInstance.get(); // 返回当前线程的实例
}

关键点

  • 强制 Looper 存在:若线程没有 Looper(如未调用 Looper.prepare()),直接抛出异常。这解释了为什么 UI 操作必须在主线程(主线程默认初始化了 Looper)。
  • 主线程标识:通过 Looper.getMainLooper() 判断当前线程是否为主线程,并记录主实例供全局访问。

紧接着是Chroreographer的构造方法:

    private Choreographer(Looper looper, int vsyncSource) {
        mLooper = looper;
        //使用当前线程looper创建 mHandler
        mHandler = new FrameHandler(looper);
        //USE_VSYNC 4.1以上默认是true,表示 具备接受VSync的能力,这个接受能力就是FrameDisplayEventReceiver
        mDisplayEventReceiver = USE_VSYNC
                ? new FrameDisplayEventReceiver(looper, vsyncSource)
                : null;
        mLastFrameTimeNanos = Long.MIN_VALUE;

        // 计算一帧的时间,Android手机屏幕是60Hz的刷新频率,就是16ms
        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());

        // 创建一个链表类型CallbackQueue的数组,大小为5,
        //也就是数组中有五个链表,每个链表存相同类型的任务:输入、动画、遍历绘制等任务(CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL)
        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));
    }

Choreographer 的构造函数初始化了多个关键组件,这些组件共同协作完成帧调度:

FrameHandler:异步消息处理器
private final class FrameHandler extends Handler {
    public FrameHandler(Looper looper) {
        super(looper); // 绑定当前线程的 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;
        }
    }
}
  • 作用:处理与帧调度相关的异步消息(如 MSG_DO_FRAME),确保任务在主线程执行。
  • 异步消息:通过 msg.setAsynchronous(true) 标记消息为异步,绕过同步屏障(后文详述)。
FrameDisplayEventReceiver:VSync 信号接收器
// USE_VSYNC 默认为 true(Android 4.1+ 启用)
mDisplayEventReceiver = USE_VSYNC 
    ? new FrameDisplayEventReceiver(looper, vsyncSource) 
    : null;
  • 功能:通过 JNI 层注册到显示系统,监听硬件 VSync 信号。
  • 信号回调:当 VSync 信号到达时,触发 onVsync() 方法,进而通过 Choreographer 调度帧任务。
mFrameIntervalNanos:帧间隔时间
// 60Hz 屏幕:1秒 / 60帧 ≈ 16.666ms
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
  • 计算依据:基于屏幕刷新率(如 60Hz)计算每帧的理想时间(16.6ms)。
  • 用途:在 doFrame() 中检测帧超时(如判断是否跳帧)。
mCallbackQueues:任务队列数组
// 五种任务类型:输入、动画、插入动画、遍历绘制、提交
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
    mCallbackQueues[i] = new CallbackQueue();
}
  • 任务分类:将回调任务按类型存入不同队列,确保执行顺序:

    CALLBACK_INPUT:处理触摸/输入事件(优先级最高)。

    CALLBACK_ANIMATION:执行属性动画、过渡动画。

    CALLBACK_INSETS_ANIMATION:窗口插入动画(如状态栏/导航栏变化)。

    CALLBACK_TRAVERSAL:执行 View 的测量、布局、绘制。

    CALLBACK_COMMIT:提交帧数据到渲染线程(最后执行)。

Choreographer调用流程

根据不同的任务类型分派任务

当调用 mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, ...) 提交任务时,系统根据任务类型(如 CALLBACK_TRAVERSAL 表示绘制任务)将任务存入对应的 CallbackQueue 队列。

postCallback()内部调用postCallbackDelayed(),接着又调用postCallbackDelayedInternal()

private void postCallbackDelayedInternal(int callbackType, Object action, long delayMillis) {
    synchronized (mLock) {
        // 计算任务的到期时间
        final long dueTime = SystemClock.uptimeMillis() + delayMillis;
        // 将任务添加到对应类型的队列
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, null);

        if (dueTime <= now) {
            scheduleFrameLocked(now); // 立即调度
        } else {
            // 发送延迟消息,最终仍触发 scheduleFrameLocked()
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            msg.setAsynchronous(true); // 关键:异步消息
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }
}

postCallbackDelayedInternal() 方法会根据延迟时间决定立即调度或延迟处理:若任务需要立即执行,直接调用 scheduleFrameLocked() 申请 VSync 信号;若存在延迟,则通过 FrameHandler 发送异步消息 MSG_DO_SCHEDULE_CALLBACK,最终仍会触发 scheduleFrameLocked()。异步消息的标记(msg.setAsynchronous(true))是关键设计,它允许这些消息绕过同步屏障——在 ViewRootImpl.scheduleTraversals() 中插入的同步屏障会阻塞普通同步消息,确保 UI 渲染任务优先执行。

    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,即绘制过程
                    doFrame(System.nanoTime(), 0);
                    break;
                case MSG_DO_SCHEDULE_VSYNC:
                    //申请VSYNC信号,例如当前需要绘制任务时
                    doScheduleVsync();
                    break;
                case MSG_DO_SCHEDULE_CALLBACK:
                    //需要延迟的任务,最终还是执行上述两个事件
                    doScheduleCallback(msg.arg1);
                    break;
            }
        }
    }

进第三个case试试。

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

发现也是走到这里,即延迟运行最终也会走到scheduleFrameLocked()

    private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            //开启了VSYNC
            if (USE_VSYNC) {
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame on vsync.");
                }

                //当前执行的线程,是否是mLooper所在线程
                if (isRunningOnLooperThreadLocked()) {
                    //申请 VSYNC 信号
                    scheduleVsyncLocked();
                } else {
                    // 若不在,就用mHandler发送消息到原线程,最后还是调用scheduleVsyncLocked方法
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);//异步
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
                // 如果未开启VSYNC则直接doFrame方法(4.1后默认开启)
                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);
            }
        }
    }

FrameHandler的作用很明显里了:发送异步消息(因为前面设置了同步屏障)。有延迟的任务发延迟消息、不在原线程的发到原线程、没开启VSYNC的直接走 doFrame 方法取执行绘制。

申请与接受VSync

Choreographer 会通过 scheduleVsyncLocked() 方法向系统申请 VSync 信号。

    private void scheduleVsyncLocked() {
        mDisplayEventReceiver.scheduleVsync();
    }

这一过程的核心在于 FrameDisplayEventReceiver,也就是此处的mDisplayEventReceiver,它是 DisplayEventReceiver 的子类,在构造时通过 JNI 层与底层显示服务建立连接(nativeInit),注册为 VSync 信号的监听者。当调用 scheduleVsync() 时,实际通过 nativeScheduleVsync 这一本地方法向系统请求下一次 VSync 中断信号。一旦硬件产生 VSync 脉冲(例如屏幕准备刷新下一帧时),系统会回调 FrameDisplayEventReceiveronVsync 方法,此时该方法将当前 VSync 的时间戳、显示设备 ID 和帧序号记录下来,并将自身封装为一个异步消息(msg.setAsynchronous(true))发送到主线程的 MessageQueue

接下来看看代码怎么说:

    public DisplayEventReceiver(Looper looper, int vsyncSource) {
        if (looper == null) {
            throw new IllegalArgumentException("looper must not be null");
        }

        mMessageQueue = looper.getQueue();
        // 注册VSYNC信号监听者
        mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,vsyncSource);

        mCloseGuard.open("dispose");
    }

在 DisplayEventReceiver 的构造方法会通过 JNI 创建一个 IDisplayEventConnection 的 VSYNC 的监听者。

FrameDisplayEventReceiver的scheduleVsync()就是在 DisplayEventReceiver中:

    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 {
            // 申请VSYNC中断信号,会回调onVsync方法
            nativeScheduleVsync(mReceiverPtr);
        }
    }

当调用 scheduleVsync() 时,实际通过 nativeScheduleVsync 这一本地方法向系统请求下一次 VSync 中断信号。

一旦硬件产生 VSync 脉冲(例如屏幕准备刷新下一帧时),系统会回调 FrameDisplayEventReceiveronVsync 方法,此时该方法将当前 VSync 的时间戳、显示设备 ID 和帧序号记录下来,并将自身封装为一个异步消息(msg.setAsynchronous(true))发送到主线程的 MessageQueue

    /**
     * 接收到VSync脉冲时 回调
     * @param timestampNanos VSync脉冲的时间戳
     * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
     * @param frame 帧号码,自增
     */
    @UnsupportedAppUsage
    public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
    }

具体实现是在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);
        }

        @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;
            //将本身作为runnable传入msg, 发消息后 会走run(),即doFrame(),也是异步消息
            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);
        }
    }

当主线程处理到这条异步消息时,会执行 FrameDisplayEventReceiverrun() 方法,进而调用 Choreographer.doFrame(),这是整个渲染流程的起点。

doFrame执行过程

    void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            if (!mFrameScheduled) {
                return; // no work to do
            }

            ...
            // 预期执行时间
            long intendedFrameTimeNanos = frameTimeNanos;
            startNanos = System.nanoTime();
            // 超时时间是否超过一帧的时间(这是因为MessageQueue虽然添加了同步屏障,但是还是有正在执行的同步任务,导致doFrame延迟执行了)
            final long jitterNanos = startNanos - frameTimeNanos;
            if (jitterNanos >= mFrameIntervalNanos) {
                // 计算掉帧数
                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                    // 掉帧超过30帧打印Log提示
                    Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                            + "The application may be doing too much work on its main thread.");
                }
                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
                ...
                frameTimeNanos = startNanos - lastFrameOffset;
            }

            ...

            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
            // Frame标志位恢复
            mFrameScheduled = false;
            // 记录最后一帧时间
            mLastFrameTimeNanos = frameTimeNanos;
        }

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

doFrame() 中,系统首先通过计算当前时间与 VSync 信号时间戳的差值(jitterNanos = startNanos - frameTimeNanos)判断是否发生跳帧:若差值超过一帧的理论时长(如 16.6ms 对应 60Hz 屏幕),则按超出时长除以单帧时间计算跳帧数,若超过 30 帧(约 500ms)则打印警告日志,提示主线程可能存在耗时操作。

随后,系统按固定顺序处理任务队列——依次执行输入事件(CALLBACK_INPUT)、动画更新(CALLBACK_ANIMATION)、窗口插入动画(CALLBACK_INSETS_ANIMATION)、视图树遍历(CALLBACK_TRAVERSAL)和帧提交(CALLBACK_COMMIT)。

每个队列的任务通过 doCallbacks() 方法提取并执行:例如 CALLBACK_TRAVERSAL 队列中的任务通常是 ViewRootImpl 提交的 mTraversalRunnable,其 run() 方法最终触发 performTraversals(),执行测量、布局、绘制三大流程。

    void doCallbacks(int callbackType, long frameTimeNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {

            final long now = System.nanoTime();
            // 根据指定的类型CallbackkQueue中查找到达执行时间的CallbackRecord
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now / TimeUtils.NANOS_PER_MS);
            if (callbacks == null) {
                return;
            }
            mCallbacksRunning = true;

            //提交任务类型
            if (callbackType == Choreographer.CALLBACK_COMMIT) {
                final long jitterNanos = now - frameTimeNanos;
                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 {
            // 迭代执行队列所有任务
            for (CallbackRecord c = callbacks; c != null; c = c.next) {
                // 回调CallbackRecord的run,其内部回调Callback的run
                c.run(frameTimeNanos);
            }
        } finally {
            synchronized (mLock) {
                mCallbacksRunning = false;
                do {
                    final CallbackRecord next = callbacks.next;
                    //回收CallbackRecord
                    recycleCallbackLocked(callbacks);
                    callbacks = next;
                } while (callbacks != null);
            }
        }
    }

任务的具体执行由 CallbackRecord.run() 完成,该函数根据 token 判断任务类型——若 tokenFRAME_CALLBACK_TOKEN(通过 postFrameCallback() 提交),则调用 FrameCallback.doFrame() 接口;否则直接执行 Runnable.run()

    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) {
                // 通过postFrameCallback 或 postFrameCallbackDelayed,会执行这里
                ((FrameCallback)action).doFrame(frameTimeNanos);
            } else {
                //取出Runnable执行run()
                ((Runnable)action).run();
            }
        }
    }

前面看到mChoreographer.postCallback传的token是null,所以取出action,就是Runnable,执行run(),这里的action就是 ViewRootImpl 发起的绘制任务mTraversalRunnable了,那么这样整个逻辑就闭环了

那么 啥时候 token == FRAME_CALLBACK_TOKEN 呢?答案是Choreographer的postFrameCallback()方法:

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

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

        //也是走到是postCallbackDelayedInternal,并且注意是CALLBACK_ANIMATION类型,
        //token是FRAME_CALLBACK_TOKEN,action就是FrameCallback
        postCallbackDelayedInternal(CALLBACK_ANIMATION,
                callback, FRAME_CALLBACK_TOKEN, delayMillis);
    }

    public interface FrameCallback {
        public void doFrame(long frameTimeNanos);
    }

计算丢帧

举个栗子:

        //Application.java
         public void onCreate() {
             super.onCreate();
             //在Application中使用postFrameCallback
             Choreographer.getInstance().postFrameCallback(new FPSFrameCallback(System.nanoTime()));
         }


    public class FPSFrameCallback implements Choreographer.FrameCallback {

      private static final String TAG = "FPS_TEST";
      private long mLastFrameTimeNanos = 0;
      private long mFrameIntervalNanos;

      public FPSFrameCallback(long lastFrameTimeNanos) {
          mLastFrameTimeNanos = lastFrameTimeNanos;
          mFrameIntervalNanos = (long)(1000000000 / 60.0);
      }

      @Override
      public void doFrame(long frameTimeNanos) {

          //初始化时间
          if (mLastFrameTimeNanos == 0) {
              mLastFrameTimeNanos = frameTimeNanos;
          }
          final long jitterNanos = frameTimeNanos - mLastFrameTimeNanos;
          if (jitterNanos >= mFrameIntervalNanos) {
              final long skippedFrames = jitterNanos / mFrameIntervalNanos;
              if(skippedFrames>30){
                  //丢帧30以上打印日志
                  Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                          + "The application may be doing too much work on its main thread.");
              }
          }
          mLastFrameTimeNanos=frameTimeNanos;
          //注册下一帧回调
          Choreographer.getInstance().postFrameCallback(this);
      }
  }

通过 postFrameCallback() 注册一个 FrameCallback,在每次 doFrame() 时计算相邻两次 VSync 信号的时间差,若超过阈值则判定为跳帧。例如,示例中的 FPSFrameCallback 在每次回调时比较当前帧与上一帧的时间差,若超过 16.6ms 的倍数,则累加跳帧数并打印警告。

这种设计使得所有 UI 操作(无论是触摸、动画还是绘制)都被严格对齐到 VSync 信号,既避免了画面撕裂,又为每一帧提供了完整的处理窗口,而开发者可通过监听回调精准定位主线程卡顿的根源,例如在 doFrame 中插入性能埋点或结合 Systrace 工具分析耗时任务。

放一个流程图~

Android 之 Choreographer 详细分析.png