ANR日志获取与基本分析方法

分析ANR,第一步就是拿到日志。说起来简单,但真到实战的时候,不少人会卡在这一步——要么不知道怎么导出,要么导出的文件不对。我个人习惯先确认日志是否完整,再开始分析。本章我就带你走一遍日志获取的完整路径,还有最基本的分析方法。

ANR日志获取与分析核心流程 ① ANR触发 • 主线程卡顿超过阈值(5s/10s/20s) • 系统自动生成 traces 日志 ② 日志获取 • 手机直接导出 /data/anr/ 目录 • adb pull / bugreport 命令 ③ 核心文件 • traces.txt 记录线程堆栈 • events.log 记录事件时间线 ④ 分析方法 • 检查主线程状态(WAIT/BLOCKED) • 分析锁等待与调用链追溯 ⑤ 根因定位 • 定位到具体代码行 • 制定并验证修复方案 💡 核心思路 掌握日志获取是ANR分析的起点。优先检查traces.txt中的主线程堆栈, 结合events.log确认时间线,能快速缩小排查范围。

一、ANR日志的三种获取方式

不同场景下,获取日志的方式不太一样。我一般按优先级来:能用adb就用adb,手机在身边就直接导出,线上问题就靠bugreport。下面这三种方式,基本覆盖了所有情况。

方式1:直接从手机导出

适用场景:手机在手,且已root或具有system权限。

# ANR日志默认路径
/data/anr/traces.txt

# 使用文件管理器或Android Studio的Device File Explorer导出

需要注意的是,Android 11+ 对 /data/anr 目录做了权限限制,非root设备可能无法直接读取。这时可以用下面两种方式替代。

方式2:adb命令拉取

适用场景:开发调试阶段,电脑连接手机。

# 拉取traces文件
adb pull /data/anr/traces.txt .

# 或者直接拉取整个anr目录
adb pull /data/anr/ .

# 生成完整bugreport(包含所有日志)
adb bugreport bugreport.zip

我个人习惯直接用 adb bugreport,因为它会把traces.txt、events.log、system.log打包在一起,省得一个个找。

方式3:通过bugreport导出

适用场景:线上问题、用户反馈、或者设备无法直接adb连接。

# 在设备上生成bugreport(无需root)
adb bugreport bugreport.zip

# 解压后,ANR相关日志在
bugreport/FS/data/anr/traces.txt
我的小建议:每次分析ANR,我至少保留三份文件:traces.txt、events.log、system.log。少一个,有时候就很难还原现场。

二、三大核心日志文件

拿到日志压缩包,里面文件很多。但你不用全看,盯住下面这三个就行。它们各自记录了不同维度的信息,配合起来用效果最好。

文件名 核心内容 分析价值
traces.txt 所有线程的堆栈信息,主线程状态、锁等待关系 ⭐ 最高,直接定位卡顿点
events.log 系统事件的时间线,ANR触发前后的关键动作 ⭐ 确认ANR发生时机和上下文
system.log 系统级别的运行日志,包括CPU、内存、IO信息 辅助判断系统负载是否过高
注意:traces.txt 每次ANR只会保留最新的一份,旧日志会被覆盖。如果你需要保留现场,记得第一时间备份。

三、ANR基本分析四步法

拿到traces.txt之后,怎么下手?我总结了一套四步法,用熟了基本能覆盖90%的ANR场景。

  1. 第一步:定位时间点 —— 在traces.txt里搜索 "ANR in",找到ANR发生的具体时间和进程。
  2. 第二步:找到主线程 —— 搜索 "main" 或 "tid=1",看主线程当前状态和堆栈。
  3. 第三步:分析锁状态 —— 看主线程是否在等待某个锁,谁持有了这个锁。
  4. 第四步:追溯调用链 —— 从主线程堆栈的最底层往上读,找到业务代码的入口。

举个例子,下面这段traces片段,主线程卡在了 Waiting 状态:

----- pid 12345 at 2025-01-15 10:30:00 -----
Cmd line: com.example.app
"main" tid=1 Waiting
  at java.lang.Object.wait(Native Method)
  at java.lang.Object.wait(Object.java:423)
  at com.example.data.local.DatabaseHelper.query(DatabaseHelper.java:85)
  - waiting on <0x0c4d5e60> (a com.example.data.local.DatabaseLock)
  at com.example.ui.MainActivity.loadData(MainActivity.java:42)
  ...

看到了吗?主线程在 DatabaseHelper.query 里等一个锁。这时候我们去搜 0x0c4d5e60,就能找到谁拿着这个锁不放。我曾经遇到过一个案例,子线程在写数据库时没释放锁,导致主线程ANR。嗯,这种锁竞争问题,在traces里其实非常明显。

分析小技巧:我一般先用 grep -n "main" traces.txt 定位主线程行号,然后看上下10行,基本就能判断状态。如果看到 "BLOCKED" 或 "Waiting",九成九是锁竞争。

四、实战经验与避坑指南

分析ANR多了,你会发现有些坑是共性的。我说几个常见的,你碰到的时候可以少走弯路。

  • 误区1:只看traces.txt,不看events.log —— 有时候ANR是系统负载高导致的,traces里看不出明显问题,但events.log里会记录CPU使用率飙升。我建议两个文件对照着看。
  • 误区2:忽略子线程的锁持有 —— 主线程等锁,锁在子线程手里,但子线程也在等别的资源。这种"锁链"在traces里会连续出现,需要顺着 tid 一个个查。
  • 误区3:日志不全就放弃分析 —— 万一traces.txt被覆盖了,还可以从bugreport的 dumpstate_board.txt 里找到部分信息。别轻易放弃。
曾经我踩过的坑:有一次线上ANR频繁,我盯着traces看了半天,主线程明明是RUNNING状态,没有任何锁等待。后来才发现,是系统因为内存不足把进程杀了,重新创建时触发了ANR。所以,状态为RUNNING不代表没问题,要结合system.log看系统资源。

说到底,ANR分析就是一个"找线索"的过程。日志就是你的现场证据,拿到完整的日志,顺着主线程堆栈一路往下摸,总能找到真相。多练几次,你也会形成自己的分析直觉。