1、ANR概述:定义、触发机制、系统判断标准
ANR,全称 Application Not Responding,中文叫“应用无响应”。说白了,就是你的 App 卡住了,系统看不下去了,弹个对话框问用户“是继续等还是直接关掉?”——这玩意儿对用户体验的伤害,比我当年写 Java Swing 死循环还狠。
核心定义:当 Android 应用的主线程(UI线程)被阻塞超过一定时间,系统就会判定发生 ANR,并弹出强制关闭对话框。注意,这个“一定时间”不是固定的,不同场景阈值不同,我后面会细说。
触发机制——系统在等什么?
我刚开始做性能优化时,以为 ANR 只跟死循环有关。后来踩坑多了才发现,系统其实在等五类“关键响应”。任何一种超时,都会触发 ANR。
| 触发场景 | 超时阈值(Android 默认) | 系统等待的对象 |
|---|---|---|
| 输入事件分发(按键、触摸) | 5 秒 | 主线程处理完 InputEvent |
| 广播接收器(BroadcastReceiver) | 前台:10 秒 / 后台:60 秒 | onReceive 方法返回 |
| 服务(Service) | 前台:20 秒 / 后台:200 秒 | onCreate、onStartCommand 等关键生命周期完成 |
| 内容提供者(ContentProvider) | 10 秒 | onCreate 完成 |
| JobScheduler / 其他系统回调 | 各厂商有差异 | 回调函数返回 |
嗯,这张表我建议你收藏。因为每次排查 ANR 时,第一步就是看 logcat 里的“ANR in ... (xxx)”——那个 xxx 告诉你究竟是哪种类型超时。我曾经花了两小时查一个 Service ANR,结果发现是 onStartCommand 里做了网络请求,超时阈值 20 秒直接被系统判了死刑。
系统判断标准——它怎么知道你卡了?
系统不是靠猜的,它有套严格的“超时监控 + 信号采样”机制。我把它拆成三步:
- 埋桩计时:系统在关键入口(如 InputDispatcher、ActivityManagerService)启动一个定时器,同时记录“当前消息序列号”。
- 超时检测:定时器到期后,检查主线程是否处理了那条关键消息。如果还没处理完,说明主线程被阻塞了。
- 信号快照:系统向主线程发送 SIGQUIT 信号,强制 dump 所有线程的堆栈(就是 /data/anr/traces.txt)。然后根据堆栈内容判定责任方。
我的个人习惯:每次看到 ANR 问题,第一时间不是看代码,而是先拉 traces.txt。里面会明确指出“main”线程(主线程)当前在哪一行卡住。这个文件就是你破案的指纹。
你想想看,如果主线程正在做下面这些事,几乎必卡:
- 在主线程调用 Thread.sleep() 或 Object.wait()
- 在主线程执行网络请求(比如 OkHttp 同步调用)
- 在主线程执行硬盘 I/O(比如读写大文件、执行 Room 同步查询)
- 在主线程持有锁,而其他线程长时间不释放(死锁或锁竞争)
- 布局嵌套过深导致 measure/layout 耗时太长
// 典型错误示例 —— 我在项目里见过不止一次
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// !!!千万别在主线程做这件事 —— 5秒必ANR
Thread.sleep(6000);
}
}
我曾经踩过的坑:有一次我自信满满地认为 Room 查询在子线程跑就没事,结果忘了把 LiveData 的 observe 放在主线程,导致 Room 自动在主线程执行查询 —— 数据量 2 万条,直接 ANR。所以记住,Room 的 @Query 如果返回 LiveData,它默认在后台线程执行;但如果你用了
.allowMainThreadQueries(),那就等于把炸弹交给主线程。
知识结构:ANR 从触发到判定全流程
下面这张图把 ANR 的生命周期串了起来,我建议你对着它对照实际日志理解。
这张图的核心在于:系统监控器是独立的 Watchdog 线程,它不会因为你主线程卡住而停止计时。所以只要超时条件满足,SIGQUIT 一定会发——哪怕主线程只是临时卡了 0.5 秒后恢复正常,但一旦超时,ANR 已经生成了。
我的经验总结:三个判断维度
做 ANR 排查久了,我习惯用三个维度快速定位:
- 时间维度:看 logcat 中“ANR in”后面的时间戳,确认超时类型(输入事件 / 广播 / Service 等)。
- 堆栈维度:直接看 traces.txt 中主线程的堆栈,找到卡在哪个方法里。如果看到 Looper.loop() 并且 msg.target 是 null,那可能是消息队列被清空了——这种情况比较罕见,我遇到过一次,是第三方 SDK 的 bug。
- 资源维度:看系统整体负载,比如 CPU 是否被其他进程吃满,或者磁盘 I/O 饱和导致主线程 write 操作阻塞。这一点容易被忽略,我曾经因为一个后台进程频繁写日志拖垮了主线程的 SharedPreferences 提交。
一句话总结:ANR 就是主线程“超时未响应”,触发点来自五大场景,系统通过超时监控 + SIGQUIT 采样来判定。排查时先看 traces.txt 的主线程堆栈,再分析阻塞原因——大部分情况下都是我们在主线程做了不该做的事。