第3章 ANR日志分析方法(traces.txt, logcat)

咱们直接切入正题。排查ANR,说白了就是跟两份日志打交道:traces.txt 和 logcat。一个像尸检报告,记录死前最后一刻的现场;另一个像监控录像,记录事发前后的系统行为。两样缺一不可。

我个人习惯先把logcat拉出来扫一眼,拿到进程名和时间点,再去蹲traces.txt。顺序别搞反了,否则你会一头扎进堆栈的海洋里出不来。

traces.txt —— ANR现场快照

位置固定在 /data/anr/traces.txt。每次ANR发生后,系统会把所有线程的调用栈 dump 到里面。注意——它只保留最近几次的ANR记录,不是从开机到现在全存着。

重要提醒: 我曾经有一次线上ANR排查,反复让用户抓日志,结果用户说“没看到traces.txt”。后来发现手机厂商魔改了系统,文件路径变成了 /data/anr/traces/ 下面按日期分文件。所以,拿到手机型号后先确认一下路径。

拉取方法很简单:

adb root
adb pull /data/anr/traces.txt .
adb shell cat /data/anr/traces.txt | grep "main" -A 30  # 快速看主线程

打开文件后,你会看到类似这样的结构:

----- pid 12345 at 2024-08-20 10:15:30 -----
Cmd line: com.example.myapp

"main" prio=5 tid=1 Native
  at android.os.BinderProxy.transactNative(Native Method)
  at android.content.ContentProviderProxy.query(ContentProviderProxy.java:...)
  at ...
  at com.example.myapp.MainActivity.onCreate(MainActivity.java:42)
  ...

"Thread-2" prio=5 tid=2 Blocked
  at java.lang.Object.wait(Native Method)
  - waiting on <0x0a1b2c3d> (a java.lang.Object)
  ...

核心字段解读:

线程名如 "main" 就是UI线程,其他是子线程
prio优先级,一般5是默认
状态Native / Runnable / Blocked / Waiting / TimedWaiting
堆栈从最底层到最顶层,看哪个方法卡住
锁信息waiting on / locked 标记了等待什么锁
排查口诀: 先看主线程是不是 Runnable 但卡在native调用?还是 Blocked 在等锁?还是进入 deep sleep(通常是IO等待)?

logcat —— ANR发生前的「监控录像」

logcat里通常会打出下面这样的ANR关键行:

I/WindowManager( 1234): ANR in com.example.myapp (com.example.myapp/.MainActivity)
I/WindowManager( 1234): Reason: Input dispatching timed out (Waiting to send non-key event ...)
I/WindowManager( 1234): CPU usage from 1234ms to 0ms ago (2024-08-20 10:15:28.123 to 10:15:29.347):
   0.0% 1234/system_server: 0.0% user + 0.0% kernel / faults: 0 minor 0 major
   99% 5678/com.example.myapp: 98% user + 1% kernel / faults: 100 minor 12 major
   ...
I/WindowManager( 1234): Total: 99% user + 1% kernel

这几行信息量很大:

  • Reason:ANR的原因类型。最常见的是 Input dispatching timed out(5秒没响应)、Broadcast of Intent(广播10秒)、Service Timeout(服务20秒)。
  • CPU usage:ANR发生前几秒内各进程的CPU占用情况。如果你的app CPU占了99%,排除一下是不是死循环。如果CPU不到1%,说明线程在sleep/wait或者IO堵塞。
  • faults:major faults 表示大量缺页中断(内存不够,频繁swap),会导致长时间阻塞。我曾经遇到过一个ANR,CPU占用只有5%,但major faults > 1000,一看是Bitmap没回收导致内存压力大。

另外,如果ANR涉及ContentProvider或Binder调用,logcat里还会打出:

W/ActivityManager( 1234): ANR: ContentProvider not responding after 10s
I/ActivityManager( 1234): Waiting for provider: com.example.myapp.provider.MyProvider

两种日志搭配使用 —— 实战演练

说个我亲手排查的案例。某用户反馈App点击后卡死,持续约10秒恢复。我的分析步骤:

  1. 抓logcat 发现 Reason 是 "Input dispatching timed out",CPU usage显示app进程占用0%,但 major faults 很高。
  2. 看traces.txt 主线程停在 SQLiteAccess 的 wait() 上,等待一个写锁。
  3. 查源码 发现 Room 的一个事务在子线程执行了全表更新,耗时15秒。主线程因为需要读取同一张表,被事务的写锁阻塞。
  4. 解决:把Room的查询也放到协程里,或者加一个超时机制。

这儿有个对比表格,帮你快速选看哪个日志:

场景优先看traces.txt优先看logcat
不知道哪个进程ANR❌ 不直观✅ 直接打出package name
需要线程堆栈细节✅ 完整调用链❌ 只显示简单堆栈(有时有partial stack)
分析CPU/IO/内存压力❌ 无✅ CPU usage + faults
查看锁竞争关系✅ waiting on / locked 明确❌ 一般没有锁信息

实战小贴士:如何一分钟定位「谁在搞鬼」

拿到traces.txt后,我通常会这么快速扫描:

# 只看主线程堆栈,找关键词
grep -A 30 '"main"' traces.txt

# 看有哪些线程处于 BLOCKED 或 WAITING
grep -E "(Blocked|Waiting)" traces.txt

# 查看所有锁对象等待链
grep -E "waiting on|locked" traces.txt

嗯,这里要注意:有些ANR是系统自己引发的,比如系统服务超时。这时候traces.txt里你的app堆栈可能是正常的,但logcat里会显示系统进程的ANR。别钻牛角尖。

还有一个坑:Android 10及以上,traces.txt 对常见App路径做了混淆/限制,你去pull可能会报 Permission denied。可以用 adb bugreport 把整个报告抓下来,里面有个 FS/data/anr/ 目录包含traces.txt。

核心总结:
  • 先看logcat的CPU usage → 判断是死循环(高CPU) 还是 IO/锁等待(低CPU)
  • 再看traces.txt主线程堆栈 → 精准定位卡住的方法
  • 结合锁信息和等待链 → 还原死锁或等待场景
  • 最后回查代码 → 修复
ANR日志分析流程 ANR日志分析流程 1. 系统检测到ANR 2. 解析 traces.txt 主线程堆栈 / 锁信息 / 等待链 3. 解析 logcat CPU usage / faults / Reason 4. 判断CPU占用 高CPU(>50%) 低CPU(<10%) 查死循环 / 频繁GC 检查 native堆栈 / 日志CPU占比 查IO / 锁 / 睡眠 检查 Blocked/Waiting 堆栈 5. 定位代码 ← 修复 实际排查中,traces.txt 和 logcat 要反复交叉验证,不要只看一半就下结论。

最后扔个终极技巧:如果你遇到的是「伪ANR」——用户觉得卡,但系统没报ANR日志。这时候 traceview 或 systrace 比traces.txt更管用。当然,那是另一个话题了。