第2章 ANR类型分类:输入派发超时、广播超时、服务超时、ContentProvider超时
2.1 现象识别:四大ANR类型的表象与触发条件
不同ANR类型在用户感知、日志特征与系统触发机制上存在显著差异。正确定义“属于哪一类ANR”是后续数据采集与分析的前提。本节通过识别表格与故障树帮助工程师在现象出现时快速归类。
| ANR类型 | 典型用户现象 | 超时时间(主线程) | 系统触发对象 | 关键日志特征 |
|---|---|---|---|---|
| 输入派发超时 InputDispatch |
点击/触摸无响应,界面“卡死” | 5 秒 | 应用窗口(Window) | Input event dispatching timed out |
| 广播超时 BroadcastReceiver |
后台操作无反馈,但UI可能仍可操作 | 前台:10 秒 后台:60 秒 |
BroadcastReceiver | Broadcast of Intent ... timed out |
| 服务超时 Service |
Service绑定/启动回调无响应 | 20 秒(前台服务) 200 秒(后台服务) |
Service | Timeout executing service: |
| ContentProvider超时 ContentProvider |
跨进程数据查询/插入超时 | 10 秒 | ContentProvider | ContentProvider not responding |
提示:“前台”与“后台”判定影响广播超时阈值。若进程处于前台(用户可见),超时阈值为 10 秒;否则为 60 秒。这直接决定ANR是否被触发。
2.2 数据采集:针对每种类型的日志获取策略
在定位具体原因前,必须通过不同路径收集该类型ANR所需的证据。以下排查表按类型列出采集动作。
| ANR类型 | 核心日志文件 | 关键字段/命令 | 补充信息 |
|---|---|---|---|
| 输入派发超时 | /data/anr/traces.txtlogcat -b events |
am_anram_input_dispatch |
获取窗口焦点变化 确认主线程是否在等锁 |
| 广播超时 | logcat -b main -b system |
am_broadcast_deliveryam_anr |
广播接收器链长度 有序广播阻塞点 |
| 服务超时 | logcat -b system |
am_anr_service |
Service所在进程 前后台状态标记 |
| ContentProvider超时 | logcat -b main -b system |
am_cp_timeout |
Provider权威进程 数据库锁状态 |
判据规则:当收集到日志头出现
ANR in ... 时,立即检查该ANR的关键字后缀:InputDispatcher→ 输入派发超时BroadcastReceiver→ 广播超时Service→ 服务超时ContentProvider→ ContentProvider超时
2.3 原因定位:故障树与主线程阻塞分析
四种ANR类型的根源最终都会落到主线程停止执行消息循环上,但阻塞入口不同。故障树展示从类型到根因的拆解路径。
注意:一种常见陷阱是“ANR日志显示广播类型,但实际阻塞原因却是主线程等锁导致的输入事件超时”。系统按照首次触发超时的组件(Receiver)报告类型,但堆栈可能展示的是等锁(锁竞争来自其他线程)。此时应优先分析堆栈真实阻塞点,而非仅依赖ANR行头。
2.4 修复动作与复测:按类型给出整改方向
| ANR类型 | 典型根因 | 修复动作 | 复测手段 |
|---|---|---|---|
| 输入派发超时 | 主线程做同步网络请求、数据库写、等锁(locks) | 将耗时操作移至子线程;使用 HandlerThread、Coroutine、RxJava 等开辟异步通道;修复锁竞争(减少synchronized临界区) | 使用 systrace 录制60秒主线程调度;模拟弱网、低内存场景触发ANR |
| 广播超时 | onReceive 执行耗时非异步操作(弹窗、数据库事务、文件写) | onReceive 只做轻量级消息投递;耗时操作通过 goAsync() + 子线程处理;避免有序广播长链 | adb shell am broadcast -a action --receiver-foreground 强制前台广播;观察超时日志 |
| 服务超时 | Service.onStartCommand 在主线程执行网络/DB操作;Service绑定卡在Binder回调 | 使用 IntentService / JobIntentService 或在工作线程处理;Binder调用加超时保护 | 通过 adb shell am start-foreground-service 触发前台服务;监控 timeout 日志 |
| ContentProvider超时 | CP的 query/insert/update 因数据库锁、Binder IPC阻塞、初始化耗时导致超时 | 数据库操作异步化;使用 WAL 模式降低写锁;Binder 调用设置进程间超时(1000ms) | 压力测试:多线程并发读写同一Provider;监控 logcat 中 am_cp_timeout 事件 |
复测判断规则:修复后至少连续执行500次触发操作(如广播发送、按键注入),监测是否出现该类型ANR日志。若出现零次,且主线程堆栈不再出现之前的阻塞方法,则判定修复有效。
2.5 预防策略:从架构设计层面规避四类ANR
- 统一线程策略:全局禁止在主线程执行任何可能超过10ms的同步操作,使用代码检查工具(如 lint、thread check)强制约束。
- 广播防护:所有 BroadcastReceiver 内部强制检查代码执行时间,推荐 onReceive 只做
startService()/postToHandler()投递。 - 服务限流:前台服务启动后若超20秒未就绪,系统直接上报ANR。建议服务首次启动时使用单独线程完成初始化,而非直接在主线程 onCreate。
- ContentProvider 设计:Provider 支持跨进程并发访问时,必须使用
SQLiteOpenHelper的 WAL 模式;避免在onCreate/query中发起网络或磁盘全量扫描。 - 监控基线化:线上集成 ANR 监控 SDK,对四类 ANR 分别设定告警阈值(例如输入派发超时发生率低于0.1%,服务超时低于0.01%),持续追踪。
2.6 判据工具:快速判断ANR类型的主脚本
以下命令可一次性从 logcat 中提取出四类ANR及其关键信息,无需人工逐行过滤。
# 提取所有ANR通用事件
adb logcat -b events -v threadtime | grep -E "(am_anr|am_input_dispatch|am_broadcast_delivery|am_anr_service|am_cp_timeout)" > anr_events.txt
# 基于key分类抽取
grep "am_input_dispatch" anr_events.txt > input_anr.txt
grep "am_broadcast_delivery" anr_events.txt > broadcast_anr.txt
grep "am_anr_service" anr_events.txt > service_anr.txt
grep "am_cp_timeout" anr_events.txt > contentprovider_anr.txt
# 输出每个类型计数
wc -l input_anr.txt broadcast_anr.txt service_anr.txt contentprovider_anr.txt
实战案例:某ROM上线后出现大量广播ANR,使用脚本发现 broadcast_anr.txt 计数超过5000,远高于其他类型。进一步分析广播接收器链,发现健康检查广播有序传递到20个接收器,其中一个接收器的 onReceive 发起同步网络请求(AT+指令超时15秒),导致整条链阻塞超过60秒。修复:将该接收器的网络请求异步化,并限制有序广播链长度不超过5个。复测后broadcast_anr 计数降至零。