1、ANR 概念与原理

ANR 全称是 Application Not Responding —— 应用无响应。说白了,就是你的 App 主线程被卡住了,系统等得不耐烦,直接弹个对话框问用户「要继续等还是关掉」。我最早接触这个概念是在做某个社交 App 的聊天列表优化时,用户滑动不快,但偶尔就会弹 ANR,当时还以为是机型问题……后来才发现根本原因是自己埋的坑。

嗯,要理解 ANR,先得知道系统在「等什么」以及「等多久」。Android 对不同类型的阻塞设了不同的超时阈值,超过这个时间,就会判定为 ANR。

触发场景

官方定义的三大触发场景,我整理成一张表,方便你对照着记:

场景类型 超时时间 触发条件
输入事件 5 秒 主线程未处理完 Input 事件(如触摸、按键)
广播 10 秒 BroadcastReceiver 的 onReceive() 未执行完毕
服务 20 秒 Service 的 onCreate() / onStartCommand() / onBind() 未执行完

这里有个细节:广播的 10 秒指的是前台广播。如果是后台广播,系统会放宽到 60 秒,但同样会触发 ANR。服务也一样,前台服务 20 秒超时,后台服务 200 秒。不过你记住核心阈值就行,实战中我们按最严格的来监控。

系统底层的检测机制

Android 是怎么知道你主线程卡住 5 秒的?靠的是「消息队列 + 监视器」这套组合拳。我拆成几步来讲:

  1. 架构层:每个应用的主线程背后是一个 Looper 循环。Looper 不断从 MessageQueue 里取消息,交给 Handler 处理。
  2. 埋点层:系统在 Input 事件派发前,会在 Looper 中插入一个「超时监视消息」。如果 5 秒内这个监视消息没有被取出(说明前面的消息执行太久),系统就判定超时。
  3. 上报层:AMS(Activity Manager Service)会收到超时通知,然后执行以下动作:暂停当前组件、生成 traces.txt、弹出 ANR 对话框。
  4. 日志层:Traces 文件会 dump 当前所有线程的堆栈,包括主线程在哪个方法卡住。这就是我们分析 ANR 的第一手证据。

注意: 我曾在某款直播 App 中遇到一个奇怪的 ANR:每次切后台回来就弹。后来发现是 BroadcastReceiver 里做了网络请求,而网络请求居然跑在主线程。广播的超时时钟在 onReceive() 开始前就已经启动了,你还没来得及切线程,时间就到了。

核心要点: ANR 的检测不是「实时监控」,而是「到点触发」。系统在每个组件启动前设置一个定时器,如果定时器到期时组件还没结束,就判定 ANR。

下面是整个检测流程的示意图,画清楚系统做了哪些事:

ANR 检测流程 输入事件 5s 广播 10s 服务 20s InputManager AMS WatchDog 超时判断:定时器到期 生成 traces.txt 弹 ANR 对话框 杀掉进程 关键点 一切都围绕主线程的 Looper 消息循环 超时之前系统不干预,超时后立刻 dump 堆栈

底层实现细节

说到 WatchDog,我记得早期 Android 版本里 SystemServer 内部有个专门的线程叫 Watchdog,它会定期检查各个服务是否还活着,如果发现主线程卡死超过阈值,就直接杀进程。现在 Android 把检测逻辑做得更分散了:

  • Input 事件:由 InputDispatcher 的 input event timeout 机制触发,超时后发消息给 AMS。
  • 广播:AMS 在广播分发时注册一个 BroadcastQueue 中的超时 Alarm,到点未完成就触发 ANR。
  • 服务:ActiveServices 类中维护一个 Service 超时列表,每次服务启动时设置一个延迟 Runnable。

说白了,这些都是基于系统 Handler 的延迟消息。你想想看,既然系统能用 Handler 来检测超时,那我们的监控不也可以模仿一套类似的机制吗?没错,后面章节讲 ANR 监控方案时,我会专门说怎么自己写一个轻量的检测器。

一个小经验: 我不建议你把保活或监控逻辑放在主线程,因为那本身就是 ANR 的源头。我习惯在另一个进程里做监控,比如用 Service 或者 JobScheduler,这样就算主进程卡死,监控进程还能上报。

最后,再来个总结:ANR 的本质就是「主线程没有及时响应系统发起的某个任务」。它的检测机制可以概括为「定时器+回调」,而分析 ANR 的核心就是看 traces 文件里主线程在干什么。后面几章我们会实战解剖 traces,你到时候就知道这堆堆栈有多直白了。