VSync、Choreographer 与缓冲:帧如何被调度
2026/8/9大约 3 分钟渲染管线Android 14VSyncChoreographer缓冲
VSync、Choreographer 与缓冲:帧如何被调度
上一章看了 GPU 出图。这一章回答:一帧什么时候开始,谁触发绘制,缓冲怎么流转, 双缓冲与三缓冲差在哪?
VSync 是节拍,不是完成保证
| 常见误解 | 正确理解 |
|---|---|
| 绘制是应用想画就画 | 绘制由 VSync 驱动,错过就等下一帧 |
| 缓冲越多越好 | 缓冲多延迟高,三缓冲是延迟与流畅的折中 |
| Choreographer 只是计时器 | 它是帧回调的调度中心,动画、输入、绘制都挂在它上面 |
核心结论
- VSync 由显示系统产生,应用按它开始帧工作。
- Choreographer 统一调度输入、动画、measure/layout/draw。
- BufferQueue 在应用与合成之间流转缓冲,避免读写冲突(撕裂)。
- 双缓冲 + VSync 是基础,三缓冲降低错过帧的影响。
- 错过 VSync 的帧会顺延,表现为掉帧或延迟。
帧调度主链
逐步解释:
- VSync:显示系统发出帧节奏信号。
- 调度:Choreographer 触发各帧回调。
- 工作:动画、输入、绘制按序执行。
- 提交:完成帧写入 BufferQueue。
- 合成:SurfaceFlinger 取缓冲上屏。
双缓冲与三缓冲
| 方案 | 缓冲数 | 特点 |
|---|---|---|
| 双缓冲 | 2 | 简单,错过 VSync 就等一轮 |
| 三缓冲 | 3 | 减少错过影响,增加一点延迟 |
应用侧可通过缓冲队列设置影响策略;具体缓冲数由系统按场景动态调整。
源码证据
阅读目标:确认 VSync 回调与帧调度入口。
正文指针:Choreographer.java doFrame();DisplayEventReceiver.java 接收 VSync。
// Choreographer.java(伪代码,行号以 r75 为准)
void doFrame(long frameTimeNanos, int frame) {
// 触发输入、动画、测量布局绘制
}这段代码证明: 所有帧工作从 doFrame 开始;VSync 通过 DisplayEventReceiver 进入 Choreographer。错过回调的帧会顺延到下一轮。
验证与排障
环境:开发设备或模拟器;以下命令不需要 root。
adb shell dumpsys gfxinfo <包名> framestats
adb shell dumpsys SurfaceFlinger | grep -iE "vsync|buffer"
adb shell perfetto -o /data/misc/perfetto-traces/trace.perfetto-trace \
-t 10s sched freq idle atrace gfx viewframestats 看帧时间戳分布;Perfetto 帧轨道看 VSync 与 doFrame 对齐情况。掉帧时 先确认是否错过 VSync,再看哪段工作超时。
常见误区
- “绘制可以随时开始”:由 VSync 驱动。
- “缓冲越多越好”:延迟与流畅的权衡。
- “Choreographer 只是计时器”:是帧调度中心。
- “错过 VSync 会自动补齐”:会顺延,表现为掉帧。
延伸问题
- 掉帧时缓冲队列如何表现?
- 如何从 framestats 看 VSync 漂移?
- 动画与绘制的帧回调顺序是什么?
- 120Hz 下 Choreographer 调度有什么变化?
源码入口
| 文件 | 关键位置 | 作用 |
|---|---|---|
Choreographer.java | doFrame() | 帧调度 |
DisplayEventReceiver.java | VSync 接收 | 信号入口 |
| BufferQueue | 缓冲流转 | 防撕裂 |
SurfaceFlinger | 合成 | 上屏 |
公共路径:frameworks/base/core/java/android/view/ 与 frameworks/native/libs/gui/。行号以 r75 检索为准。
缓冲如何交给系统合成,见 Surface 与 SurfaceFlinger。