4、性能指标:FPS、帧时间、丢帧率
做过性能优化的同学都知道,衡量流畅度最核心的三个指标就是 FPS、帧时间、丢帧率。但说实话,很多人只盯着FPS看,反而踩了不少坑。我刚开始做优化时也这样——FPS稳稳60,用户却说滑动卡顿,百思不得其解。后来才明白,这三个指标要放在一起看,才能还原真相。
1. FPS(Frames Per Second)——最熟悉的陌生人
FPS就是每秒渲染的帧数。理论上60fps代表每帧约16.7ms,人眼就觉得流畅。但问题在于:FPS是平均值。你想想看,60fps完全可以是50帧1ms + 10帧50ms拼出来的。中间那10帧的卡顿,平均值根本体现不出来。
我在项目中遇到过类似情况。优化一个图文列表,FPS一直显示59~60,但用户反馈“像在泥里滑动”。后来我把FPS曲线画出来,才发现每隔几秒就掉到30帧以下,只是持续时间很短,平均回了60。所以我说,FPS只能当个参考,不能全信。
| FPS值 | 体验说明 |
|---|---|
| 55~60 | 看起来流畅,但可能有微小卡顿 |
| 30~55 | 明显感觉不跟手,界面有粘滞感 |
| <30 | 严重卡顿,基本不可用 |
关键点:FPS低于55时用户体验会显著下降,但高于55不代表没问题——还得看帧时间分布。
2. 帧时间(Frame Time)——真正的“隐形杀手”
帧时间就是每一帧从绘制到显示所消耗的时间,单位是ms。正常情况下每帧必须 ≤16.7ms(60fps),一旦超时就叫“丢帧”。我个人习惯把帧时间当作排查卡顿的第一入口——它比FPS敏感得多。
举个例子,两个应用都显示60fps,但App1的帧时间稳定在15~16ms,App2的帧时间50%在5ms、50%在25ms。实际体验App2会频繁跳帧,因为那50%的25ms帧已经超过了16.7ms。帧时间的标准差和最大值比平均值更重要。
我的建议:拿到帧时间数据后,先看最大值是否超过16.7ms。如果超过,再看超过的帧占比(丢帧率)。不要只看平均帧时间。
3. 丢帧率(Jank Rate)——更敏感的衡量标准
丢帧率指超过16.7ms的帧占总帧数的比例。我一般要求App的丢帧率 低于5%,超过10%基本就要回炉了。但注意:不是说丢帧率5%就代表95%的时间流畅——因为丢帧往往连续发生,用户感知到的是“卡顿瞬间”而非百分比。
我曾经吃过这个亏。线上App丢帧率只有3%,但用户投诉卡顿很集中。排查后发现所有丢帧都发生在同一个滑动路径上,每次连续丢3~5帧。3%的丢帧率掩盖了局部问题的严重性。所以我把丢帧率拆成连续丢帧和单次丢帧来分析,效果就好多了。
注意事项:丢帧率要结合帧时间分布图一起看。如果丢帧集中在长尾(比如>50ms的帧),即使比例低,也要优先修复。
三个指标的关系
下面这张图能帮你理清它们之间的逻辑:
4. 如何测量这三个指标
Android提供了多种工具,我个人最常用的是 FrameMetrics API 和 dumpsys gfxinfo。先说官方的框架层打点:
- dumpsys gfxinfo:通过adb命令获取整帧的绘制、准备、处理、执行四个阶段耗时。
- Systrace / Perfetto:在系统级看到vsync边界和线程调度,定位原因很方便。
- FrameMetricsListener:App内注册监听,实时拿到每帧的帧时间。
下面是一段利用 FrameMetrics 采集帧时间的代码示例:
// 在 Activity 的 onCreate 中注册
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
window.addOnFrameMetricsAvailableListener(
{ windowFrameMetrics, dropCountSinceLastInvocation ->
val frameMetrics = FrameMetrics.Builder()
.build() // 实际可从 windowFrameMetrics 中获取
val totalDuration =
frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION) / 1000000L
// 判断是否丢帧
if (totalDuration > 16) {
Log.w("FrameMonitor", "丢帧: ${totalDuration}ms")
}
},
Handler(Looper.getMainLooper())
)
}
个人经验:在生产环境中不要忘记取消注册,否则可能造成内存泄漏。我一般配合Lifecycle在onPause/onStop中移除监听。
| 工具 | 获取指标 | 适用场景 |
|---|---|---|
| dumpsys gfxinfo | 帧时间、丢帧数 | 线下调试、线上抽样 |
| Systrace / Perfetto | 帧时间、vsync、CPU调度 | 深度定位卡顿原因 |
| FrameMetricsListener | 实时帧时间 | 集成到APM中做线上监控 |
| Choreographer | 跳帧回调 | 获取丢帧回调,做预警 |
注意:dumpsys gfxinfo 在Android 12之后已经逐步废弃,建议迁移至 FrameMetrics 或 Perfetto。如果你还在用旧版本,记得确认采样窗口——默认只收集最近128帧。
嗯,到这儿你应该对三个指标有了清晰认识。简单归纳一下:帧时间是根因,FPS是表现,丢帧率是损失。下次拿到性能数据别只盯着FPS了,先把帧时间分布拉出来看看,很多问题自然就暴露了。