1. ANR 基础概念:你屏幕卡死的时候发生了什么
大家做 Android 开发,肯定都见过那个「应用无响应」的弹窗。说白了,就是你的 App 在 UI 线程上干活太久,系统觉得它「死」了。我叫它 ANR——Application Not Responding。
我个人习惯把这个弹窗当成一个警报灯。它一亮,就说明你的应用在某个关键任务上超时了。这可不是小事,用户可能会直接杀进程走人。我曾在线上遇到过一款短视频 App,ANR 率高达 3%,用户留存哗哗往下掉,后来追根溯源就是快照重计算卡了主线程。
1.1 什么是 ANR
系统会为每个应用的主线程开启一个「超时监视器」。如果你在指定时间内没有处理完某个事件(比如触摸、广播、服务启动),系统就判定你无响应,然后弹出 ANR 对话框。
为什么会这样?你想想看,Android 是事件驱动模型。用户点一下屏幕,系统把事件放进 MessageQueue,主线程 Looper 一个个取出来执行。如果某个消息执行太久,后续的消息就要排队。系统等得不耐烦了,就会报警。
1.2 ANR 的四大分类
系统对不同场景的超时阈值是不同的。我把它们列成一张表,方便大家对照:
| 类型 | 触发场景 | 超时时间 | 我遇到过的典型案例 |
|---|---|---|---|
| 输入事件 ANR | 触摸、按键等事件 5 秒内未处理完毕 | 5s | 列表滑动时触发了快照重计算,卡住了触摸事件。 |
| 广播 ANR | 非前台广播 10 秒、前台广播 10 秒(注:不同版本有差异,Android 14 前台广播可到 10s) | 10s / 10s | 某个 BroadcastReceiver 中做了耗时 JSON 解析,直接超时。 |
| 服务 ANR | Service 的 onCreate/onStartCommand/onBind 等 20 秒未返回 | 20s | 服务启动时加载了 200MB 的配置文件,超时被系统杀掉。 |
| 内容提供者 ANR | ContentProvider 的 onCreate 执行超过 10 秒(Android 4.2+) | 10s | ContentProvider 初始化时做数据库升级,表数据量太大导致超时。 |
嗯,这里要注意:输入事件 ANR 是最常见的。很多用户反馈的「点了没反应」,其实都是这类。我经历的一个项目,日志里输入事件 ANR 占了 80%,大部分问题出自快照重计算——主线程在做布局快照的 diff 计算,根本没空处理触摸。
1.3 ANR 的超时机制细节
系统内部到底怎么监视超时?我简单画了个流程,你们心里有个数就行。
对于输入事件,系统通过 InputDispatcher 给应用进程发送输入事件时,会启动一个 5 秒的定时器。如果 5 秒内应用没有调用 finishInputEvent 告知处理完毕,InputDispatcher 就会触发 ANR。
对于广播,AMS(ActivityManagerService)在派发广播时会设置一个超时消息。如果广播处理完后没有通知 AMS,超时消息就会执行,触发 ANR。
服务和内容提供者也是类似——AMS 记录启动时刻,若超时未完成启动,直接判定 ANR。
main 线程的堆栈顶部。比如 android.os.MessageQueue.nativePollOnce 通常是空闲等待,如果卡在 View.onDraw 或 LayoutTransition 里,那基本就是重计算问题了。
1.4 一张图看懂 ANR 家族
我画了一张结构图,把 ANR 的类型、超时时间、触发场景串起来。你可以把它当作速查手册记在脑子里。
1.5 一个我踩过的坑:快照重计算引发的输入 ANR
我曾经维护过一个即时通讯 App,用户反馈在聊天列表长按删除时经常卡死。抓到的 ANR trace 显示主线程卡在 RecyclerView.onMeasure 里面,一层层调用到了 ViewGroup.dispatchDraw → Canvas.saveLayer → 快照重计算。
你想想看,长按删除会触发列表项的布局动画,系统要对所有可见项做布局快照(saved snapshot)。如果列表项布局复杂(包含大量嵌套 ViewGroup 和自定义绘制),这个快照计算可能耗时超过 5 秒。系统等不及就弹 ANR 了。
后来我定位到是一个自定义 View 在 onDraw 中频繁调用了 clipRect 和 saveLayer,导致每次重绘都会生成新的快照。优化方案很简单——用 clipRect 替代 saveLayer,或者缓存快照结果。改完后 ANR 率从 3% 降到了 0.1% 以下。
1.6 你该怎么开始排查 ANR?
我把基础检查步骤列成一个清单,遇到 ANR 可以先按这个顺序来:
- 找 trace 文件:
/data/anr/traces.txt或 BugReport 中的ANR traces部分。这是最重要的证据。 - 看主线程堆栈:找到
main线程的堆栈,看它卡在哪个方法上。是onCreate?还是onDraw?还是 Binder 调用等待? - 看 CPU 负载:如果 CPU 使用率很高(比如 90%+),可能是死循环或大量计算;如果很低(接近 0%),可能是锁等待或休眠。
- 看 Binder 调用:主线程在等待 Binder 回复时容易假死,比如
BinderProxy.transact耗时过长。 - 看锁争夺:主线程试图获取一个被其他线程持有的锁,会导致阻塞。常见于共享缓存、数据库操作。
dumpsys input 也能帮助确认输入事件是否被挂起。命令是 adb shell dumpsys input,找 InputDispatcher 状态,看有没有 pending 的事件。
好了,这就是 ANR 最基础的知识。记住四个分类和它们的超时时间,以后遇到 ANR 先对号入座。下一部分我们会深入快照重计算的原理,搞清楚它究竟是怎么把主线程拖垮的。