3、帧率测量(FPS):Choreographer原理、dumpsys gfxinfo、SurfaceFlinger如何获取帧率数据

帧率这玩意儿,说白了就是手机屏幕每秒能刷多少帧画面。60 fps 还是 120 fps,用户体验差得不是一星半点。我最早接触性能优化时,以为帧率就是掐个秒表数画面,后来才发现——Android 的帧率测量背后藏着一整套机制。

这一章,咱们就掰开揉碎聊聊:Choreographer 怎么驱动渲染?dumpsys gfxinfo 到底抓了什么数据?SurfaceFlinger 又凭什么能告诉你真实的掉帧情况?

Android 帧率测量核心架构 应用渲染层 Choreographer vsync 回调编排 VSync 信号 硬件同步脉冲 帧回调处理 input/animation/traversal 数据测量层 dumpsys gfxinfo 帧时间直方图 + 掉帧统计 SurfaceFlinger 合成帧统计 + 帧率追踪 指标输出层 帧率指标 (FPS / 掉帧率) 用于性能评估 & 卡顿定位 ⚠️ 三种测量手段互补

3.1 Choreographer:Android 渲染的"心跳"

Choreographer 这个词,直译过来是"编舞者"。它在 Android 里的角色,就是协调所有跟渲染相关的操作。我个人习惯把它理解成渲染节奏的节拍器。

每次屏幕刷新前,硬件会发一个 VSync 信号。Choreographer 收到这个信号后,会依次触发三个回调:

  1. input 回调 —— 处理用户输入事件
  2. animation 回调 —— 执行动画帧
  3. traversal 回调 —— 触发 measure / layout / draw

这三个回调跑完了,一帧的渲染数据才算准备好。然后交给 RenderThread 去渲染,最后送到 SurfaceFlinger 合成。

💡 我的小经验:排查卡顿时,我经常先看 Choreographer 的回调耗时。如果 traversal 回调超过 16ms(60fps 下),那这帧肯定掉。用 adb shell dumpsys gfxinfo 能看到每个回调的耗时分布。

为什么会卡?说白了就是 Choreographer 的回调队列里,某个任务超时了。我记得有一次排查一个列表滑动卡顿,发现是某个 View 的 onDraw 里做了 Bitmap 解码,直接拖垮了 traversal 回调。

3.2 dumpsys gfxinfo:命令行里的帧率放大镜

这个工具是我日常用得最多的。你不需要装任何第三方工具,一行命令就能拿到帧率数据。

adb shell dumpsys gfxinfo <package_name>

它会输出一大堆数据,但最关键的是这两块:

  • 帧时间直方图 (Frame timeline) —— 每一帧的渲染耗时,精确到毫秒
  • 掉帧统计 (Jank frames) —— 超过 16ms 的帧会被标记为掉帧

举个例子,输出里你会看到类似这样的内容:

Window: com.example.myapp
Stats since: 320000000000ns ago
Total frames rendered: 1200
Janky frames: 45 (3.75%)
90th percentile: 18ms
95th percentile: 22ms
99th percentile: 32ms

这里 Janky frames 就是掉帧数,3.75% 意味着每 100 帧里有将近 4 帧是卡顿的。我个人习惯把这个比例控制在 1% 以内才算合格。

📊 关键指标速查:
  • 90th percentile < 16ms → 流畅
  • 90th percentile 16~20ms → 轻度卡顿
  • 90th percentile > 20ms → 明显卡顿,需要优化
⚠️ 注意:dumpsys gfxinfo 只在 debug 版本或者 userdebug 版本上才能拿到完整数据。release 版本可能输出为空或数据不全。我曾在线上包上踩过这个坑,半天抓不到数据,后来才发现是包类型不对。

3.3 SurfaceFlinger:系统级的帧率守门员

Choreographer 管的是应用层的渲染编排,但真正把各层合成并送显的是 SurfaceFlinger。它是系统服务,运行在独立的进程中。

你可以通过下面的命令查看 SurfaceFlinger 的帧率统计:

adb shell dumpsys SurfaceFlinger --latency

这个命令会输出每一帧的 时间戳数组,包括:

  • app 时间 —— 应用开始渲染的时间
  • composition 时间 —— SurfaceFlinger 开始合成的时间
  • 送显时间 —— 实际显示到屏幕的时间

通过对比这些时间戳,你能精确算出每一帧从诞生到显示花了多久。这是最接近"用户真实感受"的帧率数据。

🔍 我曾经用过的方法:有一次线上反馈说某个页面"肉眼可见的卡",但 dumpsys gfxinfo 显示掉帧率只有 2%。我换成 SurfaceFlinger 的 latency 数据一看,发现 composition 阶段的耗时暴涨——原来是某个第三方 SDK 频繁提交了大尺寸 Bitmap 图层,把合成管线堵住了。这种问题只看应用层数据根本发现不了。

SurfaceFlinger 还提供了另一个有用的命令:

adb shell dumpsys SurfaceFlinger --list

它会列出当前所有的 Layer(图层),以及每个图层的帧率、分辨率、格式等信息。你可以在线上快速确认是不是某个图层拖慢了整体帧率。

3.4 三种方法对比:什么时候用哪个?

测量方法 数据来源 精确度 适用场景
Choreographer 回调 应用内部回调耗时 ★★★ 中 开发阶段定位卡顿代码
dumpsys gfxinfo 应用帧时间直方图 ★★★★ 高 线下 / 自动化性能测试
SurfaceFlinger latency 系统合成时间戳 ★★★★★ 最高 线上 + 线下,排查合成瓶颈

你想想看,如果只是开发时自测,用 Choreographer 回调就够了。但要出性能报告给老板看,我建议用 dumpsys gfxinfo 拿掉帧率。要是线上用户反馈卡顿,必须上 SurfaceFlinger —— 因为只有它能告诉你系统层到底发生了什么。

✅ 我的推荐组合:
  • 日常开发:Choreographer 回调 + adb logcat 看帧耗时
  • CI 自动化:dumpsys gfxinfo 采集掉帧率
  • 线上疑难:SurfaceFlinger latency 全链路追踪
⚠️ 避坑提醒:我曾经在某个项目里只用 dumpsys gfxinfo 判断帧率,结果优化了半天,用户还是反馈卡。后来才发现,gfxinfo 统计的是"应用渲染帧",而 SurfaceFlinger 合成的帧才是用户真正看到的。两个数据可能相差 10%~20%。所以——永远以 SurfaceFlinger 的数据为准。

嗯,帧率测量这块,说到底就是搞清楚数据从哪儿来、在哪儿卡、怎么度量。Choreographer 告诉你应用层有没有偷懒,dumpsys gfxinfo 把帧耗时掰开了给你看,SurfaceFlinger 则站在系统层给你最终的"判决"。三个层面都吃透了,帧率问题基本逃不过你的眼睛。