ANR 基础概念与产生原因
ANR 全称 Application Not Responding,中文叫“应用无响应”。说白了,就是你的 App 卡住了,系统看不下去了,直接弹个对话框问用户“继续等待还是关闭?”
我在项目中见过不少团队,一碰到 ANR 就慌了手脚,只知道抓日志重现场景。其实核心问题很简单:主线程干了不该它干的活。
ANR 的触发规则
系统判定 ANR 有一套硬性超时机制。不同类型的组件,超时时间不一样。我列个表,方便你对照排查:
| 组件类型 | 超时时间 | 常见场景 |
|---|---|---|
| Activity(输入事件) | 5 秒 | 按键、触摸没响应 |
| BroadcastReceiver | 10 秒(前台广播) 60 秒(后台广播) | onReceive 里做了耗时操作 |
| Service | 20 秒 | onStartCommand 或 onCreate 耗时 |
| ContentProvider | 10 秒 | query/insert 等操作卡住 |
ANR 产生的根本原因
我习惯把 ANR 原因归纳成四个大类。你想想看,一个 App 的主线程就像一条单车道公路,任何一辆车堵住,后面所有车都得等。
1. 主线程阻塞
这是最常见的原因。主线程做了太多事:网络请求、数据库读写、文件操作、复杂计算……
我曾经接手过一个项目,启动时在 onCreate 里直接调用了 BitmapFactory.decodeFile 加载一张 4K 壁纸,结果 5 秒 ANR 稳定复现。那把图片放到子线程后,问题就解了。
- 网络调用:
HttpURLConnection、OkHttp同步请求 - 磁盘 I/O:
SharedPreferences大量写入、SQLite长事务 - 锁竞争:主线程等子线程释放锁,比如
Wait、join - View 过度绘制:
onLayout、onDraw里做耗时运算
2. Binder 调用超时
主线程调用系统服务(如 ActivityManager、WindowManager)时,如果对方没及时回应,主线程就会被挂起。举个实际例子:
// 主线程调用 ContentResolver
getContentResolver().query(Uri.parse("content://xxx"), null, null, null, null);
// 如果 provider 响应慢,主线程等超过 200ms 即可能触发 ANR
嗯,这里要注意:系统对 Binder 事务有 200ms 的隐式限制,超过这个时间就可能被标记为“卡顿”。
3. 事件分发超时
当用户触摸屏幕或按下按键,InputDispatcher 会往主线程发消息。如果主线程 Looper 里的消息队列积压,5 秒没处理完上个事件,ANR 就来了。
你想想看,如果 onTouchEvent 里做了耗时操作(比如实时滤镜),手指一滑动,马上超时。我曾经见过一个照片编辑 App,滑动亮度滑块时,每次计算直方图用了 2 秒,结果滑动三次就 ANR。
4. 广播 / Service 超时
广播接收器默认在主线程执行 onReceive,你如果在里面做了网络请求或数据库写入,超时就是必然。
- 静态广播:onReceive 最多 10 秒
- 动态广播:同样受限于主线程消息队列
- Service:onStartCommand 20 秒内必须返回
goAsync() 并在子线程执行,同时持有 PendingResult 超时控制。但要注意及时调用 finish(),否则系统也会降级处理。
ANR 分类图谱
下面的 SVG 图总结了 ANR 的触发来源和核心原因,方便你系统化地记忆——我平时做性能排查时就照着这个思路来:
避坑指南
- 曾经我遇到一个 case:第三方 SDK 在
onCreate里用Handler.postDelayed做了个 6 秒后的操作,结果这个操作里直接更新 UI,但 Activity 已经进入后台 – 这不是 ANR,却导致系统误判。后来我们加了Lifecycle判断才解决。 - 还有一个坑:
SharedPreferences.commit()是同步写入磁盘,如果数据量大,主线程轻松卡几百毫秒。建议统一用apply()或换成 DataStore。 - 别忘了 Binder 线程池:当系统服务线程池耗尽时,主线程发 Binder 调用也会等。这时候
/proc/self/stat里看 sched 状态会发现大量 “D” (uninterruptible sleep)。
快速自检清单:
- 主线程有没有做网络请求?
- 有没有在 UI 线程读写数据库?
- 有没有在 onTouchEvent 里做复杂计算?
- 有没有使用同步锁并且可能死锁?
- BroadcastReceiver 的 onReceive 是否超过 10 秒?