ANR 概述:什么是 ANR?ANR 有哪些类型?
聊到 Android 性能监控,ANR 永远是绕不开的坎。我做了这么多年多媒体开发,被 ANR 坑过的次数两只手数不过来。什么叫 ANR?说白了就是 Application Not Responding —— 你的 App 没反应了,系统实在等不下去,弹了个对话框问用户是要继续等还是关掉。
为什么会这样?系统给每个 App 的 UI 线程设了超时闹钟。如果你在规定时间内没处理完该做的事,系统就会判定你“死机”了。这个超时时间因组件而异,下面我会详细说。
ANR 的核心原因
简单理解:主线程被阻塞了。主线程既要处理触摸事件、绘制界面,又要响应系统回调。一旦它卡住,整个 App 就僵住了。我印象很深的一个项目,某个短视频播放页面经常 ANR,查到最后发现是在主线程里做视频帧的硬解回调处理——那当然会超时。
| 触发场景 | 典型超时时间 |
|---|---|
| 输入事件(按键、触摸) | 5 秒 |
| 服务(Service) | 20 秒 |
| 广播(BroadcastReceiver) | 10 秒 |
| ContentProvider | 10 秒(进程启动时) |
adb shell dumpsys activity processes 查看系统实际配置。
ANR 的四大类型
系统按照触发来源,把 ANR 分成四种。每种的处理思路差别挺大,咱们逐个看。
1. 输入事件 ANR(Input ANR)
最常见,也最容易被用户感知。当系统派发 KeyEvent 或 MotionEvent 到 App 主线程后,如果 5 秒内没有被处理完,就会触发。你想想看,用户戳了屏幕,App 半天没反应,换谁都想关掉它。
我在项目中遇到过好几次:某个自定义 View 的 onTouchEvent 里直接做了耗时网络请求,结果线上疯狂报 ANR。解决办法很简单——把网络操作扔到子线程里。
2. 服务 ANR(Service ANR)
Service 的生命周期方法(onCreate、onStartCommand、onBind 等)必须在 20 秒内返回。否则系统会认为该 Service 已死。这里有个容易踩的坑:如果你在 Service 里直接做耗时初始化,比如加载大模型文件、解压资源,很容易超时。
onCreate 里写了一个文件解压操作,线上 ANR 率飙到 8%。后来改用 IntentService 或者把耗时操作放到 AsyncTask 里,问题就解决了。
3. 广播 ANR(Broadcast ANR)
广播接收器的 onReceive 方法必须在 10 秒内执行完。注意:这个方法是运行在主线程的。我在早期做多媒体开发时,经常在广播里更新 UI 或者写数据库,一不留神就超时。
而且广播 ANR 有个特点:如果你用 goAsync() 异步处理,记得在回调里调用 finish(),否则超时依然会触发。嗯,这块细节容易被忽略。
4. ContentProvider ANR
这种相对少见,主要发生在 ContentProvider 进程启动时。如果 ContentProvider 的 onCreate 在 10 秒内没有完成,系统就会 ANR。另外,当 Binder 线程池满了导致跨进程调用超时,也可能引发类似 ANR 的行为。
我个人习惯是在 ContentProvider 里只做轻量初始化,比如注册观察者、读配置表,绝不在里面做数据库全量迁移。
ANR 的类型关系图
下面这张图把 ANR 的四种类型、它们的触发时机和常见原因画在一起。你可以快速对照排查方向。
怎么发现 ANR?
我们做性能监控,首先要能快速识别 ANR。除了用户直接看到的“应用无响应”对话框,还有几个常用手段:
- 查看 /data/anr/traces.txt —— 这是系统生成的 ANR 堆栈文件,里面记录了主线程当时的调用栈。
- 使用 adb logcat -b events —— 搜索
am_anr关键字,能看到 ANR 发生的精确时间与进程信息。 - 接入监控平台(如 xCrash、Matrix 等)—— 可以自动捕获 ANR 并上传堆栈。
onTouchEvent 和 onReceive 入口加 Log.d,打印时间戳。一旦发现两次时间戳间隔超过阈值,基本就能判断发生了 ANR。这种方式虽然原始,但在调试早期很管用。
总结一下
ANR 不是什么神秘的东西。它就是系统对主线程响应能力的一次“体检”。四种类型——输入事件、服务、广播、ContentProvider——各有不同的超时阈值和触发场景。你只要记住一条核心原则:不要在主线程做任何可能超过几百毫秒的事情,就能避开绝大部分 ANR。