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.pyperfetto (命令行) 或 record 页面
产出格式HTMLProto (通过 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 打开。你会看到一张极其丰富的时间轴图。需要重点关注以下几个关键区域:

  1. CPU 调度行 — 显示每个 CPU 核心上运行了哪些线程。如果卡顿帧发生时,主线程(UI Thread)处于“非运行”状态(Runnable 或 Sleep),说明 CPU 被其他进程抢占或主线程被阻塞。
  2. App 渲染管线 — 包括主线程 (Choreographer#doFrame)、RenderThread、GPU 完成等。如果主线程 doFrame 间隔异常,说明主线程做了过多工作。
  3. SurfaceFlinger — 系统合成服务。如果这里出现掉帧,说明 GPU 或合成过程有瓶颈。
# 在 Perfetto UI 中,你可以直接搜索关键帧标记名,快速跳转到问题区间
# 搜索框输入:ListScroll::onTouchStart
常见误区: 不要只看 CPU 负载。很多时候 CPU 负载很低,但主线程因为等待锁、Binder 调用或 I/O 操作而长时间“非运行”。一定要结合线程状态(Running / Runnable / Sleeping)来判断。

4.4 原因定位:聚焦关键帧区间

使用关键帧标记定位到时间轴上的“疑似卡顿帧”后,开始逐帧分析。

  • 查看主线程 doFrame:如果 doFrame 的持续时间超过 16ms(一帧的预算),那么这一帧就是掉帧。
  • 查看 doFrame 内部的细分:测量、布局、绘制。哪个阶段耗时最长?如果测量/布局(onMeasure/onLayout)过长,说明布局层级过深或自定义 View 布局逻辑复杂;如果绘制(draw)过长,说明有大量重叠绘制或复杂 Canvas 操作。
  • 查看 RenderThread 的同步:主线程调用 invalidate 后是否立即被 RenderThread 消费?如果长时间等待,可能是 GPU 排队。
特征可能原因
主线程 doFrame > 16ms主线程做了太多工作(大图解码、动画计算、I/O)
主线程在 doFrame 内频繁进入 SleepingBinder 调用、锁等待、同步 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,大幅缩短排查周期。

Systrace/Perfetto 时间轴解读与关键帧标记流程 Systrace/Perfetto 时间轴解读关键路径 时间轴区域 CPU 调度 (R/R/S) | 主线程 doFrame | RenderThread | SurfaceFlinger 关键帧标记 Trace.beginSection(“xxx”) GPU 工作负载 GPU 帧完成时间 现象识别 数据采集 原因定位 排查提示 1. 在关键帧标记区域 查看 doFrame 耗时 2. 关注主线程状态 3. 结合 GPU 工作负载 4. 排查锁等待/Binder