4. Systrace/Perfetto 基础使用:trace 标签、时间轴解读、关键帧标记
卡顿问题的排查,如同医生使用心电图诊断病情。Systrace 和 Perfetto 就是 Android 开发者的“心电图机”,它们能记录系统在一段时间内的所有活动,让我们看到“哪里堵了”。本章将带你掌握这两款工具的基础用法,学习如何精准采集数据、识别关键信息,并标记出问题的核心。
现象识别:用户感知帧率低、操作滞涩、动画掉帧。
数据采集:使用 Systrace/Perfetto 抓取带有关键帧标记的 trace 文件。
原因定位:解读时间轴,分析 CPU、GPU、渲染线程、主线程的工作状态。
修复动作:根据定位结果调整逻辑,减少主线程负载。
复测:重新抓取 trace,确认帧率提升。
预防:建立标准 trace 采集规范,将关键帧标记写入开发流程。
4.1 现象识别:抓住“卡顿瞬间”
在准备采集 trace 之前,必须先明确要分析的场景。是列表滑动卡顿?还是启动动画掉帧?或是某个特定页面跳转时卡住?你需要先通过肉眼观察,确认一个大致的复现路径。
- 场景 1:列表滚动 — 用户快速滑动时,列表出现明显的停顿或白块。
- 场景 2:应用冷启动 — 从点击图标到首帧绘制完成耗时过长。
- 场景 3:动画过渡 — 页面转场动画不平滑,中间有明显跳帧。
只有明确了问题场景,才能准确地设置“关键帧标记”,从而在浩如烟海的时间轴中快速定位到问题区间。
4.2 数据采集:跑通 Systrace/Perfetto 流程
Systrace (旧版) 和 Perfetto (新版) 是上下兼容的关系。Perfetto 是更现代、更强大的替代方案,建议直接使用 Perfetto。但阅读旧 trace 文件时仍需了解 Systrace 的基础概念。
| 对比项 | Systrace (旧) | Perfetto (新) |
|---|---|---|
| 命令 | systrace.py | perfetto (命令行) 或 record 页面 |
| 产出格式 | HTML | Proto (通过 UI 转为 SQL 或 可视化) |
| 数据丰富度 | 系统级 + 部分 App Trace Tag | 全量系统 + 内存/电量/CPU 调度 |
| 推荐使用 | 遗留项目或熟悉旧工具 | 新项目首选 |
4.2.1 通过 Perfetto 抓取(推荐)
你可以使用 Perfetto 的命令行工具或直接在设备上打开 perfetto.dev 录制的 WebUI。以下是最常用的命令行采集方式:
# 1. 确保设备已 root 或使用 userdebug/buid 版本
adb shell perfetto \
-c /data/misc/perfetto-config/trace_config.pbtxt \
-o /data/misc/perfetto-traces/trace.perfetto-trace
# 2. 或者使用轻量级在线录制(在浏览器中操作)
# 打开 chrome://tracing 或 perfetto.dev,点击 Record new trace
在抓取过程中,你需要快速复现卡顿场景,并在问题发生前后,手动添加一个“关键帧标记”。
4.2.2 添加关键帧标记(Trace Tag)
关键帧标记就像在时间轴上画了一个“异常区间”,让你能瞬间聚焦。在 Android 代码中,使用 Trace.beginSection 和 Trace.endSection。
// 在卡顿发生的关键代码段前后添加标记
import android.os.Trace;
// 开始标记(例如:在列表滑动开始前)
Trace.beginSection("ListScroll::onTouchStart");
// ... 你的列表滑动逻辑 ...
// 结束标记(在列表滑动状态切换或结束绘制后)
Trace.endSection(); // 必须成对出现,否则 trace 会异常
Trace.beginSection。这些标记会直接出现在 Perfetto 的时间轴中,让你一眼看到“异常帧”落在哪个业务区间。
4.3 时间轴解读:从视觉到数据
拿到 trace 文件后,用 Perfetto UI 打开。你会看到一张极其丰富的时间轴图。需要重点关注以下几个关键区域:
- CPU 调度行 — 显示每个 CPU 核心上运行了哪些线程。如果卡顿帧发生时,主线程(UI Thread)处于“非运行”状态(Runnable 或 Sleep),说明 CPU 被其他进程抢占或主线程被阻塞。
- App 渲染管线 — 包括主线程 (Choreographer#doFrame)、RenderThread、GPU 完成等。如果主线程 doFrame 间隔异常,说明主线程做了过多工作。
- SurfaceFlinger — 系统合成服务。如果这里出现掉帧,说明 GPU 或合成过程有瓶颈。
# 在 Perfetto UI 中,你可以直接搜索关键帧标记名,快速跳转到问题区间
# 搜索框输入:ListScroll::onTouchStart
4.4 原因定位:聚焦关键帧区间
使用关键帧标记定位到时间轴上的“疑似卡顿帧”后,开始逐帧分析。
- 查看主线程 doFrame:如果 doFrame 的持续时间超过 16ms(一帧的预算),那么这一帧就是掉帧。
- 查看 doFrame 内部的细分:测量、布局、绘制。哪个阶段耗时最长?如果测量/布局(onMeasure/onLayout)过长,说明布局层级过深或自定义 View 布局逻辑复杂;如果绘制(draw)过长,说明有大量重叠绘制或复杂 Canvas 操作。
- 查看 RenderThread 的同步:主线程调用
invalidate后是否立即被 RenderThread 消费?如果长时间等待,可能是 GPU 排队。
| 特征 | 可能原因 |
|---|---|
| 主线程 doFrame > 16ms | 主线程做了太多工作(大图解码、动画计算、I/O) |
| 主线程在 doFrame 内频繁进入 Sleeping | Binder 调用、锁等待、同步 Barrier |
| RenderThread 出现大段“空闲” | GPU 资源下发命令慢,或等待 SurfaceFlinger |
| SurfaceFlinger 掉帧 | 合成层过多、硬件合成器(HWC)能力不足 |
4.5 修复动作与复测
定位到原因后,实施修复。例如:
- 主线程负载高:将图片解码、网络请求移到子线程。
- 布局层级深:使用 ConstraintLayout 或 Compose 减少嵌套。
- 渲染同步慢:开启 GPU 分析,检查是否使用了非硬件加速的 API。
修复后,必须复测:再次使用 Perfetto 抓取相同场景的 trace,并重点观察关键帧标记区域内的 doFrame 耗时是否恢复正常。
# 复测时的采样参数建议(确保数据可比)
adb shell perfetto \
-c /data/misc/perfetto-config/trace_config.pbtxt \
-o /data/misc/perfetto-traces/trace_after_fix.perfetto-trace
4.6 预防:将 trace 标记融入开发规范
不要等到出现问题才想到加标记。应将在所有性能敏感路径中提前加入标准化的 trace 标签,作为代码的一部分。
- 给每个 Activity 的 onCreate、onResume 加上标记。
- 给列表的 Adapter 中的 onBindViewHolder 加上标记。
- 给自定义动画的每一帧开始和结束加上标记。
这样,一旦出现线上卡顿,可以立即收集到包含业务上下文的现场 trace,大幅缩短排查周期。