第1章 Choreographer 帧率监控原理
1.1 Choreographer 概述
Choreographer 是 Android 系统在应用层负责协调 UI 绘制节奏的核心调度器。它接收底层硬件发出的 VSYNC 信号,在每一帧开始时刻按优先级依次触发输入处理、动画更新和视图遍历绘制三个回调,从而保证整个渲染管线以固定节奏(通常 60 FPS 或 120 FPS)运行。如果任何一个回调阶段超时,就会产生“掉帧”(Jank),直接影响用户滑动、动画的流畅度。
1.2 VSYNC 信号机制
VSYNC(Vertical Synchronization)是显示硬件在每次刷新完成后产生的同步脉冲信号。Android 将其作为渲染节奏的基准,分为三个阶段传递:
| VSYNC 类型 | 来源 | 作用 |
|---|---|---|
| 硬件 VSYNC | 显示控制器(Display Controller) | 固定频率中断(60 Hz / 120 Hz),SurfaceFlinger 据此合成图层 |
| 软件 VSYNC | SurfaceFlinger | 将硬件信号重采样后分发给应用进程,作为调度基准 |
| 虚拟 VSYNC | Choreographer 内部 | 应用层回调触发的时间基准,由 Choreographer 计算产生 |
当应用进程收到虚拟 VSYNC 后,Choreographer 立即开始执行帧回调队列,确保 UI 更新与屏幕刷新周期对齐。
1.3 Choreographer 回调机制
Choreographer 内部维护了三种回调队列,按优先级从高到低依次执行:
| 回调类型 | 优先级 | 触发时机 | 主要任务 |
|---|---|---|---|
CALLBACK_INPUT | 最高 | VSYNC 到来后立即触发 | 处理触摸、按键、轨迹球等输入事件 |
CALLBACK_ANIMATION | 中等 | 输入处理完成后 | 更新属性动画、补间动画、Choreographer 驱动的动画值 |
CALLBACK_TRAVERSAL | 最低 | 动画更新完成后 | 执行 View 树的 measure / layout / draw 流程 |
三种回调在同一个 VSYNC 周期内串行执行,任何回调超时都会压缩后续回调的执行时间,最终导致帧无法在 16.67 ms 内完成。
1.4 帧绘制流程
从 VSYNC 信号到达应用进程到最终帧渲染完成,经过以下关键步骤:
- VSYNC 信号:SurfaceFlinger 通过
EventThread将虚拟 VSYNC 发送给应用进程。 - Choreographer 调度:收到信号后依次出队三种回调。
- 输入处理:分发当前帧待处理的输入事件。
- 动画更新:推进所有活跃动画的当前值。
- 遍历绘制:执行 ViewRootImpl 的
performTraversals(),完成 Measure、Layout、Draw。 - 渲染输出:DisplayList 提交给 RenderThread,GPU 渲染完成后送显。
如果步骤 2–5 的总耗时超过 VSYNC 周期,则无法赶上当前帧的送显截止时间,造成掉帧。
1.5 掉帧定义与监控基础
掉帧的本质是 Choreographer 回调链的总执行时间超过了一个 VSYNC 周期。在 60 Hz 屏幕上一个周期为 16.67 ms,在 120 Hz 屏幕上则为 8.33 ms。通过注册 Choreographer.FrameCallback 可以精确测量每一帧的到达间隔,从而判断是否掉帧:
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
private long mLastFrameTimeNanos = 0;
@Override
public void doFrame(long frameTimeNanos) {
if (mLastFrameTimeNanos != 0) {
long intervalNanos = frameTimeNanos - mLastFrameTimeNanos;
double intervalMs = intervalNanos / 1_000_000.0;
if (intervalMs > 16.67) {
long dropped = Math.round((intervalMs - 16.67) / 16.67);
Log.w("FrameMonitor", "掉帧 " + dropped + " 帧, 实际间隔 " + String.format("%.2f", intervalMs) + " ms");
}
}
mLastFrameTimeNanos = frameTimeNanos;
// 继续监听下一帧
Choreographer.getInstance().postFrameCallback(this);
}
});
除了代码埋点,也可以通过 adb 命令快速获取应用帧率数据:
# 查看指定包名的最近帧率统计(需 Android 7.0+)
adb shell dumpsys gfxinfo <package_name>
# 持续输出帧率日志(Android 12+)
adb shell dumpsys gfxinfo <package_name> framestats
监控实践建议:在开发阶段使用
Choreographer.FrameCallback 精确测量每一帧间隔;在线上环境通过 FrameMetricsAggregator(Android 7.0+)或 PerformanceMonitor 库收集掉帧分布,避免在 Release 包中频繁回调产生额外性能开销。
1.6 关键 API 与命令速查
| API / 命令 | 用途 | 最低版本 |
|---|---|---|
Choreographer.postFrameCallback() | 注册帧回调,获取 VSYNC 时间戳 | API 16+ |
Choreographer.FrameCallback.doFrame() | 回调方法,参数为帧时间(纳秒) | API 16+ |
adb shell dumpsys gfxinfo | 输出帧率、丢帧数、绘制时间 | API 11+ |
FrameMetricsAggregator | 聚合帧各阶段耗时(输入、动画、遍历、GPU 等) | API 24+ |
常见误区:不要将
Choreographer 的回调频率与屏幕刷新率直接等同。当应用处于 idle 状态时,Choreographer 不会每帧都触发回调;只有存在待处理的输入、动画或绘制请求时才会被 VSYNC 唤醒。因此,长时间没有回调不代表没有掉帧,需要结合 dumpsys gfxinfo 的累积统计综合判断。