卡顿量化指标:帧率、帧时间、丢帧率、Jank 数

卡顿不是感觉,而是可以被量化的数字。把模糊的「卡」翻译成四个精确的度量:帧率、帧时间、丢帧率、Jank 数,才能判断严重程度、定位瓶颈、验证修复效果。本章建立指标的标准化定义与采集方法,作为后续排查的基础语言。

本章价值:掌握四个指标的数学含义、物理意义及 Android 平台采集手段,避免用“看起来卡”这种无效描述汇报问题。

故障树:从卡顿现象到量化指标

用户感觉“卡”时,底层是一连串渲染事件的因果链。下图为故障树的简化版,展示现象如何分解到四个指标。

卡顿量化指标体系结构图 用户感知 → 界面卡顿 渲染丢帧 / 帧时间波动 / 绘制超过 16.67ms 帧率 (FPS) 实际渲染帧数/秒 帧时间 每帧渲染耗时(ms) 丢帧率 超时帧占比(%) Jank 数 严重卡顿帧累计 采集方式:FrameTimingMetrics / Macrobenchmark / dumpsys gfxinfo / Choreographer 工具:Perfetto / Android Studio Profiler / 自行埋点 排查逻辑: 若 FPS 低但帧时间稳定 → 瓶颈在计算/布局 若帧时间波动大 → 突发的垃圾回收/资源加载 若 Jank 数突增 → 立即检查主线程长任务

指标定义与权衡

指标 数学定义 理想值 波动含义
帧率 (FPS) 1 秒内实际渲染的帧总数 ≥60 FPS(120 Hz 屏 ≥120) 低于目标值 → 整体性能不足
帧时间 每帧从开始绘制到送显的耗时 (ms) ≤16.67ms(90Hz 为 11.11ms) 均值低但存在尖峰 → 偶发卡顿
丢帧率 帧时间 > 目标帧间隔的帧数 / 总帧数 ×100% <5% 越高表示性能不足或负载不均
Jank 数 帧时间 ≥ 目标帧间隔 × 2 的帧数(Android 官方定义) 尽量为 0 每出现一次 = 用户能感知的卡顿
常见误解: FPS 60 不等于不卡。若帧时间呈“跳绳”状——前一帧 10ms,下一帧 60ms,后一帧 5ms——FPS 仍可能接近 60,但用户感觉明显顿挫。此时必须看帧时间分布和 Jank 数。

数据采集方式

Android 平台提供了至少三种方式获取上述指标,按精度和实时性排序如下。

  1. FrameTimingMetrics(Android 12+):最精准、低开销,每个帧时间都会被记录,并可导出单帧详细阶段(测量/布局/绘制)。
  2. Macrobenchmark + BaselineProfile:自动化测试场景,返回 FPS、丢帧率、Jank 数,适合 CI 流水线。
  3. dumpsys gfxinfo / profcollect:命令行工具,适合线下分析和历史对比。

以下为通过 FrameTimingMetrics 获取帧时间的核心代码片段:

// 在 Compose Activity 或 Fragment 中注册
val frameTimingMetrics = FrameTimingMetrics.create()
frameTimingMetrics?.let { metrics ->
    // 在渲染线程回调中获取最近帧的时间
    val frameTimeNanos = metrics.getFrameTimeFrameMetrics()
    // 转换为毫秒
    val frameTimeMs = frameTimeNanos / 1_000_000f
    Log.d("FrameTiming", "帧时间: $frameTimeMs ms")
    // 可以用数组记录 N 帧后计算丢帧率
}
// 注意:FrameTimingMetrics 在 API 33+ 中稳定
采集建议:
  • 线上监控:使用 FrameTimingMetrics 或集成第三方 APM(如 Firebase Performance),对 Jank 数设置阈值告警(例如单屏 Jank > 3)。
  • 线下测试:运行 Macrobenchmark 时附加 --iterations 10 并输出 50 分位 / 95 分位帧时间。
  • 单帧定位:通过 Perfetto 抓取 Choreographer#doFrame 和 RenderThread 的 trace,找到特定帧的超时原因。

排查表:根据指标数值快速定位

以下排查表帮助你在拿到数据后立马判断下一步方向。

现象特征 帧率 帧时间 (P50 / P95) 丢帧率 Jank 数 典型原因
整体慢、操作跟手但延迟 ↓(30~40) P50≈25ms,P95≈35ms 30%~50% 每帧 Jank 略多 布局复杂度高 / 过度绘制
忽快忽慢,动画跳跃 50~60 但波动 P50=12ms,P95=50ms 10%~20% 少量高尖峰 主线程 GC / 网络回包
首次打开卡几秒 短暂跌到 5 单帧 200ms+ 短期 90%+ 1~2 个大 Jank 冷启动 / 资源加载
滚动列表时间歇顿挫 45~55 P50=15ms,P95=67ms 5%~15% 滚动过程中每屏 2~5 个 Jank Item 复用 / 图片解码

预防:建立量化门禁

在开发阶段就应使用以下规则拦截卡顿合入主分支:

  • 门禁规则 1:Macrobenchmark 场景下 Jank 数 ≤ 1,丢帧率 ≤ 3%。
  • 门禁规则 2:单次操作帧时间 P99 不超过目标帧间隔的 1.5 倍(90Hz 下 ≤16.67ms)。
  • 门禁规则 3:对比基线,帧时间均值增幅不超过 5%。

采集工具集成到 CI 后,每次 MR 自动跑一遍,卡顿风险立即可见。