一句话总结:
ANR 就是“应用卡死弹窗”,主线程被耗时操作堵住了,用户等不及就崩溃!解决核心:别让主线程干重活,耗时任务全扔子线程,再用 Handler/协程切回来更新 UI。
一、ANR 的本质:主线程 Looper 的“交通大拥堵”
要理解 ANR,必须先理解 Android 的主线程消息循环机制。主线程(UI 线程)内部有一个 Looper,它不断地从 MessageQueue(消息队列)中取出消息(如点击、绘制、生命周期回调)并执行。
ANR 的根本原因:队列中某个消息(Message)的执行时间过长(例如一个耗时的网络请求),导致后续的所有消息都被阻塞,队列发生“交通拥堵”。系统无法处理新的用户输入或屏幕绘制事件,从而表现为界面卡死。
二、ANR 的“裁判员”:系统如何发现并报告拥堵?
ANR 并不是由应用自己检测的,而是由系统服务(运行在 system_server 进程中)作为“裁判员”来监控和触发的。
1. 输入事件 ANR (Input ANR) - 最常见的场景
-
裁判员:
InputDispatcher线程。 -
监控机制:
- 当用户触摸屏幕时,硬件中断将事件传递给
InputReader线程,后者将其封装成输入事件。 InputReader将事件交给InputDispatcher线程,InputDispatcher负责将事件派发给目标应用的窗口。- 在派发前,
InputDispatcher会设置一个 5 秒的超时“闹钟”。 - 如果应用主线程因为正在处理其他耗时任务而未能及时消费掉这个输入事件(即
Looper没机会处理它),InputDispatcher的“闹钟”就会响起。 InputDispatcher会认为该应用无响应,并上报给ActivityManagerService(AMS),最终由 AMS 弹出 ANR 对话框。
- 当用户触摸屏幕时,硬件中断将事件传递给
2. Service 及 BroadcastReceiver ANR
-
裁判员:
ActivityManagerService(AMS)。 -
监控机制:
- BroadcastReceiver:AMS 通过 Binder 将广播消息发送给应用进程的
ActivityThread后,会在ActiveServices中为该广播设置一个 10 秒(前台应用)或 60 秒(后台应用)的超时。如果应用在规定时间内没有执行完onReceive方法,AMS 就会认定为 ANR。 - Service:与广播类似,AMS 在处理 Service 的生命周期(如
onCreate,onStartCommand)时也会设置一个超时,前台 Service 为 20 秒,后台 Service 则长达 200 秒。
- BroadcastReceiver:AMS 通过 Binder 将广播消息发送给应用进程的
三、ANR 的四大元凶与规避之道
所有 ANR 问题都可以归结为以下四类主线程阻塞,解决方案的核心都是将任务移出主线程。
| 元凶类型 | 具体场景 | 推荐解决方案 |
|---|---|---|
| 1. CPU 密集型 | 大量数据排序、复杂算法、JSON 解析、Bitmap 变换 | Kotlin 协程:使用 Dispatchers.Default 切换到计算密集型线程池。 |
| 2. I/O 阻塞 | 网络请求、数据库读写、文件操作、SharedPreferences 读写 | Kotlin 协程:使用 Dispatchers.IO 切换到 I/O 密集型线程池。结合 Room、Retrofit 等库的 suspend 支持。 |
| 3. 锁竞争 | 主线程等待一个被子线程持有的锁(synchronized) | 1. 优化锁的粒度,减少锁的持有时间。 2. 使用读写锁 ( ReentrantReadWriteLock) 等更高效的并发工具。3. 审查代码,避免主线程参与任何可能产生等待的锁逻辑。 |
| 4. 进程间通信 (IPC) 阻塞 | 主线程进行同步的 Binder 调用,而对端进程处理缓慢或发生死锁 | 1. 总是假定对端会很慢,将所有 Binder 调用放在子线程。2. 优先使用 oneway 关键字定义 AIDL 接口,使其成为异步调用。 |
四、从日志到内核:ANR 的现代化排查工具链
1. 开发期预防:StrictMode (严苛模式)
在 Application.onCreate 中开启,它能主动检测出主线程上的违规 I/O 和网络操作,并以日志或崩溃的形式报告,帮助你在开发阶段就扼杀 ANR 隐患。
2. 现场分析:/data/anr/traces.txt 文件
ANR 发生时,系统会生成此文件,记录下当时所有线程的堆栈信息。
-
分析步骤:
- 用
adb pull /data/anr/traces.txt导出文件。 - 在文件中搜索
Cmd line: com.your.package找到你的应用信息。 - 重点关注
"main"线程的堆栈,查看其状态(通常是WAITING或BLOCKED)以及它被卡住的具体代码行。 - 同时查看其他线程是否持有了主线程正在等待的锁。
- 用
3. 系统级透视:Perfetto (新一代 systrace)
对于由 I/O 争用、CPU 调度问题或复杂的 Binder 调用引起的疑难 ANR,traces.txt 可能不足以定位问题。
- 优势:Perfetto 可以抓取系统全局的 Trace 信息,让你清晰地看到在 ANR 发生前后,你的应用线程状态、CPU 核心的运行情况、内核的 I/O 事件等。
- 使用场景:当
traces.txt显示主线程BLOCKED在一个看起来无害的地方时,使用 Perfetto 可能会发现是内核正在忙于为其他进程处理 I/O,导致你的应用无法及时获得 CPU 时间片或 I/O 资源,这才是 ANR 的深层原因。