第1章 Choreographer工作原理:帧回调机制、VSYNC信号、回调队列

1.1 Choreographer概述

Choreographer(编舞者)是 Android 框架层中负责协调 UI 帧渲染的核心调度器,位于 android.view 包内。它监听垂直同步信号(VSYNC),在每一帧到达时按优先级依次唤醒回调队列中的任务,驱动 View 的测量、布局、绘制以及动画更新。监控 UI 帧率的核心入口 FrameCallback 正是注册在 Choreographer 上的回调接口。

关键定位:Choreographer 是 Android 渲染引擎的“心跳”,应用层通过 Choreographer.getInstance() 获取单例,向其投递帧回调即可精确感知每一帧的起始时间点。

1.2 VSYNC信号机制

VSYNC(Vertical Synchronization)是屏幕刷新周期的硬件同步信号,用于避免画面撕裂(Tearing)。Android 中 VSYNC 来源分为两类:

类型来源触发方式特点
硬件 VSYNC显示控制器(Display Controller)硬件中断精确稳定,与屏幕刷新率严格对齐
软件 VSYNC(SwVSYNC)SurfaceFlinger 合成线程模拟/派生信号虚拟化同步,用于非实时场景或模拟器

SurfaceFlinger 收到硬件 VSYNC 后,通过 EventThread 分发到各应用进程。Choreographer 内部通过 FrameDisplayEventReceiver(继承自 DisplayEventReceiver)接收 VSYNC 事件,驱动帧回调队列的执行。

注意:若设备开启“开发者选项 → 严格模式(Strict Mode)”或“Profile GPU Rendering”,Choreographer 的调度行为不变,但会额外记录帧时间戳用于调试,不影响 VSYNC 信号源。

1.3 回调队列(CallbackQueue)

Choreographer 内部维护了四个优先级递减的回调队列,用于在每一帧内按顺序处理不同类型的任务。队列定义在 Choreographer.CallbackQueue 内部类中,类型常量如下:

回调类型常量值触发阶段典型用途
CALLBACK_INPUT0帧起始输入事件(触摸、按键)的分发
CALLBACK_ANIMATION1输入事件后动画帧更新(ValueAnimator、属性动画)
CALLBACK_TRAVERSAL2动画更新后View 的 measure / layout / draw
CALLBACK_COMMIT3遍历完成后渲染命令提交(硬件加速相关)

四个队列按 值从小到大 依次执行,同队列内的回调按投递顺序 FIFO 处理。任一队列执行超时都会导致该帧掉帧(Jank)。

1.4 FrameCallback 帧回调

Choreographer.FrameCallback 是应用层监控帧率的标准接口,定义如下:

public interface FrameCallback {
    // frameTimeNanos:当前帧的 VSYNC 时间戳(单位:纳秒,CLOCK_MONOTONIC)
    void doFrame(long frameTimeNanos);
}

通过 postFrameCallback(FrameCallback) 注册回调后,Choreographer 在下一帧的 CALLBACK_ANIMATION 阶段之后、CALLBACK_TRAVERSAL 之前调用 doFrame。连续监控需要在 doFrame 末尾重新注册自身:

public class FrameMonitor implements Choreographer.FrameCallback {
    private long lastFrameTimeNanos = 0;

    public void start() {
        Choreographer.getInstance().postFrameCallback(this);
    }

    @Override
    public void doFrame(long frameTimeNanos) {
        if (lastFrameTimeNanos != 0) {
            long frameIntervalNanos = frameTimeNanos - lastFrameTimeNanos;
            float fps = 1_000_000_000f / frameIntervalNanos;
            android.util.Log.d("FrameMonitor", "当前帧率: " + String.format("%.1f", fps) + " fps");
        }
        lastFrameTimeNanos = frameTimeNanos;
        // 注册下一帧回调
        Choreographer.getInstance().postFrameCallback(this);
    }
}
帧率计算要点:frameTimeNanos 是 CLOCK_MONOTONIC 时间,差值为相邻两帧的 VSYNC 间隔。60Hz 屏幕理想间隔约为 16.67ms(1,000,000,000ns / 60)。连续两次回调的时间差大于 16.67ms × 1.5(约 25ms)通常视为一次掉帧。

1.5 完整工作流程

下图串联 VSYNC 信号、Choreographer 调度、回调队列执行、帧回调触发及最终 UI 渲染的完整链路:

Choreographer 工作原理流程 VSYNC 信号源 硬件中断 / SwVSYNC Choreographer 调度器 FrameDisplayEventReceiver CallbackQueue(按优先级执行) INPUT(0) ANIMATION(1) TRAVERSAL(2) COMMIT(3) FrameCallback.doFrame() 帧率监控入口 UI 渲染管线 Measure / Layout / Draw 屏幕刷新(Display) 💡 核心要点 • VSYNC 触发 Choreographer • 回调队列按优先级执行 • doFrame() 为监控入口 • 超时时长直接导致掉帧 ⏱ 帧间隔参考 60Hz → 16.67ms 90Hz → 11.11ms 120Hz → 8.33ms

1.6 实践要点与注意事项

  • 单例获取:必须通过 Choreographer.getInstance() 获取主线程实例,不要在子线程直接调用;若需子线程监控,使用 Choreographer.forDisplay(displayId) 或 HandlerThread 配套方案。
  • 回调自注册:连续监控时务必在 doFrame 末尾重新 postFrameCallback(this),否则只回调一次。漏注册会导致监控中断。
  • 时间戳单位:frameTimeNanos 单位是纳秒,差值计算时注意溢出(建议使用 long 防溢出后再转 float)。
  • 1.5 倍阈值:业界常用“实际间隔 > 理想间隔 × 1.5”判断掉帧,60Hz 下阈值约 25ms,此值可在 FrameMetrics 中验证。
  • 不要阻塞 doFrame:doFrame 运行在主线程,任何耗时操作(IO、锁等待、复杂计算)都会延迟后续回调队列,引发连锁掉帧。
常见错误:在 doFrame 内直接执行 Thread.sleep() 或大对象分配,导致该帧执行时间超过 VSYNC 周期,下一帧回调被迫跳过 — 表现为帧率骤降且日志中出现“Skipped X frames”警告。