ANR 概述:什么是 ANR?ANR 有哪些类型?

聊到 Android 性能监控,ANR 永远是绕不开的坎。我做了这么多年多媒体开发,被 ANR 坑过的次数两只手数不过来。什么叫 ANR?说白了就是 Application Not Responding —— 你的 App 没反应了,系统实在等不下去,弹了个对话框问用户是要继续等还是关掉。

为什么会这样?系统给每个 App 的 UI 线程设了超时闹钟。如果你在规定时间内没处理完该做的事,系统就会判定你“死机”了。这个超时时间因组件而异,下面我会详细说。

ANR 的核心原因

简单理解:主线程被阻塞了。主线程既要处理触摸事件、绘制界面,又要响应系统回调。一旦它卡住,整个 App 就僵住了。我印象很深的一个项目,某个短视频播放页面经常 ANR,查到最后发现是在主线程里做视频帧的硬解回调处理——那当然会超时。

触发场景 典型超时时间
输入事件(按键、触摸) 5 秒
服务(Service) 20 秒
广播(BroadcastReceiver) 10 秒
ContentProvider 10 秒(进程启动时)
小提示:ANR 的超时时间在 Android 不同版本上有细微差异,但上面这些值是业内公认的大致范围。你可以通过 adb shell dumpsys activity processes 查看系统实际配置。

ANR 的四大类型

系统按照触发来源,把 ANR 分成四种。每种的处理思路差别挺大,咱们逐个看。

1. 输入事件 ANR(Input ANR)

最常见,也最容易被用户感知。当系统派发 KeyEvent 或 MotionEvent 到 App 主线程后,如果 5 秒内没有被处理完,就会触发。你想想看,用户戳了屏幕,App 半天没反应,换谁都想关掉它。

我在项目中遇到过好几次:某个自定义 View 的 onTouchEvent 里直接做了耗时网络请求,结果线上疯狂报 ANR。解决办法很简单——把网络操作扔到子线程里。

2. 服务 ANR(Service ANR)

Service 的生命周期方法(onCreate、onStartCommand、onBind 等)必须在 20 秒内返回。否则系统会认为该 Service 已死。这里有个容易踩的坑:如果你在 Service 里直接做耗时初始化,比如加载大模型文件、解压资源,很容易超时。

避坑指南:我曾经在 Service 的 onCreate 里写了一个文件解压操作,线上 ANR 率飙到 8%。后来改用 IntentService 或者把耗时操作放到 AsyncTask 里,问题就解决了。

3. 广播 ANR(Broadcast ANR)

广播接收器的 onReceive 方法必须在 10 秒内执行完。注意:这个方法是运行在主线程的。我在早期做多媒体开发时,经常在广播里更新 UI 或者写数据库,一不留神就超时。

而且广播 ANR 有个特点:如果你用 goAsync() 异步处理,记得在回调里调用 finish(),否则超时依然会触发。嗯,这块细节容易被忽略。

4. ContentProvider ANR

这种相对少见,主要发生在 ContentProvider 进程启动时。如果 ContentProvider 的 onCreate 在 10 秒内没有完成,系统就会 ANR。另外,当 Binder 线程池满了导致跨进程调用超时,也可能引发类似 ANR 的行为。

我个人习惯是在 ContentProvider 里只做轻量初始化,比如注册观察者、读配置表,绝不在里面做数据库全量迁移。

重点回顾:所有 ANR 都指向一个共同点——主线程里干了太多不该干的活。不管你是做 UI、做播放器还是做后台服务,只要主线程被堵住,系统就不会留情。

ANR 的类型关系图

下面这张图把 ANR 的四种类型、它们的触发时机和常见原因画在一起。你可以快速对照排查方向。

ANR 类型与触发关系 ANR(应用无响应) 输入事件 ANR 超时 5 秒 服务 ANR 超时 20 秒 广播 ANR 超时 10 秒 ContentProvider ANR 超时 10 秒(进程启动) 本质原因:主线程被阻塞 耗时操作(网络、I/O、计算)未移至子线程 或主线程死锁 / Binder 线程池耗尽

怎么发现 ANR?

我们做性能监控,首先要能快速识别 ANR。除了用户直接看到的“应用无响应”对话框,还有几个常用手段:

  • 查看 /data/anr/traces.txt —— 这是系统生成的 ANR 堆栈文件,里面记录了主线程当时的调用栈。
  • 使用 adb logcat -b events —— 搜索 am_anr 关键字,能看到 ANR 发生的精确时间与进程信息。
  • 接入监控平台(如 xCrash、Matrix 等)—— 可以自动捕获 ANR 并上传堆栈。
个人建议:刚开始做多媒体播放器优化时,我习惯在 onTouchEvent 和 onReceive 入口加 Log.d,打印时间戳。一旦发现两次时间戳间隔超过阈值,基本就能判断发生了 ANR。这种方式虽然原始,但在调试早期很管用。

总结一下

ANR 不是什么神秘的东西。它就是系统对主线程响应能力的一次“体检”。四种类型——输入事件、服务、广播、ContentProvider——各有不同的超时阈值和触发场景。你只要记住一条核心原则:不要在主线程做任何可能超过几百毫秒的事情,就能避开绝大部分 ANR。