第4章:使用Systrace分析ANR

说到ANR排查,我最常用的工具不是logcat,而是Systrace。为什么?因为logcat只能告诉你“发生了什么”,而Systrace能告诉你“为什么发生”。今天咱们就聊聊怎么用这个神器。

我个人习惯在定位ANR时,先抓一份Systrace,再看logcat。顺序反了反而浪费时间。你想想看,ANR的本质是主线程卡住太久,那到底是CPU抢不到时间片?还是Binder调用超时?或是有个锁被死死拽住?Systrace一打开,这些全都一目了然。

4.1 什么是Systrace?它能看什么?

Systrace是Android平台上的性能跟踪工具。它通过ftrace内核机制,把系统层面和应用层面的活动都记录下来。说白了,就是给Android系统装了一台“行车记录仪”。

它能监控以下几类关键信息:

类别具体内容
CPU调度每个CPU核心上跑了哪些线程、运行时长、是否被抢占
Binder通信线程间IPC调用、阻塞状态、等待原因
锁竞争同步锁(synchronized、Lock)的等待和持有
输入事件输入分发的延迟、丢帧情况(jank)
SurfaceFlinger帧合成、屏幕刷新同步

我在项目中遇到过这样一个案例:一个ANR日志里主线程一直卡在InputStream.read(),logcat看不出任何端倪。后来抓了Systrace,发现当时CPU 0-7号核心全部满负载跑别的后台线程,主线程根本没分到时间片。这就是典型的“CPU饥饿”——光看logcat永远找不到根因。

4.2 抓取Systrace的三种方式

抓Systrace很简单,但不同场景要用不同方法。我分情况讲:

4.2.1 通过命令行(最通用)

python systrace.py -t 10 -o mytrace.html -a com.example.yourapp \
    sched freq idle load am wm gfx view webview

参数说明:

  • -t 10:抓10秒
  • –o mytrace.html:输出为HTML文件,可直接在Chrome打开
  • -a 包名:指定要跟踪的应用进程
  • 后面的sched、freq等是跟踪类别,常用sched(调度)、gfx(图形)、view(视图)
小技巧:我习惯把sched和gfx一起开,前者看线程调度后者看界面渲染。如果怀疑是数据库引起的ANR,可以加上freq和idle看CPU频率变化。

4.2.2 通过Android Studio Profiler(适合调试阶段)

Android Studio 3.2以上版本自带CPU Profiler,点“Record”开始录制。优点是可视化好,能看到实时堆栈。缺点是录制时长有限,而且对低端设备有性能开销。我个人只在调试重现问题时用它。

4.2.3 通过Perfetto(新项目推荐)

Perfetto是Systrace的下一代工具,从Android 10开始原生支持。抓取命令稍有不同:

adb shell perfetto -o /data/misc/perfetto-traces/trace.perfetto-trace \
    -t 10s sched freq idle am wm view

抓完后用adb pull拉下来,在ui.perfetto.dev上打开分析。Perfetto的UI比Systrace更现代,过滤功能更强。嗯,这里要注意:如果你的APP还在用旧API(比如Trace.beginSection),Perfetto同样兼容。

4.3 读懂Systrace的时间轴

打开抓好的Systrace HTML文件(用Chrome),你会看到密密麻麻的彩色条带。别慌,我教你看核心区域:

上半部分:CPU调度行
每个CPU核心一行,上面跑着不同颜色的线程块。颜色代表不同进程。鼠标悬停能看到线程名、优先级、运行时长。

下半部分:进程内部跟踪
这一层是按进程展开的,你会看到:

  • 主线程(通常是“包名”或“main”)
  • 渲染线程(RenderThread)
  • 其他线程(binder线程池、线程池等)

关键信号:如果主线程的条带出现大面积空白或灰色块,说明它没在运行(被挂起或阻塞)。如果出现红色“S”标记,说明发生了调度延迟。

排查ANR的黄金三步:
  1. 找到ANR发生的时间点(可以在logcat里找到ANR时间戳,或直接在Systrace里搜索“ANR”字符串)
  2. 放大那个时刻,看主线程在干嘛:是处于Runnable但没被调度?还是处于Blocked等待锁?
  3. 从主线程的堆栈反推,找到导致阻塞的根因(比如Binder调用、I/O操作、锁竞争)

4.4 实战:用Systrace定位两个典型ANR

案例1:主线程等待Binder响应

Systrace上,主线程条带出现一大段黄色块,上面标着“Binder: waiting for reply”。鼠标点进去看详细信息,会显示正在等待的ServiceName和接口。

这种情况多半是系统服务响应超时,比如PackageManagerService在查询大量应用信息时卡住了。我曾经遇到过某款低端机,系统PackageManager在安装大量应用后查询速度极慢,导致主线程ANR。解决方案是在子线程做这类查询,或者使用缓存。

案例2:Room数据库写操作堵塞主线程

主线程在RoomDatabase.query()或insert()处卡住,Systrace上可以看到一个很宽的灰色块,表明主线程正在等一个锁。同时你会在另一个线程(比如RoomTaskExecutor线程)上看到数据库写操作在持续执行。

这通常是数据库事务冲突或WAL模式下的写放大。我之前遇到一个项目,用户反馈App经常ANR,一查Systrace发现Room在onCreate()里执行了migration操作(表结构变更),主线程等了一两秒才返回。

避坑指南:千万不要在主线程调用Room的query()或insert()——即使你用了LiveData,Room在首次观察数据时也会在主线程做一次同步查询来获取初始值。我用allowMainThreadQueries()测试过,结果就是稳定ANR。

4.5 用Systrace对比“ANR窗口期”和“正常期”

很多同学只看ANR发生时刻的Systrace,但我建议你再加一步:抓一份正常操作下的Systrace做对比。比如用户在列表滑动时不卡,但点击某个按钮就ANR。对比两份trace,你能清晰看到差异点。

具体做法:

  1. 正常操作时抓一份trace(比如滑动5秒)
  2. 重复触发ANR场景抓另一份trace
  3. 在Chrome里同时打开两个tab,左右对比

重点看主线程的“调度间隙”——正常时每几十微秒就调度一次,ANR时可能出现几百毫秒没有调度。这种差异直接指向问题根因。

4.6 本章知识结构

Systrace分析ANR知识体系 Systrace分析ANR 抓取方式 关键信号解读 实战排查步骤 命令行 Profiler Perfetto 主线程状态 Binder等待 锁竞争/CPU饥饿 ANR窗口定位 对比分析 💡 实战要点 • 抓取时长建议5~10秒,包含ANR发生前后各几秒 • 区分“主线程没运行”和“主线程在运行但慢”——前者是调度问题,后者是任务耗时 • 如果Systrace里ANR时刻没有明显的阻塞,检查APP是否触发了GC停顿(System.gc())

4.7 避坑清单

  • 别只抓一次。我曾经复盘一个ANR,第一次trace没抓到,第二次才成功。无规律性ANR需要多次抓取。
  • 注意trace文件大小。长期录制会占几百MB,拉取时很慢。建议用-t 10限制时间。
  • 不要忽略“idle”状态。主线程进入idle不一定就是坏事(比如正常等待用户输入),但如果idle时长>5秒且伴随着CPU空转,那就是问题。
  • 结合logcat一起看。Systrace能看到“是什么”,但不知道“为什么会有这个操作”。比如主线程在等SharedPreferences写磁盘,Systrace只显示线程状态,而logcat里log才告诉你哪个业务触发了写操作。