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 知识结构总览
下面这张图帮你理清三种日志工具的关系和应用场景:
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 在登录后经常白屏无响应。我会这么做:
- 第一步:让同事用他自己的手机复现,马上执行
adb bugreport。 - 第二步:解压后在
FS/data/anr/下找到traces.txt。 - 第三步:搜索自己进程的 pid,看主线程堆栈。我见过主线程卡在
SocketInputStream.read()上的,原因是网络请求没有超时。 - 第四步:对应查看 logcat 里 CPU 使用率,发现主线程 100% 不干活,但系统 CPU 空闲,锁分析没有问题,那就是 IO 阻塞了。
- 第五步:修复方案:给网络请求加个 5 秒超时,并且在子线程里发起请求。
你看,一个 ANR 问题,三样工具全用上了。没有它们,我只能靠猜。