第2章:Android ANR触发机制深入解析
2.1 ANR是什么?其实就是“系统等不下去了”
ANR全称 Application Not Responding。说白了,就是系统给你发了个信号:“你的App主线程卡住了,我不能再等你了。” 然后弹出一个让人崩溃的对话框——「XXX 无响应」。
我自己刚做Android时,一看到这个框就慌。后来才明白,ANR不是Bug,是系统的一种自我保护机制。它告诉你某个任务超时了。
那系统到底等多久才算“等不下去”?来看下面的核心时间阈值:
| 发生场景 | 超时时长 | 谁在监控 |
|---|---|---|
| 输入事件(按键/触摸)无响应 | 5秒 | InputDispatcher |
| 广播接收器 onReceive 未执行完 | 前台10秒 / 后台60秒 | ActivityManagerService |
| Service 的 onCreate/onStartCommand 等生命周期 | 20秒 | ActivityManagerService |
| ContentProvider 发布超时 | 10秒 | ActivityManagerService |
重点: 5秒!这是最常见的ANR触发时间。只要主线程在5秒内没处理完输入事件,系统就弹出ANR。
2.2 ANR触发流程——三步走
系统是怎么一步步判定ANR的?我画了一张流程图,你一看就懂:
(SVG图插入位置,见下方内联SVG)
记忆技巧: 前台输入事件5秒、前台广播10秒、Service 20秒。记住这三个数字,面试常问。
2.3 系统到底在监控什么?
ANR检测依赖两个关键角色:
- InputDispatcher:专门盯着输入事件。从用户触摸到控件响应,如果5秒没动静,直接报ANR。
- ActivityManagerService(AMS):监控广播、服务、ContentProvider这些组件生命周期。
我记得有一次排查一个问题:一个后台广播超时60秒才触发ANR。很多人以为是Bug,其实这就是设计——后台广播给的时间长,因为系统认为后台任务可以慢慢跑。但别高兴太早,系统一旦判定ANR,直接dump所有线程堆栈到 /data/anr/traces.txt。
2.4 实战案例:一个简单的ANR复现
光说理论不够,咱们写个demo试试。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_block).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 在主线程上睡6秒 —— 超过5秒,必出ANR
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
跑起来,点一下按钮,等6秒。你会看到ANR弹窗。这时候去抓 traces.txt,会看到主线程卡在 Thread.sleep() 上。这就是最典型的ANR。
注意: 用 Thread.sleep() 模拟阻塞只是演示。生产环境里,真正的ANR元凶往往是:
- 数据库慢查询(比如一次查几千条记录)
- 网络请求同步调用
- 大量位图解码
- 锁竞争(死锁或者活锁)
- 数据库慢查询(比如一次查几千条记录)
- 网络请求同步调用
- 大量位图解码
- 锁竞争(死锁或者活锁)
2.5 不同组件超时差异一览
| 组件类型 | 超时值 | 超时后动作 | 关键区别 |
|---|---|---|---|
| Input事件 | 5秒 | 弹出ANR对话框 | 只有主线程处理时就计时 |
| BroadcastReceiver(前台) | 10秒 | 弹出ANR对话框 | 执行在onReceive中 |
| BroadcastReceiver(后台) | 60秒 | 弹出ANR对话框 | Android 8.0后后台广播限制更严 |
| Service | 20秒 | 弹出ANR对话框 | 只对关键生命周期方法生效 |
| ContentProvider | 10秒 | 弹出ANR对话框 | 发布超时,非查询超时 |
2.6 系统日志里怎么看ANR线索?
当ANR发生时,logcat会输出关键信息。我通常这样筛选:
// 快速定位ANR日志
adb logcat | grep -E "ANR|am_anr|InputDispatcher"
// 实际输出示例
ActivityManager: ANR in com.example.myapp (com.example.myapp/.MainActivity)
ActivityManager: PID: 12345
ActivityManager: Reason: Input dispatching timed out (Waiting to send key event because the focused window has not finished processing all of the input events...)
ActivityManager: Load: 2.15 / 1.82 / 1.67
CPU usage from 0ms to 5000ms later:
... 堆栈详情
这里有个坑:CPU负载高不一定代表ANR是因为CPU不够。有时候主线程光等锁,CPU很闲,也一样ANR。所以别只看load值,要看线程状态。
2.7 避坑指南:那些年我遇到的伪ANR
我曾经被一个低级错误坑过:用 SharedPreferences.apply() 大量写入,以为异步就没事。结果在低端机上,apply 会在主线程的写入队列里排队,一旦队列积压,主线程就被堵住了。嘿,这不就是ANR吗?
另外,不要在主线程做任何IO操作,包括写数据库、读写文件、加载大图。这些都是血泪教训。
核心原则: 主线程只做UI操作和轻量计算。所有耗时操作都扔到子线程或者使用协程/线程池。
2.8 小结:你该记住什么
- ANR是超时机制,不是Bug,是系统保护。
- 前台输入事件超时5秒,广播10/60秒,Service 20秒。
- 检测由InputDispatcher和AMS完成。
- 最重要的排查工具:
/data/anr/traces.txt+ logcat。 - 主线程不要阻塞,其他都是废话。