1、ANR概述:定义、触发机制、系统判断标准

ANR,全称 Application Not Responding,中文叫“应用无响应”。说白了,就是你的 App 卡住了,系统看不下去了,弹个对话框问用户“是继续等还是直接关掉?”——这玩意儿对用户体验的伤害,比我当年写 Java Swing 死循环还狠。

核心定义:当 Android 应用的主线程(UI线程)被阻塞超过一定时间,系统就会判定发生 ANR,并弹出强制关闭对话框。注意,这个“一定时间”不是固定的,不同场景阈值不同,我后面会细说。

触发机制——系统在等什么?

我刚开始做性能优化时,以为 ANR 只跟死循环有关。后来踩坑多了才发现,系统其实在等五类“关键响应”。任何一种超时,都会触发 ANR。

触发场景超时阈值(Android 默认)系统等待的对象
输入事件分发(按键、触摸)5 秒主线程处理完 InputEvent
广播接收器(BroadcastReceiver)前台:10 秒 / 后台:60 秒onReceive 方法返回
服务(Service)前台:20 秒 / 后台:200 秒onCreate、onStartCommand 等关键生命周期完成
内容提供者(ContentProvider)10 秒onCreate 完成
JobScheduler / 其他系统回调各厂商有差异回调函数返回

嗯,这张表我建议你收藏。因为每次排查 ANR 时,第一步就是看 logcat 里的“ANR in ... (xxx)”——那个 xxx 告诉你究竟是哪种类型超时。我曾经花了两小时查一个 Service ANR,结果发现是 onStartCommand 里做了网络请求,超时阈值 20 秒直接被系统判了死刑。

系统判断标准——它怎么知道你卡了?

系统不是靠猜的,它有套严格的“超时监控 + 信号采样”机制。我把它拆成三步:

  1. 埋桩计时:系统在关键入口(如 InputDispatcher、ActivityManagerService)启动一个定时器,同时记录“当前消息序列号”。
  2. 超时检测:定时器到期后,检查主线程是否处理了那条关键消息。如果还没处理完,说明主线程被阻塞了。
  3. 信号快照:系统向主线程发送 SIGQUIT 信号,强制 dump 所有线程的堆栈(就是 /data/anr/traces.txt)。然后根据堆栈内容判定责任方。
我的个人习惯:每次看到 ANR 问题,第一时间不是看代码,而是先拉 traces.txt。里面会明确指出“main”线程(主线程)当前在哪一行卡住。这个文件就是你破案的指纹。

你想想看,如果主线程正在做下面这些事,几乎必卡:

  • 在主线程调用 Thread.sleep() 或 Object.wait()
  • 在主线程执行网络请求(比如 OkHttp 同步调用)
  • 在主线程执行硬盘 I/O(比如读写大文件、执行 Room 同步查询)
  • 在主线程持有锁,而其他线程长时间不释放(死锁或锁竞争)
  • 布局嵌套过深导致 measure/layout 耗时太长
// 典型错误示例 —— 我在项目里见过不止一次
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // !!!千万别在主线程做这件事 —— 5秒必ANR
        Thread.sleep(6000);
    }
}
我曾经踩过的坑:有一次我自信满满地认为 Room 查询在子线程跑就没事,结果忘了把 LiveData 的 observe 放在主线程,导致 Room 自动在主线程执行查询 —— 数据量 2 万条,直接 ANR。所以记住,Room 的 @Query 如果返回 LiveData,它默认在后台线程执行;但如果你用了 .allowMainThreadQueries(),那就等于把炸弹交给主线程。

知识结构:ANR 从触发到判定全流程

下面这张图把 ANR 的生命周期串了起来,我建议你对着它对照实际日志理解。

ANR 触发与系统判断标准流程 用户操作/系统事件 主线程 MessageQueue 系统超时监控器 超时? 正常处理 发送 SIGQUIT 信号 Dump /data/anr/traces.txt 分析主线程堆栈 弹出 ANR 对话框 常见阻塞原因 • 主线程 I/O 操作 • 死锁 / 锁等待 • 过度刷新布局 • Room 同步查询

这张图的核心在于:系统监控器是独立的 Watchdog 线程,它不会因为你主线程卡住而停止计时。所以只要超时条件满足,SIGQUIT 一定会发——哪怕主线程只是临时卡了 0.5 秒后恢复正常,但一旦超时,ANR 已经生成了。

我的经验总结:三个判断维度

做 ANR 排查久了,我习惯用三个维度快速定位:

  1. 时间维度:看 logcat 中“ANR in”后面的时间戳,确认超时类型(输入事件 / 广播 / Service 等)。
  2. 堆栈维度:直接看 traces.txt 中主线程的堆栈,找到卡在哪个方法里。如果看到 Looper.loop() 并且 msg.target 是 null,那可能是消息队列被清空了——这种情况比较罕见,我遇到过一次,是第三方 SDK 的 bug。
  3. 资源维度:看系统整体负载,比如 CPU 是否被其他进程吃满,或者磁盘 I/O 饱和导致主线程 write 操作阻塞。这一点容易被忽略,我曾经因为一个后台进程频繁写日志拖垮了主线程的 SharedPreferences 提交。
一句话总结:ANR 就是主线程“超时未响应”,触发点来自五大场景,系统通过超时监控 + SIGQUIT 采样来判定。排查时先看 traces.txt 的主线程堆栈,再分析阻塞原因——大部分情况下都是我们在主线程做了不该做的事。