第1章 Choreographer工作原理:帧回调机制、VSYNC信号、回调队列
1.1 Choreographer概述
Choreographer(编舞者)是 Android 框架层中负责协调 UI 帧渲染的核心调度器,位于 android.view 包内。它监听垂直同步信号(VSYNC),在每一帧到达时按优先级依次唤醒回调队列中的任务,驱动 View 的测量、布局、绘制以及动画更新。监控 UI 帧率的核心入口 FrameCallback 正是注册在 Choreographer 上的回调接口。
Choreographer.getInstance() 获取单例,向其投递帧回调即可精确感知每一帧的起始时间点。
1.2 VSYNC信号机制
VSYNC(Vertical Synchronization)是屏幕刷新周期的硬件同步信号,用于避免画面撕裂(Tearing)。Android 中 VSYNC 来源分为两类:
| 类型 | 来源 | 触发方式 | 特点 |
|---|---|---|---|
| 硬件 VSYNC | 显示控制器(Display Controller) | 硬件中断 | 精确稳定,与屏幕刷新率严格对齐 |
| 软件 VSYNC(SwVSYNC) | SurfaceFlinger 合成线程 | 模拟/派生信号 | 虚拟化同步,用于非实时场景或模拟器 |
SurfaceFlinger 收到硬件 VSYNC 后,通过 EventThread 分发到各应用进程。Choreographer 内部通过 FrameDisplayEventReceiver(继承自 DisplayEventReceiver)接收 VSYNC 事件,驱动帧回调队列的执行。
1.3 回调队列(CallbackQueue)
Choreographer 内部维护了四个优先级递减的回调队列,用于在每一帧内按顺序处理不同类型的任务。队列定义在 Choreographer.CallbackQueue 内部类中,类型常量如下:
| 回调类型常量 | 值 | 触发阶段 | 典型用途 |
|---|---|---|---|
CALLBACK_INPUT | 0 | 帧起始 | 输入事件(触摸、按键)的分发 |
CALLBACK_ANIMATION | 1 | 输入事件后 | 动画帧更新(ValueAnimator、属性动画) |
CALLBACK_TRAVERSAL | 2 | 动画更新后 | View 的 measure / layout / draw |
CALLBACK_COMMIT | 3 | 遍历完成后 | 渲染命令提交(硬件加速相关) |
四个队列按 值从小到大 依次执行,同队列内的回调按投递顺序 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 渲染的完整链路:
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”警告。