4. Crash与ANR的日志定位:Logcat、adb bugreport、tombstone文件

日志定位这事儿,说白了就是案发现场的取证。Crash 和 ANR 虽然表现不一样,但根源都得从日志里找线索。我个人习惯是拿到问题先翻日志,而不是直接看代码。因为你很难猜到用户是在什么奇怪场景下把应用搞崩的。

4.1 Logcat:最直接的命令行工具

Logcat 是 Android 调试桥(adb)的一部分。你把它当作一个实时输出流就行,所有应用和系统日志都会往这里写。

核心命令速查表

命令说明
adb logcat实时输出所有日志,刷屏杀手
adb logcat -v threadtime带线程ID和时间戳,我必备
adb logcat -b crash只看崩溃缓冲区,快速定位
adb logcat -b main -b system同时看应用和系统日志
adb logcat *:E只输出 Error 级别以上信息

抓取 Crash 日志:我一般先清空缓冲区,然后复现问题:

adb logcat -c                    # 清空旧日志
adb logcat -v threadtime > crash.log  # 开始记录
# 然后操作App复现崩溃
# Ctrl+C 停止

抓取 ANR 日志:ANR 发生时,Logcat 里会有类似这样的关键行:

I/ActivityManager: ANR in com.example.app (com.example.app/.MainActivity)
  PID: 12345
  Reason: Input dispatching timed out (Waiting because no window has focus...)  
  Load: 36.45 / 28.67 / 16.79
  CPU usage from 1234ms to 2345ms later:
    30% 12345/com.example.app: 20% user + 10% kernel / faults: 1243 minor 0 major
    10% 678/com.google.android.gms: 5% user + 5% kernel / faults: 0 minor 0 major

我的小技巧:搜关键词 "ANR in" 或 "FATAL EXCEPTION"。我曾经遇到过 ANR 日志里 CPU 占用很低,最后发现是主线程被死锁搞死了,Logcat 里没有明显的 CPU 飙高。

4.2 adb bugreport:大而全的快照

adb bugreport 会生成一个 zip 包,里面包含系统配置、进程状态、内存快照、Logcat 历史、Anr traces 等几乎所有信息。我把它叫做「全身体检报告」。

adb bugreport bugreport.zip

解压后,你会看到一堆文件和文件夹。重点关注这几个:

  • bugreport-日期.txt:主报告,全局概况
  • FS/data/anr/:ANR traces 文件夹
  • FS/data/tombstones/:native crash 墓碑文件
  • dumpstate_board.txt:板级信息,看电源、温度等
  • logcat.txt:完整的 logcat 历史

避坑提醒:我曾经因为 bugreport 文件太大(500MB+)被同事吐槽。建议在复现后立即抓取,不要长时间运行。另外,生成过程本身会消耗资源,如果系统已经处于 ANR 中,可能会卡住很久。

4.3 Tombstone 文件:Native Crash 的尸检报告

当 NDK 代码(C/C++)崩溃时,系统会生成一个 tombstones 文件。它记录了崩溃时的寄存器、堆栈、内存映射等信息。说白了就是 native 层的「死亡现场照片」。

文件位置:/data/tombstones/tombstone_XX(从00开始编号)

提取方式:

adb root
adb remount
adb pull /data/tombstones/ ./tombstones/

读懂关键字段:

Build fingerprint: 'google/...'
Revision: '0'
ABI: 'arm64'
Timestamp: 2025-01-15 10:23:45.123456789+0800

pid: 12345, tid: 12345, name: my_native_thread
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
    #00 pc 0000000000123456  /system/lib64/libc.so (strlen+12)
    #01 pc 0000000000789abc  /data/app/com.example.app/lib/arm64/libnative.so (Java_com_example_app_NativeBridge_doWork+48)
    #02 pc 0000000000789123  /data/app/com.example.app/lib/arm64/libnative.so (JNI_OnLoad+20)
  • signal 11:SIGSEGV 空指针解引用,最常见
  • fault addr 0x0:访问了地址0,就是空指针
  • 寄存器值:x0=0x0 证明了空指针来源
  • 堆栈回溯:从 libc 的 strlen 一路回推到我们自己 so 里的 doWork 函数

实战经验:遇到 signal 11 (SIGSEGV) 别慌。我排查过一个空指针,发现是 JNI 传进来的 jobject 被局部变量覆盖了。用 addr2line 工具把 pc 地址 转成源代码行号,精准定位。

4.4 知识结构总览

下面这张图帮你理清三种日志工具的关系和应用场景:

Crash & ANR 日志定位工具体系 Crash & ANR 日志定位工具体系 adb logcat 实时日志流 adb bugreport 全量系统快照 tombstone 文件 Native 崩溃残骸 Java Crash ANR Native Crash 系统信息 Logcat 中找 FATAL EXCEPTION / ANR in bugreport 解包后 FS/data/anr/ 或 traces.txt tombstone 中 signal + 堆栈 定位策略总结 Java Crash → logcat -b crash | ANR → bugreport 取 traces | Native Crash → tombstone + addr2line 左:实时诊断,快速复现 中:全面体检,事后分析 右:精确到寄存器级

4.5 实战对比:何时该用哪个?

场景推荐工具理由
开发中偶现 Java 空指针logcat -b crash实时看到崩溃堆栈,一行代码定位
用户反馈 ANR,但本地复现不了adb bugreport可以从 traces.txt 看到主线程卡在哪个锁上
NDK 代码闪退,无任何 Java 日志tombstone 文件只有 native 层能记录到寄存器级别信息
内存泄漏导致渐近式卡顿bugreport + hprof配合内存堆转储一起看
线上多机型兼容问题三方平台+logcat让用户通过系统 bugreport 功能上传

我曾经踩过的坑:在某次线上 ANR 排查中,我只看了 logcat 里的 "ANR in",却没看 traces.txt。结果发现主线程正在等待一个已经退出的 Binder 线程,而 logcat 里只记录了 "Input dispatching timed out",误导我找了半天输入事件。

4.6 一个综合示例:用三件套定位 ANR

假设用户反馈 App 在登录后经常白屏无响应。我会这么做:

  1. 第一步:让同事用他自己的手机复现,马上执行 adb bugreport。
  2. 第二步:解压后在 FS/data/anr/ 下找到 traces.txt。
  3. 第三步:搜索自己进程的 pid,看主线程堆栈。我见过主线程卡在 SocketInputStream.read() 上的,原因是网络请求没有超时。
  4. 第四步:对应查看 logcat 里 CPU 使用率,发现主线程 100% 不干活,但系统 CPU 空闲,锁分析没有问题,那就是 IO 阻塞了。
  5. 第五步:修复方案:给网络请求加个 5 秒超时,并且在子线程里发起请求。

你看,一个 ANR 问题,三样工具全用上了。没有它们,我只能靠猜。