帧率基础:FPS、帧时间与卡顿概念

讲性能优化,绕不开帧率。很多同学一上来就盯着 FPS 看,觉得只要 60 就万事大吉。说实话,我早期也犯过这个错误。后来踩了几次坑才发现——FPS 只是个表象,真正要揪的是「帧时间」。今天咱们就把最基础的概念掰开揉碎,讲清楚了再往下走。

1. FPS:每秒多少帧?别被数字骗了

FPS(Frames Per Second)就是画面每秒钟刷新的次数。60 FPS 代表一秒内渲染了 60 帧画面,理论上每帧耗时 16.67 毫秒。这个数字越大,用户感知到的流畅度越高。

但有个问题:FPS 是一个「平均指标」。如果一秒内前 500ms 跑了 59 帧,后 500ms 只跑了 1 帧,FPS 算下来还是 60。你想想看,用户眼里的实际体验就是突变的卡顿。所以我个人习惯——不只看 FPS 的平均值,更关注它的「抖动」。

FPS 数值用户体验常见场景
≥ 60非常流畅静态页面、简单列表滑动
30 ~ 59可感知掉帧复杂布局、大量图片加载
15 ~ 29明显卡顿GC 频繁、主线程耗时操作
< 15几乎不可用内存抖动、过度绘制严重
一个小技巧:在 Profiler 里我总把 FPS 曲线和 CPU/GPU 时间线叠加看。如果 FPS 突然掉到 20 以下,但 CPU 使用率反而很低,那大概率是 GPU 瓶颈——渲染太复杂了。

2. 帧时间:真正该盯的死线

什么叫帧时间?就是一帧从开始绘制到最终显示到屏幕上的耗时。Android 目前主流屏幕刷新率是 60Hz,那么每一帧的生命周期就是 16.67ms。你所有的 UI 工作——布局、测量、绘制、合成——都得在这个时间内完成。超了?画面就跳过去了,下一帧才能补上。

我在项目中遇到过这样的事:一个列表页加载了 20 张高清图,不做任何压缩。Profiler 一看,单帧时间飙到 80ms,FPS 却还有 45 左右。为什么?因为掉帧 3~4 帧之后,数据又被平均上去了。听我的,以后看帧率先把帧时间曲线拉出来。比 FPS 准得多。

// 你可以用 FrameMetrics 来获取精确帧时间
window.addOnFrameMetricsAvailableListener { _, frameMetrics, _ ->
    val frameTimeNanos = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION)
    // 转换成毫秒
    val frameTimeMs = frameTimeNanos / 1_000_000f
    if (frameTimeMs > 16.67f) {
        Log.w("FrameTracer", "掉帧!耗时:$frameTimeMs ms")
    }
}

3. 卡顿的本质:掉帧与 jank

卡顿就是用户感受到的画面停顿或跳跃。它的根源只有一个:帧时间超出了屏幕刷新周期。比如 60Hz 的屏幕上,某帧处理了 50ms。那这 50ms 里本该有 3 次刷新机会,结果只显示了 1 帧。肉眼就看到了「跳一下」的效果。

Android 社区管这种丢帧叫 jank。Profiler 里会统计 jank 数和 jank 率。我建议你关注这两个指标:

  • Jank 次数:在 1 秒内发生掉帧的帧数量。
  • Jank 率:jank 帧数 / 总帧数。超过 5% 就需要警惕。
我曾经犯过的一个错:只盯着 FPS 优化,把一帧从 20ms 降到了 18ms,觉得进步很大。结果 jank 率根本没变——因为 18ms 仍然超过 16.67ms,该掉的帧一个不少。后来我明白:优化的目标不是「让帧时间变小」,而是「让帧时间始终低于 16.67ms」。

4. 核心关系图:一目了然

下面这张图把 FPS、帧时间和卡顿的关系串起来了。记住这三个核心节点,后面的实战才有方向。

帧率基础核心知识图谱 FPS(每秒帧数) 平均指标,易被平均掩盖 帧时间(ms) 关键指标,需<16.67ms 卡顿(Jank) 用户感知到的掉帧 平均值 ≠ 真实体验 需关注最小值/抖动 帧时间分解 布局+绘制+合成 Jank 率>5%⚠️ 需要深入优化 核心结论 • 60 FPS ≠ 60 帧都不超时,可能包含多次掉帧后的平均值 • 帧时间 > 16.67ms 必然产生 jank,这是卡顿的直接原因

5. 实战中的取舍:你该关注哪个指标?

我的习惯是分层级看:

  1. 第一层(快速筛查):看 FPS 曲线是否长期低于 50。如果是,直接进入帧时间分析。
  2. 第二层(精确定位):打开 Profiler 的 Frame Timeline,找到那些红色或黄色的帧(表示超时)。记下它们的帧时间分布。
  3. 第三层(根因定位):结合 CPU/GPU 曲线,看是主线程阻塞了,还是 GPU 渲染超负荷。后面几章我会专门讲怎么读这些曲线。
一句话总结本段:FPS 是面子,帧时间是里子,Jank 率是痛感。你做优化,先盯里子,再看痛感有没有消失。面子自然会好看。

好了,基础概念就到这。下次我们用 Profiler 跑一遍真实 App,亲手抓一帧超时的「犯罪现场」。