ANR治理从入门到实战:主线程阻塞场景与解决方案 - 第1节
1.1 ANR 现象定义
ANR(Application Not Responding)是 Android 系统在检测到应用主线程(UI 线程)超时未处理输入事件或未完成关键操作时,触发的用户可见异常。系统会弹出 ANR 对话框,提示用户“应用无响应”,并提供“等待”或“关闭”选项。ANR 本质是主线程阻塞的强制中断信号。
| ANR 类型 | 超时阈值 | 触发场景 |
|---|---|---|
| 输入事件分发超时 | 5 秒 | 按键、触摸事件在 5 秒内未被分发并处理完成 |
| 广播接收器超时 | 前台广播 10 秒 / 后台广播 60 秒 | BroadcastReceiver 的 onReceive() 未在规定时间内返回 |
| 服务超时 | 前台服务 20 秒 / 后台服务 200 秒 | Service 的 onCreate()、onStartCommand()、onBind() 未在时限内完成 |
| ContentProvider 超时 | 10 秒 | ContentProvider 的 query()、insert()、update()、delete() 执行超时 |
| 输入法(IME)超时 | 5 秒 | 输入法框架绑定或输入会话启动超时 |
关键理解:超时时间是系统从“事件开始”到“主线程空闲”的完整时间。如果主线程连续执行多个阻塞操作(例如同步网络请求+文件写入+大量计算),即使每个操作小于 5 秒,累计超时仍会触发 ANR。
1.2 典型表现与用户感知
用户视角的典型表现
- 应用界面卡死,无法响应用户触摸、滑动、按键操作
- 屏幕闪烁或部分区域残留重绘(出现“飞转的等待圈”或“冻结画面”)
- 系统弹出 ANR 对话框,标题为“XX 应用无响应”,并提示“是否将其关闭?”
- 点击“等待”后,应用可能恢复,也可能继续卡死;点击“关闭”则进程被杀死
- 后台广播引发的 ANR 可能不弹出对话框,仅在系统日志中记录(用户无感知但影响后续行为)
用户感知的严重程度指标
| 感知等级 | 用户操作 | 结果 | 对用户体验的伤害 |
|---|---|---|---|
| 轻度 | 快速滑动列表卡顿超过 3 秒 | 掉帧但未触发 ANR | 不信任应用流畅性 |
| 中度 | 点击按钮后 5 秒内无反应并弹出 ANR 对话框 | 用户选择“等待”,应用暂时恢复 | 中断操作,用户可能强制关闭 |
| 重度 | 频繁出现 ANR 对话框,用户无法正常使用 | 用户选择“关闭”或系统自动杀死进程 | 用户卸载应用、差评、投诉 |
1.3 系统日志特征(ANR 故障排查入口)
当 ANR 发生时,系统会在 /data/anr/traces.txt 文件中记录主线程与所有线程的堆栈快照。同时,logcat 中会输出 ANR 相关日志。以下是两种关键日志特征。
1.3.1 logcat 中的 ANR 标志
// logcat 输出示例(摘自主线程超时场景)
2025-02-11 14:23:11.456 1234-5678/? I/ActivityManager: ANR in com.example.app (com.example.app/.MainActivity)
Reason: Input dispatching timed out (Waiting because no window has focus but there is a focused application that may eventually add a window when it finishes starting up.)
Load: 7.82 / 6.12 / 4.33
CPU usage: 4564ms user, 1230ms system, 89% total
• com.example.app (pid 12345): 78% user, 12% system
• system_server (pid 6789): 15% user, 5% system
• logd (pid 1234): 1% user, 0.5% system
... (top 5 CPU consuming threads)
• Thread 1 (main) state: WAIT
• Thread 3 (RenderThread) state: RUNNABLE
logcat 关键字段分析:
- Reason:直接说明超时类型(Input dispatching timed out / Broadcast of Intent ... timed out / Service ... timed out)
- Load:系统负载(1/5/15 分钟均值),高负载可能暗示系统资源紧张
- CPU usage:进程和系统线程的 CPU 占用百分比,用于判断是否因 CPU 饥饿导致阻塞
- Thread 1 (main) state:主线程当前状态(WAIT / BLOCKED / RUNNABLE)
1.3.2 traces.txt 中的主线程堆栈
// /data/anr/traces.txt 示例
----- pid 12345 at 2025-02-11 14:23:11 -----
Cmd line: com.example.app
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1 obj=0x7f2345a0
| sysTid=12345 nice=-10 cgrp=default sched=0/0 handle=0x7f123456
| state=S | schedstat=( 12000000 500000 20 ) utm=8 stm=4 core=3
| stack=0x7f100000-0x7f120000
java.lang.Thread.sleep(Native Method)
at android.os.SystemClock.sleep(SystemClock.java:120)
at com.example.app.MainActivity.onClick(MainActivity.java:45)
at android.view.View.performClick(View.java:7125)
at android.view.View$PerformClick.run(View.java:28541)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:246)
at android.app.ActivityThread.main(ActivityThread.java:8571)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
traces.txt 关键字段:
- Blocked / WAIT / RUNNABLE:线程状态直接指示阻塞类型
- stack 调用链:从最新调用到 Looper.loop,可以定位到应用代码中导致超时的具体方法(本例中为
MainActivity.onClick调用SystemClock.sleep) - 先于 ANR 发生时的所有线程堆栈:不仅主线程,其他线程的堆栈也有助于发现死锁或资源竞争
排查规则:先在 logcat 中定位 ANR 的 Reason 和主线程状态,再根据 traces.txt 中主线程堆栈的顶部调用找到业务代码。如果主线程状态为 “Blocked”,则关注等待的锁对象;如果为 “WAIT”,则关注 sleep 或 wait 的原因。
1.4 ANR 现象故障树
1.5 排查判断规则(快速定位 ANR 类型)
使用以下排查表,根据用户反馈和初步日志快速判断 ANR 场景类别:
| 用户反馈特征 | logcat Reason 关键字 | traces 主线程状态 | 大概率类型 |
|---|---|---|---|
| 点击后长时间无响应 | Input dispatching timed out | BLOCKED 或 RUNNABLE | 输入事件分发超时 |
| 切换页面后卡住 | Broadcast of Intent { ... } timed out | WAIT 或 BLOCKED | 广播接收器超时 |
| 后台推送后 ANR | Service ... timed out | BLOCKED 或 WAIT | 服务超时 |
| 操作数据库/ContentProvider 后卡死 | ContentProvider ... timed out | BLOCKED | ContentProvider 超时 |
| 输入法弹出时卡死 | InputMethod ... timed out | WAIT 或 BLOCKED | 输入法超时 |
注意:上述判断规则起始阶段。实际中可能出现混合超时(例如多个广播同时到来导致主线程排队)。建议始终同步查看
traces.txt 中所有线程的堆栈,以排除死锁和资源竞争。
1.6 本章小结(数据采集前置能力)
在进入“数据采集”章节之前,你需要掌握以下能力:
- 通过 logcat 获取 ANR 的 Reason 和 CPU 负载
- 从 traces.txt 获取主线程堆栈及所有线程状态
- 结合用户感知表现判断超时类型,缩小排查范围
- 理解 ANR 阈值的差异性(前台 vs 后台,输入 vs 广播)