1. ANR 基础概念:你屏幕卡死的时候发生了什么

大家做 Android 开发,肯定都见过那个「应用无响应」的弹窗。说白了,就是你的 App 在 UI 线程上干活太久,系统觉得它「死」了。我叫它 ANR——Application Not Responding。

我个人习惯把这个弹窗当成一个警报灯。它一亮,就说明你的应用在某个关键任务上超时了。这可不是小事,用户可能会直接杀进程走人。我曾在线上遇到过一款短视频 App,ANR 率高达 3%,用户留存哗哗往下掉,后来追根溯源就是快照重计算卡了主线程。

1.1 什么是 ANR

系统会为每个应用的主线程开启一个「超时监视器」。如果你在指定时间内没有处理完某个事件(比如触摸、广播、服务启动),系统就判定你无响应,然后弹出 ANR 对话框。

为什么会这样?你想想看,Android 是事件驱动模型。用户点一下屏幕,系统把事件放进 MessageQueue,主线程 Looper 一个个取出来执行。如果某个消息执行太久,后续的消息就要排队。系统等得不耐烦了,就会报警。

核心结论:ANR 本质是主线程阻塞,导致无法及时响应系统分发的输入事件或生命周期回调。

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。

小技巧:抓取 ANR 的 trace 文件时,注意看 main 线程的堆栈顶部。比如 android.os.MessageQueue.nativePollOnce 通常是空闲等待,如果卡在 View.onDraw 或 LayoutTransition 里,那基本就是重计算问题了。

1.4 一张图看懂 ANR 家族

我画了一张结构图,把 ANR 的类型、超时时间、触发场景串起来。你可以把它当作速查手册记在脑子里。

ANR 分类与超时机制概览 ANR(应用无响应) 输入事件 ANR 广播 ANR 服务 ANR 内容提供者 ANR 超时:5s 超时:10s 超时:20s 超时:10s 触摸/按键事件 5 秒内未处理 (最常见!) BroadcastReceiver onReceive 超时 (前台/后台有别) Service onCreate/Start等 20 秒未返回 ContentProvider onCreate 超时 (Android 4.2+) 核心机制:系统通过 AMS / InputDispatcher 启动超时定时器 主线程阻塞超过阈值 → 弹出 ANR 对话框 → 用户可选择等待或关闭 关键排查方向:主线程堆栈、CPU 使用率、Binder 调用、锁竞争
注意:超时时间在不同 Android 版本上可能会微调。比如 Android 12 开始对前台广播的超时限制更严格。我建议你在做兼容性测试时,以目标版本的 CTS 测试用例为准。别以为 10 秒就是铁律——有些定制 ROM 甚至会把时间缩短到 5 秒。

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 可以先按这个顺序来:

  1. 找 trace 文件:/data/anr/traces.txt 或 BugReport 中的 ANR traces 部分。这是最重要的证据。
  2. 看主线程堆栈:找到 main 线程的堆栈,看它卡在哪个方法上。是 onCreate?还是 onDraw?还是 Binder 调用等待?
  3. 看 CPU 负载:如果 CPU 使用率很高(比如 90%+),可能是死循环或大量计算;如果很低(接近 0%),可能是锁等待或休眠。
  4. 看 Binder 调用:主线程在等待 Binder 回复时容易假死,比如 BinderProxy.transact 耗时过长。
  5. 看锁争夺:主线程试图获取一个被其他线程持有的锁,会导致阻塞。常见于共享缓存、数据库操作。
我的建议:就算你还没学会看复杂堆栈,先学会抓 dumpsys input 也能帮助确认输入事件是否被挂起。命令是 adb shell dumpsys input,找 InputDispatcher 状态,看有没有 pending 的事件。

好了,这就是 ANR 最基础的知识。记住四个分类和它们的超时时间,以后遇到 ANR 先对号入座。下一部分我们会深入快照重计算的原理,搞清楚它究竟是怎么把主线程拖垮的。