第3章 常见ANR类型分类及现象特征
Android ANR 按触发机制可分为四类:输入事件超时、广播超时、服务超时、ContentProvider 超时。每种类型的超时阈值、触发场景和日志特征都不同,本章按“现象识别 → 数据采集 → 原因定位 → 修复动作 → 复测 → 预防”逐一拆解。
3.1 现象识别
| ANR 类型 | 默认超时阈值 | 触发场景 | 典型现象 |
|---|---|---|---|
| 输入事件超时 | 5s(前台) | 事件队列未在规定时间内分发结束 | 应用无响应,用户触摸或按键无效 |
| 广播超时 | 10s(前台) 60s(后台) |
BroadcastReceiver.onReceive() 执行超时 | 应用可能显示“无响应”,日志中包含 Broadcast of Intent |
| 服务超时 | 20s(前台) 200s(后台) |
Service.onCreate() / onStartCommand() 执行超时 | 服务未启动完成,系统杀掉进程 |
| ContentProvider 超时 | 10s(查询/更新) | ContentProvider 的 query / insert / update / delete 耗时过长 | 调用方应用 ANR,Provider 所在进程可能被系统杀死 |
经验规则:输入事件超时最易复现(通常由主线程阻塞引起),广播超时多与后台任务未控制有关,服务超时常见于耗时初始化,ContentProvider 超时常由数据库大事务或网络请求造成。
3.2 数据采集
当发生 ANR 时,系统会在 /data/anr/traces.txt 文件中保存当时的线程堆栈。同时 logcat 中会输出包含 ANR in 关键字的日志行。
3.2.1 采集命令
# 获取 ANR trace 文件(需要 root 或 adb 高权限)
adb shell cat /data/anr/traces.txt > traces.txt
# 抓取实时 logcat 并过滤 ANR 日志
adb logcat -b main -b system | grep -i "ANR" > anr_log.txt
# 同时获取应用进程的详细日志(替换为实际包名)
adb logcat -b main -b system --pid=$(adb shell pidof com.example.app) > app_log.txt
3.2.2 日志关键字段
# 输入事件超时日志示例
ANR in com.example.app (com.example.app/.MainActivity)
PID: 12345
Reason: Input dispatching timed out (Waiting to send key event because the focused window has not finished processing all input events that were previously delivered to it.)
CPU usage: ...
# 广播超时日志示例
ANR in com.example.app
PID: 12346
Reason: Broadcast of Intent { act=com.example.ACTION_SLOW }
Load: 2.31 / 3.12 / 2.87
...
采集要点:优先保留 ANR 触发前后的
CPU usage 块、Load 值、各线程堆栈摘要,以及 waiting on 或 blocked on 关键字。
3.3 原因定位
使用以下故障树(Fault Tree)进行系统化排查:
3.3.1 排查表
| ANR 类型 | Trace 关键词 | CPU 使用率特征 | 常见第三方原因 |
|---|---|---|---|
| 输入事件超时 | Input dispatching timed out |
主线程几乎 100%,其余线程空闲 | View 过度绘制、同步网络请求主线程 |
| 广播超时 | Broadcast of Intent |
广播接收器线程(或主线程)高 CPU | 第三方 SDK 在 onReceive 中执行文件读写 |
| 服务超时 | Timeout executing service |
服务所在进程 CPU 高,可能伴随 GC 频繁 | 服务初始化时加载大模型或数据库 |
| ContentProvider 超时 | Provider returned from query 或 timeout |
Provider 进程 CPU 高,调用方线程等待 | 房间数据库大事务未加索引、IO 竞争 |
3.4 修复动作
| 类型 | 典型修复 | 代码示例(简化) |
|---|---|---|
| 输入事件超时 | 将耗时操作移到子线程;减少布局层级;使用 AsyncTask 或 HandlerThread |
|
| 广播超时 | 改用 goAsync() 或 JobScheduler;不要在 onReceive 中做耗时任务 |
|
| 服务超时 | 将初始化放到 onStartCommand 的分线程;使用 IntentService 或 WorkManager |
|
| ContentProvider 超时 | 添加数据库索引;控制事务大小;使用 ContentProviderOperation 批量操作 |
|
重要提示:修改后不得在主线程直接调用
Thread.sleep() 或执行大于 5ms 的磁盘 I/O。对于广播 goAsync(),务必在子线程结束后调用 finish(),否则广播仍会超时。
3.5 复测
- 使用
adb shell am start -W或自动化测试触发相同场景; - 连续操作 10 次,观察是否再次弹出 ANR 对话框;
- 检查
/data/anr/traces.txt中是否仍有阻塞线程; - 使用
dumpsys gfxinfo验证渲染/响应时间是否达标。
3.6 预防
- 对四种 ANR 类型分别配置 监控阈值(例如主线程卡顿超过 2s 记录堆栈);
- 使用 StrictMode 在开发阶段检测主线程 disk read / network;
- 在 CI 中加入 ANR 自动化测试用例,集成 blockcanary 或 Matrix;
- 定期 review 广播、服务、Provider 的
onCreate/onReceive代码,确保没有遗漏的同步操作。
预防核心:将四种超时的“红线”内化为团队编码规范,结合自动化检测工具在开发期拦截,避免上线后被用户报 ANR。