第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.txt
logcat -b events
am_anr
am_input_dispatch
获取窗口焦点变化
确认主线程是否在等锁
广播超时 logcat -b main -b system am_broadcast_delivery
am_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类型分类故障树 ANR (主线程阻塞) 输入派发超时 广播超时 服务超时 ContentProvider超时 未处理事件及 HTC通道阻塞 主线程等锁 (monitor) 主线程等IO (IPC/disk) onReceive 耗时操作 有序广播 排队阻塞 重复注册 死循环 onStartCommand 耗时操作 bindService 跨进程等锁 onDestroy 清理阻塞 query/insert 数据库锁 进程间 Binder call Provider 初始化 说明: 故障树从上至下判断:先确认日志中的ANR关键词 → 定位类型 → 再根据子叶子节点 检查主线程堆栈中的具体阻塞方法(等锁、等IO、等Binder回调、等文件读写)。 真实排查中,主线程堆栈的顶部几行通常直接暴露阻塞原因。 STW公式:ANR类型 = 系统超时器观测到主线程处理耗时 > 阈值 主线程 stop-the-world 时间 → 类型由事件源(Input/Receiver/Service/CP)决定
注意:一种常见陷阱是“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 计数降至零。