第9章:掉帧分析工具:Systrace/Perfetto使用技巧、FrameTimeline解读、jank分类与根因定位
掉帧分析,说白了就是跟「卡顿」打交道。我做了这么多年性能优化,最深的体会是:没有工具,你就是在盲人摸象。Systrace 和 Perfetto 就是我们的「眼睛」,而 FrameTimeline 则是那盏「探照灯」。
这一章,我会把这三样东西怎么用、怎么看、怎么定位根因,掰开了讲清楚。
9.1 Systrace 与 Perfetto:选哪个?
先回答一个常见问题:Systrace 和 Perfetto 到底用哪个?
我个人习惯是:Android 10 以下用 Systrace,10 及以上无脑上 Perfetto。Perfetto 是 Systrace 的进化版,数据更全、UI 更现代、还能做 SQL 查询。但有些老项目还在用旧设备,Systrace 依然有用武之地。
核心区别速览:
- Systrace:轻量、快速、适合日常快速抓取。但数据量有限,不支持长时间录制。
- Perfetto:支持多数据源(CPU、GPU、内存、I/O)、可录制数十分钟、支持 SQL 分析。我建议你花一周时间熟悉它,回报率极高。
9.2 抓取技巧:别瞎抓,要有目标
很多同学一上来就抓全量 trace,结果数据大到打不开。嗯,这里要注意:抓取前先想清楚你要看什么。
比如,你怀疑是 GPU 渲染慢,那就只开 GPU 相关的 tag。我一般这样抓:
# Systrace 示例:只抓渲染和输入相关
python systrace.py -t 10 -o trace.html gfx input view sched
# Perfetto 示例:抓取 15 秒,包含 GPU 和帧信息
adb shell perfetto -c /data/local/tmp/perfetto_config.pbtx -o /data/local/tmp/trace.perfetto-trace
我的小技巧:抓取时让用户反复执行那个卡顿操作 3-5 次。这样 trace 里会有多个样本,方便对比「正常帧」和「掉帧」的差异。
9.3 FrameTimeline:帧的「体检报告」
FrameTimeline 是 Perfetto 里我最爱的功能之一。它把每一帧的生命周期都画了出来——从输入事件到渲染完成,一目了然。
我曾经在一个视频播放器项目里,发现帧率忽高忽低。打开 FrameTimeline 一看,好家伙,每一帧的「预期完成时间」和「实际完成时间」之间差了整整 16ms。这就是典型的「渲染管线阻塞」。
解读 FrameTimeline 时,我主要看三个指标:
| 指标 | 含义 | 正常值 |
|---|---|---|
| Expected Frame Time | 系统期望的帧时长(通常 16.6ms) | ≤ 16.6ms |
| Actual Frame Time | 实际完成一帧的时间 | ≤ 16.6ms |
| Frame Missed | 是否掉帧 | false |
如果 Actual Frame Time 超过 16.6ms,那就是掉帧了。接下来要问:是 CPU 慢了,还是 GPU 慢了?
9.4 Jank 分类:别把所有掉帧都当成一回事
掉帧也分三六九等。我一般把 jank 分成三类:
- 输入响应型 jank:用户点击后,应用响应慢。根因通常是主线程被阻塞(比如布局计算、垃圾回收)。
- 渲染管线型 jank:CPU 或 GPU 某一端处理不过来。常见于复杂动画、大量 Overdraw。
- 系统资源型 jank:CPU 频率不够、内存不足、I/O 抖动。这类问题往往不是应用代码的锅,而是系统调度出了问题。
避坑指南:我曾经遇到一个案例,掉帧率高达 30%,但所有代码看起来都没问题。最后发现是 CPU 降频 了——手机发热,系统自动限制了频率。所以,分析 jank 时一定要看 CPU 频率曲线,别光盯着应用层。
9.5 根因定位:三步法
我总结了一个「三步定位法」,屡试不爽:
- 第一步:看 FrameTimeline。确认掉帧发生在哪一帧,是 CPU 端还是 GPU 端。
- 第二步:看线程状态。在 Perfetto 里找到对应线程,看它是在 Running、Runnable 还是 Sleeping。如果是 Runnable 但没 Running,说明 CPU 资源不够。
- 第三步:看函数调用栈。用 Perfetto 的 SQL 查询功能,找出耗时最长的函数。比如:
-- 查询耗时超过 8ms 的 slice
SELECT name, dur FROM slice
WHERE dur > 8000000
ORDER BY dur DESC
LIMIT 10;
这三步走下来,90% 的掉帧问题都能定位到根因。
9.6 知识体系总览
下面这张图,是我对本章知识结构的总结。你可以把它当作一个「检查清单」:
9.7 实战案例:一个典型的掉帧分析
最后,分享一个我最近遇到的案例。某社交应用在滑动列表时掉帧严重。我抓了 Perfetto trace,打开 FrameTimeline,发现掉帧集中在 GPU 端。
然后我看了 GPU 的渲染管线,发现 RenderThread 在等待一个同步锁。再往下查,原来是某个自定义 View 的 onDraw 里做了 Bitmap 的缩放操作——这个操作是同步的,而且很耗时。
解决方案很简单:把 Bitmap 缩放放到后台线程做,或者用 GPU 加速的 API。改完后,掉帧率从 15% 降到了 1% 以下。
我的经验:很多掉帧问题,根因都在「不该在主线程做的事」。你只要养成一个习惯——每次写代码前问自己:这个操作会不会阻塞主线程?——就能避免 80% 的掉帧问题。
公众号:蓝海资料掘金营,微信deep3321