认识 ANR:它到底是什么?
ANR,全称 Application Not Responding,中文叫“应用无响应”。说白了,就是你的 App 卡住了,系统觉得它“死了”,于是弹个对话框问用户:是等还是关?
我记得刚入行时,第一次看到 ANR 日志,满屏的堆栈信息,我完全懵了。后来才明白——其实 ANR 的本质就一句话:主线程被阻塞太久,系统看不下去了。
触发条件:系统是怎么判定的?
Android 系统会给每个组件设定一个“容忍时间”。一旦超时,系统就会认为这个组件“失联”,然后果断触发 ANR。具体阈值因版本、厂商、组件类型而异,我整理了一张表,方便你对照。
| 组件类型 | 超时阈值(常见值) | 说明 |
|---|---|---|
| 输入事件(按键/触摸) | 5 秒 | 主线程没在规定时间内处理完输入事件 |
| BroadcastReceiver(前台) | 10 秒 | onReceive 方法执行超时 |
| BroadcastReceiver(后台) | 60 秒 | Android 8.0+ 后台广播限制,但仍有超时 |
| Service(前台) | 20 秒 | onStartCommand / onBind 等生命周期方法执行太久 |
| Service(后台) | 200 秒 | 某些厂商或低内存情况可能更短 |
| ContentProvider | 10 秒 | query / insert / update / delete 操作超时 |
⚠️ 注意:不同 Android 版本、厂商 ROM 可能修改超时时间。比如某些国产手机把广播超时缩短到 5 秒。嗯,这点我踩过坑,后面会细讲。
三种最常见的 ANR 类型
做动态交付场景,我碰到的 ANR 基本跑不出这三类。我们逐个来看。
1. 输入无响应(Input Dispatching Timeout)
用户点了一下屏幕,系统把事件发到主线程,但主线程正忙着做别的事——比如在跑一个很重的 JSON 解析,或者死锁了。5 秒后,系统弹窗:“XXX 未响应”。
典型案例:列表滑动时,主线程执行网络请求 + 图片解码,导致触摸事件被阻塞。我曾经排查过一个案子,用户反馈“点哪里都没反应”,最后发现是某个第三方 SDK 在 UI 线程做了磁盘写操作。
Input dispatching timed out。后面会跟一个“Waiting because no window has focus”之类的信息,能帮你快速定位是哪个窗口卡住了。
2. 广播超时(Broadcast Timeout)
广播接收器的 onReceive() 方法默认在主线程执行。如果你在里面做了耗时操作——比如数据库查询、网络请求——系统会在前台 10 秒(或后台 60 秒)后爆 ANR。
我遇到过最离谱的一次:运营同学在动态配置下发的广播里,用循环往 SharedPreferences 里写 5000 个 key-value。没错,直接在主线程写了 10 多秒……结果线上 ANR 率飙升到 3%。
onReceive() 方法返回后,系统就会认为广播处理完毕。如果在里面 post 了 Runnable 到主线程,那其实是“异步”的,但主线程仍然可能被后续任务阻塞。我建议直接用 goAsync() 并启动子线程处理。
// 错误做法:直接在 onReceive 里做耗时操作
@Override
public void onReceive(Context context, Intent intent) {
// 如果这里执行超过10秒,就会ANR
heavyDatabaseOp(); // ❌
}
// 正确做法:使用 goAsync 异步处理
@Override
public void onReceive(Context context, Intent intent) {
final PendingResult result = goAsync();
new Thread(() -> {
heavyDatabaseOp();
result.finish(); // 必须调用,否则系统会一直等待
}).start();
}
3. 服务超时(Service Timeout)
Service 的生命周期方法(onCreate、onStartCommand、onBind、onDestroy)默认跑在主线程。一旦执行时间超过 20 秒(前台)或 200 秒(后台),系统就炸了。
说实话,前台服务 20 秒的超时已经很长了,但如果你在里面做网络请求的同步等待,或者读写大文件,依然会触发。我有个同事曾经在 onStartCommand 里写了个 Thread.sleep(15_000) 来模拟长任务……结果可想而知。
ANR 的核心知识框架
下面这张图,把 ANR 的定义、触发条件、常见类型、排查入口串在了一起。我自己每次排查 ANR 时,脑子里都会过一遍这个流程。
排查 ANR 的两个核心日志文件
遇到 ANR 别慌。我个人习惯先看两个地方:
- Logcat 主日志:搜索 "ANR in" 或 "forced to unresponsive",能直接看到是哪个进程、什么组件超时。
- /data/anr/traces.txt:系统在 ANR 发生时 dump 出所有线程的堆栈。重点关注主线程("main")的堆栈,它停在哪里,哪里就是罪魁祸首。
📌 重要指标:线上 ANR 率是性能监控的核心指标之一。动态交付场景中,由于配置下发、热修复、资源加载等操作频繁,主线程很容易被打断。我建议把 ANR 阈值设为 0.1% 以下——超过这个值,就该着手优化了。