第2章 Systrace基础:抓取Trace、分析CPU调度与UI线程

Systrace 分析知识体系 抓取 Trace CPU 调度分析 UI 线程分析 线程状态识别 Jank / 掉帧定位 定位卡顿根因 分析调度延迟 分析渲染管线 汇聚定位根因

2.1 什么是 Systrace

Systrace 是 Android 平台上一款系统级追踪工具。它能抓取 kernel、framework、app 各个层级的事件,然后生成一张时间轴图。说白了,就是把手机里「什么时间谁在干活、干了多久」全都记录下来。

我刚开始做性能优化时,遇到卡顿第一反应就是加日志、打点。后来发现,日志打多了影响性能,打少了又查不到问题。用了 Systrace 之后才明白——系统级 profiling,就得用系统级的工具。

2.2 抓取 Trace 的两种方式

抓 trace 不像你想的那么复杂。常用的方式就两种,我一个个说。

方式一:命令行抓取

我个人习惯用命令行,灵活性高。核心命令就一行:

python systrace.py -t 10 -o trace.html sched gfx view input am wm

解释几个关键参数:

  • -t 10:抓 10 秒。别抓太久,文件会很大,分析起来也费劲。
  • -o trace.html:输出文件名。记得用 .html 后缀,这样可以直接在浏览器打开。
  • sched gfx view ...:标签(tag)。sched 是 CPU 调度,gfx 是图形渲染,view 是视图体系。我建议至少带上 sched 和 gfx,否则后面分析 CPU 和 UI 线程时会缺数据。
小技巧:如果你觉得记参数麻烦,可以直接用 adb shell atrace -t 10 sched gfx view。效果一样,而且不用找 systrace.py 脚本的位置。

方式二:Android Studio 图形化抓取

在 Android Studio 里打开 Profiler,选择 "CPU" 板块,点 "Record" 按钮。这种方式对新手友好,但我自己很少用——图形化工具启动慢,而且自定义标签的能力不如命令行。

注意:我曾经有一次在生产环境抓 trace,忘了关「高级 profiling」选项,结果 trace 文件撑到 200MB+,浏览器直接卡死。建议抓取时长控制在 10-15 秒以内,标签按需选择,别全勾上。

2.3 看懂 Systrace 界面

打开生成的 trace.html,你看到的是一张密密麻麻的时间轴图。别慌,抓住三个核心区域就够了:

  1. 顶部时间轴:显示全局时间线,可以缩放、拖拽。
  2. 中间进程/线程行:每个进程占一行,点开能看到里面的线程。UI 线程通常叫 "主线程" 或者你的包名。
  3. 彩色条形图:每个条形代表一个事件片段。颜色越深、条越长,说明耗时越严重。

你想想看,整个 trace 其实就是一张「系统运行流水账」。我们要做的,就是在这张流水账里找到「哪个环节慢了」。

2.4 CPU 调度分析

CPU 调度这块,很多人容易忽略。其实很多卡顿不是代码写得烂,而是线程没抢到 CPU 时间片。

在 Systrace 里,每个线程的状态用颜色区分:

线程状态 颜色 含义
Running 绿色 线程正在 CPU 上执行
Runnable 蓝色 线程就绪,但未被调度
Sleeping 红色 线程休眠/等待锁
Uninterruptible Sleep 橙色 不可中断休眠(常见于 I/O)

分析要点:如果你看到 UI 线程频繁出现大段蓝色(Runnable),说明它在等 CPU。这时候就算代码写得再快,界面也是卡顿的。根本原因可能是其他线程占用了太多 CPU 资源,比如后台 GC 频繁、其他进程抢占。

避坑指南:我曾经遇到一个案例,UI 线程每次滑动的 runnable 时间都超过 30ms。查了很久才发现,是某个三方 SDK 在后台开了大量线程做图像处理。去掉那个 SDK 后,滑动帧率直接从 40fps 涨到 55fps。

2.5 UI 线程分析

UI 线程分析是 Systrace 最常见的用途。说白了,就是看「一帧在 UI 线程上花了多久」。

在 Systrace 里搜索你的包名,或者直接找 "主线程" 行。重点关注这几个标记:

  • Frame:系统给每一帧打的标记。如果 Frame 标记间隔超过 16.6ms,说明掉帧了。
  • performTraversals:View 三大流程(measure、layout、draw)的入口。耗时过长说明布局复杂或绘制重。
  • Choreographer.doFrame:编舞者回调。这里能看到输入、动画、绘制各阶段耗时。
  • VSync:垂直同步信号。如果 VSync 与 doFrame 间隔很大,说明主线程被其他任务堵住了。
核心判断逻辑:
点击一个 Frame 标记,查看它的 Duration。如果 Duration > 16.6ms,这一帧就是 Jank。
然后看这个 Frame 里面哪个子片段耗时最长,那就是卡顿的 root cause。

举个例子,我遇到过一帧耗时 45ms,点开 Frame 发现 performTraversals 占了 38ms。再往下钻,里面 draw 占了 30ms。最后定位到是一个自定义 View 在 onDraw 里做了大量的 Path 运算。优化方案就是把这个运算缓存成 Bitmap,启动时预计算好。

2.6 实战:定位一个卡顿问题

好了,理论说完了,我带你走一遍完整的排查流程。假设现在用户反馈「列表滑动卡顿」。

  1. 抓 trace:adb shell atrace -t 12 sched gfx view input,滑动过程中抓取。
  2. 看整体帧率:打开 trace 后,先看顶部有没有大量红色 Frame 标记。红色代表掉帧。
  3. 定位问题线程:找到 UI 线程,看它的运行状态。是 Runnable 还是 Running?
  4. 如果是 Runnable:切到 CPU 行,看哪个核心忙。找到抢占 CPU 的进程。
  5. 如果是 Running 但耗时:点开 Frame,看 performTraversals 或 doFrame 里的细分耗时。
  6. 锁定根因:是布局 measure 慢?还是 draw 里做了什么?或者等锁?
  7. 修复验证:改完代码后,同样的场景再抓一次 trace,确保帧率恢复正常。
我的经验:Systrace 不是一次就能抓到问题的。有时候 trace 抓 10 秒,刚好那几秒没卡顿。我一般会连续抓 3-5 次,再对比分析。另外,别忘了用 trace 的搜索功能:按 Ctrl+F 搜 "jank"、"long"、"warning" 等关键词,系统会帮你高亮可疑事件。