第3章 Systrace 与 Perfetto:从入门到实战
3.1 先搞清楚——Systrace 到底在干嘛
搞性能优化,尤其是卡顿问题,我手里最常用的工具就是 Systrace。说实话,刚入行那会儿我觉得这玩意儿就是个时间轴彩条,后来才发现里面门道很多。
Systrace 的核心机制很简单——它在系统层埋了 hook,能记录每个线程在什么时间点干了什么事。你看到的那一条条彩色“面条”,其实都是 Android 系统里的关键组件:SurfaceFlinger、InputReader、Choreographer、RenderThread……
你想想看,如果应用掉帧了,我第一反应就是打开 Systrace 看“Choreographer#doFrame”这个 marker。如果它显示耗时超过 16ms,那肯定是有东西在主线程里搞鬼。我曾经在一个启动优化项目里,就是通过这个 marker 发现某个第三方 SDK 在 onCreate 里做了大量 IO,直接把首帧拖到了 500ms 开外。
3.2 抓取第一个 Trace 文件
抓取 Trace 文件的方式有好几种,我平时用命令行居多,尤其是要带参数定制埋点的时候。
# 抓取 5 秒,包名 com.example.app,包括常见 tag
python systrace.py -t 5 -a com.example.app \
-o trace_output.html \
sched freq idle am wm gfx view input res
# Android 10+ 我推荐直接改用 Perfetto 的命令行
perfetto \
--txt -c /data/misc/perfetto-configs/trace_config.pbtx \
-o /data/misc/perfetto-traces/trace.perfetto-trace
嗯,这里要注意:抓取时长别太长。我之前图省事抓了 30 秒,结果生成的文件有几百 MB,解析起来跟蜗牛一样。对大多数卡顿分析来说,5~10 秒足够了——你只需要复现问题的那一瞬间。
| 工具 | 输出格式 | 适用场景 |
|---|---|---|
| Systrace (Python) | .html | 快速抓取、< Android 10 的老设备 |
| Perfetto 命令行 | .perfetto-trace | 复杂埋点、Android 10+ 新设备 |
| Android Studio Profiler | 直接可视化 | 开发调试阶段、逐帧分析 |
3.3 解读 Trace —— 从“看彩虹”到“找元凶”
第一次看 Systrace 的人大概率会懵:满屏的彩条,密密麻麻的缩写。我来告诉你一个简单的方法:只看主线程。
找到你的应用进程(包名那一行),展开主线程(通常叫“主线程 (tid=xxx)”)。重点关注这几个 marker:
- Choreographer#doFrame —— 每一次帧绘制的起点,如果它下面有长条,那就是掉帧的源头。
- Input —— 输入事件处理,如果这里耗时多,说明点按卡顿。
- performTraversals —— 布局/绘制过程,Layout 阶段超时常见于复杂布局。
- dispatchAppVisibility —— 冷启动/热启动时的界面显示。
我个人习惯是:先看顶部有没有红色的“jank”标记,如果有,直接双指放大到那段区域,然后盯着主线程看它在那段时间里到底在跑什么方法。如果你发现长条上写着“Layout”或者“measure”,那就是布局层级太深或重复测量。
3.4 从 Systrace 到 Perfetto —— 为什么我建议你快迁移?
说句实话,Systrace 抓 .html 文件在 Android 10 之后官方已经停止维护了。Perfetto 才是现在的正统。它原生支持 Android、Linux,甚至 Chrome。数据格式从 HTML 变成 protobuf,解析效率高得多,而且能追踪 GPU、内存、电源等更多维度。
我在一个视频卡顿排查项目里,就是用 Perfetto 抓了 GPU 的“gpu.renderer”标签,才确认是硬件加速没开导致的渲染管线阻塞——这个信息旧版 Systrace 根本看不到。
# Perfetto 的配置文件示例(trace_config.pbtx)
buffers: {
size_kb: 20480
fill_policy: RING_BUFFER
}
data_sources: {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "power/suspend_resume"
ftrace_events: "sched/sched_switch"
ftrace_events: "workqueue/workqueue_execute_start"
ftrace_events: "workqueue/workqueue_execute_end"
}
}
}
duration_ms: 5000
# 启动时自动 dump
file_write_period_ms: 5000
3.5 Trace 文件解读的进阶姿势
无论是 Systrace 还是 Perfetto,解读的思路始终是一样:找长条 → 定位线程 → 分析原因。我整理了一个常见问题对照表,帮你快速判断:
| 现象 | 可能原因 | 验证方式 |
|---|---|---|
| Choreographer#doFrame 超 16ms | 主线程被阻塞(IO、锁、反射调用) | 查看 doFrame 下方是否有长条的函数调用 |
| Input 事件与下一个 frame 间隔很大 | Input 线程被优先级反转或进程被降频 | 看 sched 标签下的进程唤醒延迟 |
| SurfaceFlinger 频繁领空 | App 提交缓冲区太慢,或 GPU 渲染跟不上 | 检查 gpu_render 和 dequeueBuffer 的耗时 |
| WakeLock 长时间亮着 | 应用没及时释放锁,导致系统无法休眠 | 搜索 "wake_lock" 标签下的持锁时长 |
我曾经遇到过一个非常隐蔽的问题:用户反馈正常滑动时偶尔卡一下,抓了十几次 Systrace 都没发现明显长条。后来在 Perfetto 里启用了“sched_switch”事件,发现是某个后台线程频繁持有高优先级锁,导致主线程被抢占调度——说白了就是锁的优先级继承没处理好。这种问题用单纯看 marker 的方式根本发现不了,必须得分析 CPU 调度上下文切换。
ui.perfetto.dev(这是个本地离线工具)或者用 traceconv 转换为 HTML。我建议你先把整站缓存下来,然后直接用 Chrome 加载本地文件,速度比在线快得多。
这张图我把分析路径分成了四层:抓取 → 格式 → 工具 → 分析视角。你实际排查的时候,从上往下走一遍,基本能把问题范围缩小。注意右侧的 GPU/内存追踪,这是 Perfetto 相比 Systrace 最强的部分——我最近一次项目里就是因为看到 GPU 渲染队列有空闲,才确定是 CPU 提交缓冲区太慢导致的帧率下降。
好了,这一章的内容差不多就是这些。工具用法很简单,但真正要变成“性能直觉”还需要多练。下次你再遇到卡顿反馈,不妨先冷静下来——打开 Systrace 或者 Perfetto,抓一段数据,然后按照我上面说的思路一步步往下查。只要你看得足够多,那些彩条在你眼里就会变成一张张“犯罪现场”的地图。