卡顿与帧时间线:帧预算与掉帧定位
2026/8/9大约 4 分钟性能与稳定性Android 14卡顿帧FrameTimeline
卡顿与帧时间线:帧预算与掉帧定位
上一章看了 ANR。这一章回答:一帧的时间预算是多少,掉帧怎么定义,从掉帧到根因 怎么定位?
60、90、120 Hz 对应不同提交期限,不能统一用 16.6 ms 判断。FrameTimeline 给出期望与 实际呈现时间;确认迟到帧后,还要继续区分应用生产、GPU 渲染与 SurfaceFlinger 合成阶段。
帧预算取决于刷新率
| 常见误解 | 正确理解 |
|---|---|
| 卡顿就是 doFrame 太慢 | 可能是输入延迟、动画调度、测量绘制或合成任一环节 |
| 60fps 就是每帧 16.6ms,超了就掉帧 | 掉帧判定要看期望帧时间,不同刷新率预算不同 |
| 掉帧数高就代表用户感知卡 | 偶发一帧掉不感知,连续多帧超预算才明显 |
核心结论
- 帧预算随刷新率变化:60/90/120Hz 对应约 16.6/11.1/8.3ms。
- FrameTimeline 给每帧“期望时间 + 实际时间”,超出的帧标为掉帧。
- 定位顺序:输入 → 动画 → 测量/布局 → 绘制 → 合成,逐段看耗时。
- Perfetto 帧轨道与线程状态联动,能还原掉帧瞬间谁在跑。
- 偶发掉帧看调度与 GC;持续掉帧看测量/绘制复杂度。
帧流水线与掉帧定位
逐步解释:
- 输入:事件到达与分发是否及时。
- 动画:动画回调是否按时推进。
- 测量/布局:视图树计算是否超时。
- 绘制:RenderThread 与 GPU 工作是否超预算。
- 合成:SurfaceFlinger 合成是否错过 VSync。
- 帧时间线:哪段超预算,掉帧就定位到哪。
偶发 vs 持续掉帧
偶发掉帧(几秒一次)常来自 GC、调度抢占或瞬时 Binder 等待;持续掉帧(几乎每帧) 常来自测量/绘制复杂度、动画开销或合成瓶颈。两种场景的取证方式不同:偶发看时间线 找“那一次”的阻塞,持续看方法耗时与布局层级。
源码证据
阅读目标:确认帧时间线数据源。
正文指针:Choreographer.java doFrame();libs/frametimeline/ 的 FrameTimeline 记录;SurfaceFlinger 的帧追踪。
// Choreographer.java(伪代码,行号以 r75 为准)
void doFrame(long frameTimeNanos, int frame) {
// 帧开始:之后执行动画、测量、布局、绘制
}这段代码证明: 应用侧帧的起点是 Choreographer;FrameTimeline 把该帧的期望时间 与实际完成时间对比,掉帧即可在 Perfetto 帧轨道中直接看到。
验证与排障
环境:开发设备或模拟器;以下命令不需要 root。
adb shell dumpsys gfxinfo <包名> framestats
adb shell dumpsys gfxinfo <包名> | grep -E "Janky frames|Total frames|50th|95th"
adb shell perfetto -o /data/misc/perfetto-traces/trace.perfetto-trace \
-t 10s sched freq idle atrace gfx view am wmframestats 给出逐帧明细;gfxinfo 汇总掉帧比例;Perfetto 还原掉帧瞬间。先确认是 偶发还是持续,再决定看调度还是看绘制复杂度。
常见误区
- “掉帧 = 主线程慢”:合成、GPU、调度都会导致掉帧。
- “16.6ms 是万能预算”:预算随刷新率变化。
- “掉帧数多就代表卡”:要看连续性与用户感知。
- “只看 gfxinfo 就够”:要结合时间线找根因。
延伸问题
- 90/120Hz 下掉帧判定有什么不同?
- RenderThread 与主线程的掉帧责任如何区分?
- 动画期间掉帧与静止页面掉帧的定位差异是什么?
- 如何用 FrameTimeline 对比系统合成与应用绘制?
源码入口
| 文件 | 关键位置 | 作用 |
|---|---|---|
Choreographer.java | doFrame() | 帧回调起点 |
libs/frametimeline/ | 帧记录 | 期望/实际时间 |
SurfaceFlinger | 帧追踪 | 合成侧时间 |
dumpsys gfxinfo | framestats | 逐帧数据 |
公共路径:frameworks/base/core/java/android/view/ 与 frameworks/base/libs/frametimeline/。行号以 r75 检索为准。
需要分析首帧之前的阶段时,见 冷启动优化。