第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)进行系统化排查:

ANR 类型故障树 发生 ANR 输入事件超时 广播超时 服务超时 ContentProvider超时 主线程阻塞 事件队列积压 onReceive耗时 后台排队延迟 初始化耗时 死锁/等待 数据库大事务 跨进程IPC阻塞 判断规则 • trace 中主线程在 "InputMethodManager" / "ViewRootImpl" 中等待 → 输入事件超时 • trace 主线程卡在 BroadcastReceiver.onReceive() 或 Service.onCreate() 对应调用栈 → 对应超时 • 无明显主线程栈但 ContentProvider 有 "Provider key=xxx waiting" → ContentProvider 超时 故障树帮助快速识别 ANR 类型,结合排查表精确锁定原因

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
new Thread(() -> { /*耗时操作*/ }).start();
广播超时 改用 goAsync() 或 JobScheduler;不要在 onReceive 中做耗时任务
onReceive() { final PendingResult res = goAsync(); new Thread(() -> { /*处理*/ res.finish(); }).start(); }
服务超时 将初始化放到 onStartCommand 的分线程;使用 IntentService 或 WorkManager
onStartCommand() { new Thread(() -> { /*初始化*/ stopSelf(); }).start(); }
ContentProvider 超时 添加数据库索引;控制事务大小;使用 ContentProviderOperation 批量操作
db.execSQL("CREATE INDEX idx_name ON table(col)");
重要提示:修改后不得在主线程直接调用 Thread.sleep() 或执行大于 5ms 的磁盘 I/O。对于广播 goAsync(),务必在子线程结束后调用 finish(),否则广播仍会超时。

3.5 复测

  1. 使用 adb shell am start -W 或自动化测试触发相同场景;
  2. 连续操作 10 次,观察是否再次弹出 ANR 对话框;
  3. 检查 /data/anr/traces.txt 中是否仍有阻塞线程;
  4. 使用 dumpsys gfxinfo 验证渲染/响应时间是否达标。

3.6 预防

  • 对四种 ANR 类型分别配置 监控阈值(例如主线程卡顿超过 2s 记录堆栈);
  • 使用 StrictMode 在开发阶段检测主线程 disk read / network;
  • 在 CI 中加入 ANR 自动化测试用例,集成 blockcanary 或 Matrix;
  • 定期 review 广播、服务、Provider 的 onCreate / onReceive 代码,确保没有遗漏的同步操作。
预防核心:将四种超时的“红线”内化为团队编码规范,结合自动化检测工具在开发期拦截,避免上线后被用户报 ANR。