ANR 是什么?我第一回碰上它时的狼狈样

刚工作那会儿,我接手一个已经上线的社交应用。用户反馈说“点完发送就卡死,等十几秒才能动”。我当时连 ANR 是啥都不太清楚,就闷头查代码、加日志、打补丁,折腾了两周没搞定。直到老大丢过来一个 traces.txt 文件,说:“你看看主线程在干嘛。”

嗯,那是我第一次正式认识 ANR。现在回头看,如果早一点理解 ANR 的本质,至少能省下一周的弯路。

到底什么是 ANR?

ANR 的全称是 Application Not Responding,翻译过来就是“应用无响应”。

说白了,Android 系统有一个“看门狗”机制。它盯着你的主线程:如果主线程在规定时间内没干完该干的事,系统就会弹出一个对话框——“XX 应用无响应”,用户可以选择“继续等待”或者“关闭应用”。

⚠️ 注意: ANR 不是崩溃,它比崩溃更恶心。崩溃至少会直接闪退,用户忍忍就过去了;ANR 会冻住整个界面,用户只能干等,流失率极高。我见过一个电商 App 因为搜索页 ANR,一天掉了 5% 的转化率。

ANR 的超时类型(记住这张表)

不同的场景,超时时间不同。我建议新人把下面这个表背下来,分析日志时常常要用到。

事件类型 超时时间 触发条件
输入事件(按键/触摸) 5 秒 主线程没有在 5 秒内处理完输入事件
广播接收器(前台) 10 秒 onReceive() 方法执行超过 10 秒
广播接收器(后台) 60 秒 低优先级后台广播,超时放宽到 60 秒
服务(前台/后台) 20 秒 onCreate()、onStartCommand()、onBind() 等耗时过长
ContentProvider 10 秒 发布前 onCreate() 执行超过 10 秒
💡 个人经验: 我见过最隐蔽的是后台广播超时。很多开发者以为后台广播 60 秒很宽裕,就在 onReceive() 里做网络请求、写数据库,结果低端机上一跑就炸。记住:广播接收器是跑在主线程的!

ANR 会带来什么影响?

不要以为 ANR 只是弹个框那么简单。它的连锁反应很要命:

  • 用户体验极差: 界面冻结导致用户反复点击、误操作,甚至以为是手机坏了。
  • 应用可能被系统杀死: 连续出现 ANR,AMS(Activity Manager Service)会直接干掉你的进程。
  • 数据丢失: 如果 ANR 发生在保存草稿、上传文件的过程中,数据可能不完整。
  • 应用市场评分暴跌: 很多用户遇到 ANR 第一反应是去应用商店给差评。
  • 后台任务雪崩: 主线程卡住后,其他依赖主线程的异步回调、Handler 消息也会堆积,形成雪崩效应。

ANR 发生的典型场景

根据我这些年修复 ANR 的经验,90% 的 ANR 都逃不出下面几种情况:

  1. 主线程做 IO 操作: 比如读写文件、网络请求、数据库操作。
  2. 主线程进行复杂计算: 比如解析大 JSON、图片压缩、加密运算。
  3. 主线程等待锁: 子线程持有一个锁,主线程必须等它释放,如果子线程里也有耗时操作……死锁也就来了。
  4. 主线程和 Binder 线程死锁: 这是一种经典的 ANR,我在项目里至少碰到过三次。
  5. CPU 资源被抢: 其他进程(比如系统服务、后台 App)吃掉了大量 CPU,你的应用主线程调度不到时间片。
🔍 一句话总结 ANR 的本质: 主线程在规定时间内未能完成事件处理,导致系统判定应用无响应。

这张图帮你看清 ANR 的完整链路

下面这张 SVG 图把 ANR 的触发、检测、上报链路画清楚了。我建议你保存下来,分析问题时对着它走一遍,思路会清晰很多。

ANR 触发与检测流程 用户操作 / 系统事件 主线程处理事件(Message Queue) 在规定时间内完成 正常响应,继续运行 超时(5s/10s/20s/60s) AMS 检测到超时 生成 ANR 文件 输出 anr/traces.txt 开发者分析 traces.txt 修正后避免 📌 关键点:ANR 文件(/data/anr/traces.txt)记录了主线程所有堆栈。主线程如果卡在某个锁上, 就能看到明确等待的锁对象。这是定位 ANR 的第一手证据,比 logcat 日志更精准。

回到最初的那个问题

我当年碰到的 ANR,其实就是主线程在发送消息时做了一次网络图片压缩。当时图片库做了一次同步磁盘缓存,主线程直接被 IO 卡死。解决办法很简单:用 AsyncTask 或者协程把压缩和缓存扔到后台线程。

嗯,现在看起来也就是一行代码的事。但当时不懂 ANR 原理,排查了整整两周。所以学这章的时候,我希望你能把 ANR 的机制和超时类型刻在脑子里。后面的章节我们会逐一攻破各种复现策略和日志定位技巧。

💡 日常开发习惯: 我建议每次写完一段可能耗时的代码,都问自己一句:“这个操作在主线程还是子线程?” 如果跟 UI 无关,就该用 WorkManager、Coroutine 或 HandlerThread。养成习惯后,ANR 基本找不上你。