ANR 概述与影响归纳总结

ANR 全称 Application Not Responding,说直白点,就是你的 App 卡死了,系统给了用户一个「强制关闭」的选项。我在做第一代埋点 SDK 时,就吃过 ANR 的大亏——当时因为主线程写文件阻塞,上线第一天就收到几十个 ANR 报警。

嗯,系统的判断逻辑其实不复杂:当主线程超过 5 秒没有处理输入事件,或者广播接收器 10 秒没回来,或者 Service 20 秒没完成,系统就会判定为 ANR。你想想看,如果你是用户,点一下按钮等 5 秒没反应,第一反应就是关掉这个 App 吧?

触发 ANR 的典型场景

根据我这些年踩过的坑,我把最常见的触发场景归了类。下面这个表格是基础判断标准,但我建议你记在脑子里:

类型 超时阈值 我的血泪教训
输入事件派发 5 秒 曾经因为在主线程做 SharedPreferences 写入,300 条数据卡了 7 秒
广播接收器(前台) 10 秒 某次在 onReceive 里直接请求网络,结果 Wi-Fi 切换时直接 ANR
广播接收器(后台) 60 秒 后台广播其实更隐蔽,我遇到过第三方 SDK 在后台广播里做复杂计算
Service 20 秒 Service 的 ANR 往往跟死锁有关,我就见过同步锁嵌套导致的永久阻塞
ContentProvider 10 秒 跨进程查询时如果 Provider 所在进程被阻塞,也会触发 ANR

关键点:所有 ANR 的根因都是主线程被阻塞。但阻塞的原因千奇百怪——IO、锁竞争、Binder 调用、甚至是 CPU 负载过高导致的调度延迟。

ANR 对用户和业务的影响

影响其实分三层,越往上越致命:

  1. 用户体验暴跌:用户等着用,结果屏幕卡住。我亲眼见过用户卡了 5 秒后直接 Home 键强制退出的操作流。
  2. 应用被系统杀掉:系统会在 ANR 弹窗出现一段时间后直接 kill 进程,你的埋点还没上报就丢了。
  3. 用户流失与差评:这个最痛。据我了解的数据,单次 ANR 能导致次日留存下降 2%~5%。

所以我们在做埋点工程化时,第一条红线就是「埋点代码绝不能引起 ANR」。我自己曾因为在线程同步设计不当,导致埋点上报线程卡住主线程——那一次教训直接让我写了全套 ANR 监控链路。

注意:不要以为只有用户可见的卡顿才叫 ANR。像 Service 和后台广播的 ANR 用户看不到弹窗,但系统日志里会记录,同样影响应用状态。我们做数据保护时一定要把暗 ANR 也纳入追踪。

ANR 触发到影响的完整链路

下面这张图是我在项目里总结出来的完整链路。你看一遍就能理解为什么我们说「主线阻塞」是万恶之源。

ANR 触发影响链路 用户输入/触摸事件 主线程消息队列(Looper) 主线程执行消息(可能阻塞) 系统超时监测 ANR 触发 ANR 弹窗/日志 系统 Kill 进程 用户流失/差评 定时检测:InputDispatching 、Service Timeout 等

实用技巧:我自己在排查 ANR 时,第一件事就是拿 trace 文件看主线程在做什么。如果看到「Thread-xxx blocked at ...」,基本就是死锁;如果是「pollOnce + 堆栈很长」,多半是 Binder 调用太久。记住这个套路,效率提升 50%。

ANR 的常见分类维度

为了方便后续做链路追踪,我把 ANR 按「触发源」和「可见性」做了分类。这个分类在我的埋点上报聚合逻辑中一直在用:

  • 可见 ANR:用户能看到弹窗(输入事件超时、前台广播超时)。这类 ANR 直接伤害用户感知,需要最高优先级治理。
  • 不可见 ANR:后台广播、Service、ContentProvider 超时。用户看不到弹窗,但系统日志会生成,且可能杀掉后台服务。
  • 主线程阻塞:一切 ANR 的根因,但有些阻塞没触发超时阈值,属于「准 ANR」——我通常把主线程卡顿超过 3 秒的也纳入监控。

总结影响:ANR 不仅仅是技术问题,更是业务问题。我自己测算过:一个 Top 级的 App 如果 ANR 率超过 0.5%(每 100 次启动就有 0.5 次 ANR),日活损失就可能达到 5 位数。所以我们做数据保护的第一步,就是先管好主线程。