ANR概述与原理归纳总结

聊ANR之前,我想先说句大实话——这玩意儿几乎是每个Android开发都会遇到的坎。我自己最早碰到ANR时,完全是一脸懵的状态。弹个对话框,点“等待”还是“关闭”?哪个都不对。后来慢慢踩坑、看源码、分析trace,才搞明白它到底是怎么回事。

说白了,ANR就是系统觉得你的App“反应太慢了”。不管是用户点了没反应,还是广播没处理完,系统都会认为你影响了用户体验。那它什么时候启动这个机制呢?今天我们就从根上把它理清楚。

核心定义: Application Not Responding,指应用在特定时间内未能完成对输入事件或组件的响应,系统会弹出ANR对话框。

1.1 系统如何判断ANR?

Android系统对ANR的判定,靠的是一套定时器机制。说白了,系统启动了一个倒计时器,如果倒计时结束你还没完成该干的事,ANR就来了。

下面是4个主要场景,每个都有明确的超时阈值:

组件类型 超时时间 核心逻辑
BroadcastReceiver(前台) 10秒 onReceive() 执行时间超限
BroadcastReceiver(后台) 60秒 同上,但超时时间更长
InputEvent(触摸/按键) 5秒 主线程未完成事件分发
Service(前台启动) 20秒 onCreate / onStartCommand 执行超时
ContentProvider 10秒 查询或更新操作超时

嗯,这里要注意:前台广播只有10秒。我曾经以为10秒挺长的,后来发现有些json解析或者数据库写入稍一膨胀,就踩线了。

1.2 ANR的底层触发链路

系统是怎么抓到ANR呢?我们来拆一下关键步骤:

  1. 调度器发起请求:比如系统向App主线程派发一个输入事件。
  2. 启动超时计时:ActivityManagerService内部启动一个Handler,发送延时消息。
  3. 等待响应:如果主线程在超时前处理完成,取消计时器。
  4. 超时触发:计时器到达,系统认为App无响应。
  5. 生成trace文件:dump主线程及所有线程的堆栈到 /data/anr/traces.txt。
  6. 弹框提示用户:显示ANR对话框,让用户选择等待或关闭。
// 以 Input 事件为例,核心代码路径在 InputDispatcher.cpp
// 等待超时后会输出类似这样的日志:
// W/InputDispatcher: ANR in com.example.app (pid=12345)
// Reason: Input dispatching timed out (Waiting because no window has focus...)
// 这就是系统在告诉你——主线程被卡住了

我个人习惯是:看到这类日志,马上把注意力集中到主线程正在干什么。是布局?是IO?还是锁竞争?

1.3 为什么主线程不能阻塞?

再说直接点:主线程就是UI线程。所有界面刷新、事件响应、生命周期回调都在这里跑。你一旦把它堵住,用户界面就“冻住”了。

Android系统设计了一个MessageQueue机制,主线程不停地从队列取消息来执行。如果某条消息执行太久,后续消息全部排队,用户操作也排着。5秒没响应,ANR。

一个常见误解:很多人以为ANR只是“卡顿阈值放大版”。不对。卡顿是掉帧,用户能看到但还能交互;ANR是完全无响应,用户点啥都没用。这是两个量级的问题。

1.4 我从项目中总结的高发场景

我这些年遇到的ANR,基本逃不出下面这几类:

  • 主线程做网络请求:特别是低端机,弱网环境,阻塞十几秒很正常。
  • 主线程做复杂布局:一次inflate几百层嵌套的布局,甚至包含webview。
  • 锁竞争导致的死锁:两个线程互相等对方的锁,主线程恰好被卷进来。
  • 文件IO写大文件:比如在主线程write几十MB数据到存储卡。
  • Binder调用等待超时:主线程调一个binder方法,远程服务迟迟不返回。
  • 广播执行超长逻辑:onReceive里跑数据库批量插入,直接超10秒。
避坑指南:我曾经在一个项目里遇到一个隐藏极深的ANR——onCreate里启动了一个CountDownTimer,它的tick回调直接更新UI,但每次tick里又嵌套了SharedPreferences的commit(同步写入)。你知道的,commit是同步写磁盘,低端机写一次几十毫秒,累积下来就炸了。

1.5 遇到ANR了,第一反应做什么?

我建议你养成这个习惯:ANR发生后,第一时间拉trace文件。位置是 /data/anr/traces.txt。如果手机没root,可以用 adb bugreport 打包出来。

在trace里,看主线程的堆栈:

// 一个实际的trace片段
"main" prio=5 tid=1 Blocked
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x7515c438
  | sysTid=12345 nice=-10 cgrp=default sched=0/0 handle=0x7f2b8b48e0
  at com.example.app.MainActivity.onCreate(MainActivity.java:45)
  - waiting to lock <0x12345678> (a java.lang.Object)
  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3200)
  ...
  // 看到"waiting to lock"了吗?这是典型的主线程在等锁。

另外,别只看主线程。看看其他线程正在干什么,特别是那些你开了但没管理好的线程池,有些可能在死循环或者做重IO。

1.6 本章知识体系总览

下面这张图,我把ANR的核心脉络串起来了。你可以把它当成本章的一张“地图”:

ANR核心知识体系 ANR 概述与原理 输入事件超时(5s) 广播接收器超时 Service/Provider超时 AMS定时器 ⇒ 超时 ⇒ 生成trace 分析主线程Stack + 锁状态 主线程IO/网络 死锁/锁竞争 布局绘制过重 Binder调用阻塞 掌握上面这棵树,你就知道ANR从哪里来,又该查哪里。

说实话,ANR原理本身并不复杂,难的是在实际的复杂系统里快速定位。有了这张图打底,后面我们再深入具体触发场景和复现策略时,心里就有谱了。