第3章 获取ANR日志的标准化方法
3.1 现象识别:何时需要采集ANR日志
ANR 发生时,系统会在 /data/anr/ 目录下生成 traces.txt 文件,记录所有线程的堆栈快照。并非每次 ANR 都需要采集完整日志,应依据以下判断规则决定采集深度:
| 场景 | 现象特征 | 采集策略 |
|---|---|---|
| 偶现 ANR | 无固定复现路径,用户操作差异大 | 必采 traces.txt + logcat |
| 必现 ANR | 固定操作步骤即可触发 | 优先 Profiler 实时捕获 |
| 线上大面积 ANR | 版本更新后爆发,影响面广 | 立即触发 BugReport + 线上平台 |
| 低端设备 ANR | 仅在低内存/低性能设备出现 | 留痕采集 + 内存快照 |
采集红线:生产环境禁止直接执行 adb 命令。线上 ANR 必须依赖预埋的日志采集框架自动上报,不得通过远程连接设备拉取 traces.txt,否则会破坏现场且违反用户隐私协议。
3.2 数据采集:四种标准化方法
3.2.1 adb 命令拉取 traces.txt
适用于线下调试机或内测设备,是获取 ANR 现场最直接的手段。执行前先确认 traces 文件是否存在:
# 检查 ANR traces 文件是否存在
adb shell ls -l /data/anr/
# 拉取最新 traces.txt 到本地
adb pull /data/anr/traces.txt ./anr_logs/
# 如果 traces.txt 过大,只查看主线程堆栈
adb shell cat /data/anr/traces.txt | grep -A 30 "main"
最佳实践:拉取后立即执行
adb logcat -d > logcat.txt 保存 logcat,与 traces.txt 配对分析。traces.txt 仅记录堆栈快照,缺少时序日志时很难还原前置操作。
3.2.2 BugReport 全量日志
BugReport 包含 traces.txt、logcat、系统服务状态、内存信息和 CPU 负载,适用于需要综合分析系统环境的场景。采集命令:
# 生成 BugReport(耗时 1-3 分钟)
adb bugreport ./bugreport_output/
# 解压后关键文件路径
# bugreport-设备名-日期.zip
# ├── bugreport-设备名-日期.txt
# ├── FS/data/anr/traces.txt
# └── dumpstate_xxx.txt
BugReport 适合与竞品对比或系统级 ANR 排查,但文件体积常在 50 MB 以上,不适合批量上传。
3.2.3 Android Studio Profiler 实时捕获
在开发阶段,使用 Android Studio Profiler 的 CPU Profiler 或 Memory Profiler 实时观察线程状态,可以在 ANR 发生前捕获到主线程长时间阻塞的征兆。
- 适用时机:必现 ANR,且需要在 IDE 中实时观察 CPU 与线程调度。
- 操作步骤:连接设备 → 打开 Profiler → 复现 ANR → 冻结时间轴 → 导出 Call Chart。
- 优势:可视化展示方法调用链,定位到具体类与方法行号。
- 局限:Profiler 本身会带来 5%-15% 的性能开销,低端设备上可能改变 ANR 复现条件。
3.2.4 线上日志采集平台
线上 ANR 必须依赖自动化采集平台。主流方案对比如下:
| 平台 | 接入成本 | 日志丰富度 | 实时性 | 典型场景 |
|---|---|---|---|---|
| Firebase Crashlytics | 低(Gradle 插件) | 堆栈 + 自定义键值对 | 分钟级 | 中小团队快速上线 |
| 自建平台(ELK/日志网关) | 高(需搭建后端) | 全量日志 + 堆栈 + 埋点 | 准实时 | 大规模用户、定制化需求 |
| 友盟 / Bugly | 低 | 堆栈 + 设备信息 | 天级报表 | 国内 Android 生态 |
线上日志自建方案关键指标:
• 每条 ANR 日志大小控制在 64 KB 以内(含堆栈 + 最近 200 条 logcat)
• 上报频率不超过 1 次 / 分钟 / 设备,避免流量与存储爆炸
• 必须包含 elapsedRealtime 时间戳,用于对齐系统服务状态
• 每条 ANR 日志大小控制在 64 KB 以内(含堆栈 + 最近 200 条 logcat)
• 上报频率不超过 1 次 / 分钟 / 设备,避免流量与存储爆炸
• 必须包含 elapsedRealtime 时间戳,用于对齐系统服务状态
3.3 原因定位:从日志中提取关键线索
拿到 traces.txt 后,按 三看原则 快速定位:
- 看主线程状态:搜索
"main"或"tid=1",检查是否处于BLOCKED、WAITING、SLEEPING状态。 - 看锁竞争链:找到
waiting to lock与held by配对行,识别死锁或长时间持锁。 - 看 Binder 调用:搜索
Binder:793_1等线程,判断是否因系统服务响应超时导致 ANR。
// traces.txt 关键字段示例(ANR 主线程阻塞在锁等待)
"main" tid=1 BLOCKED
at com.example.app.MainActivity.onClick(MainActivity.java:85)
- waiting to lock <0x0a4b8c00> (a java.lang.Object)
at com.example.app.DataManager.loadData(DataManager.java:120)
- locked <0x0a4b8c00> (a java.lang.Object) <-- 持锁线程在此
3.4 修复动作:基于日志的优化方向
| 日志线索 | 常见根因 | 修复动作 |
|---|---|---|
| 主线程 BLOCKED + waiting to lock | 子线程持锁未释放 | 使用 synchronized 缩小锁范围,或改用 ReadWriteLock |
| 主线程执行 SQL / 网络 I/O | 在主线程做磁盘或网络操作 | 迁移到 AsyncTask / Coroutine(Dispatchers.IO) |
| Binder 调用长时间未返回 | 系统服务(AMS/WMS)负载过高 | 减少跨进程调用频率,增加本地缓存 |
| 主线程 CPU 密集计算 | 图片解码/JSON 解析在主线程 | 使用 WorkManager 或 RxJava 调度到后台线程 |
3.5 复测:验证修复有效性
修复后必须按 三级复测 流程确认 ANR 已消除:
- L1 单元复测:在修复分支上,用 Profiler 人工复现原操作路径,确认主线程不再出现 BLOCKED 状态。
- L2 压力复测:使用
adb shell am instrument或 Monkey 脚本连续执行目标操作 1000 次,观察是否触发 ANR。 - L3 线上灰度验证:发布至 5% 灰度用户,观察 Crashlytics/自建平台中 ANR 率是否下降至基线水平。
# 压力复测示例:对特定 Activity 执行 500 次启动
adb shell am instrument -w -e class com.example.app.MainActivityTest \
com.example.app.test/androidx.test.runner.AndroidJUnitRunner
3.6 预防:建立日志采集规范
从流程上避免 ANR 日志采集遗漏,需固化以下 四条规则:
- 发布前绑定采集配置:每次构建打包时,通过 Gradle 任务自动注入线上日志采集开关与采样率。
- 统一日志格式:所有 ANR 上报字段必须包含
thread_name、thread_state、lock_info、cpu_usage四个核心字段。 - 本地留痕兜底:在
Context.getCacheDir()下保留最近 3 次 ANR 的 traces 副本,防止网络上报失败后丢失现场。 - 定期巡检日志覆盖率:每周检查线上 ANR 日志采集率(上报 ANR 数 / 实际 ANR 数),低于 90% 需排查采集管道。
采集率计算公式: 采集率 = (上报到平台的 ANR 数量) / (Android 系统记录的 ANR 数量) × 100%。系统记录可通过
adb shell dumpsys activity processes | grep "anr" 查看。