Skip to content

Android ANR 触发机制、排障定位与防范 ​

回到总览:并发与运行时底座
相关模块:Android 主线程消息循环:Looper / Handler / MessageQueue · Android Binder IPC 机制与跨进程通信

一句话定义 ​

ANR(Application Not Responding,应用无响应)是 Android 系统 AMS 检测到主线程在规定时间内未能响应特定事件(输入事件、Service 生命周期、BroadcastReceiver、ContentProvider)时,通过信号弹出的系统级无响应机制。

代码索引 ​

对应 Lab:anr-mechanism-demo · AnrMechanismDemoActivity.kt

为什么需要 ​

  • 为什么主线程耗时一定会引发 ANR?
    • 一句话答:Android 的 UI 渲染与四大组件回调均在主线程消息队列(MessageQueue)中排队;当某个消息阻塞主线程超过系统阀值时,后续的输入事件或组件生命周期消息无法被及时处理,系统定时器就会被触发打断并抛出 ANR。
  • 为什么 ANR 发生在主线程,但根因却经常在子线程?
    • 一句话答:主线程可能在等待子线程持有的锁(Lock Contention),或者子线程死循环做 CPU 密集计算占满了多核资源(CPU Starvation),导致主线程即使空闲也分不到 CPU 时间片执行。
  • 在 Flutter / 混合开发中如何防范 ANR?
    • 一句话答:Flutter 在 Android 平台上的 UI TaskRunner 默认运行在宿主主线程上;如果 Dart 代码中触发了密集型同步计算或 PlatformChannel 同步调用阻塞了主线程,同样会导致 Android 宿主抛出 ANR。

底层机制 ​

1. ANR 的四大触发场景与超时阈值 ​

对应 Lab:anr-mechanism-demo

Android 系统在 AMS(ActivityManagerService)中为四大场景分别设置了倒计时定时器(Handler 延迟消息):

ANR 类型触发条件前台阈值后台阈值触发点
InputDispatching按键或触摸事件 5s 内未处理完成5 秒5 秒InputDispatcher
ServiceonCreate() / onStartCommand() 超时20 秒200 秒AMS.ServiceTimeout
BroadcastReceiveronReceive() 执行超时10 秒60 秒BroadcastQueue
ContentProviderpublish() 或 query() 启动超时20 秒20 秒AMS.ProviderTimeout

2. ANR 的系统倒计时工作原理 ​

以 Service 启动为例,AMS 的倒计时过程如下:

图注补充:Handler: 2. postDelayed 发送 20s 超时 ANR 消息;AMS: 3. Service 启动完成,发送 serviceDone 回执;Handler: 4. 移除/取消 20s 延迟 ANR 消息;AMS: 5. 20s 延迟消息到期触发, 执行 appNotResponding;App: 6. 发送 SIGQUIT(3), 收集 traces.txt 并弹出 ANR 框。

  1. AMS 在发起 startService 请求的同时,向自身的 AnrHandler 发送一个 20 秒延迟的 ANR 消息。
  2. 如果 App 主线程在 20 秒内完成了 Service 的 onCreate(),会向 AMS 发送回执,AMS 收到回执后取消/移除该延迟 ANR 消息。
  3. 如果主线程卡死(如发生死锁、主线程 I/O),AMS 20 秒内未收到回执,延迟消息被触发,AMS 倾倒堆栈信息(Dump Stacks)并弹出 ANR 对话框。

3. ANR 发生的常见三大根因类型 ​

  1. 主线程直接阻塞:主线程做同步网络请求、大文件 I/O、复杂的数据库查询。
  2. 主线程锁等待 (Lock Contention):主线程尝试获取某个对象的 synchronized 锁,而该锁被子线程持有,且子线程正在执行耗时 I/O。
  3. CPU 资源耗尽 (CPU Starvation):后台多个子线程在死循环做密集计算,抢占了 CPU 时间片,导致主线程即使没有阻塞也无法分到 CPU 资源执行。

Android / Flutter / Web / Backend 对照 ​

维度Android 原生Flutter / DartWeb / Backend 对照
卡死机制系统级 ANR (AMS 定时器 + 弹框)页面假死 / 触发宿主 ANR浏览器页面未响应 (Unresponsive Page)
检测手段BlockCanary / Perfetto / traces.txtPerformance Overlay / DevTools ProfilerLong Tasks API / Sentry
异步避坑异步子线程 / Coroutines Dispatchers.IOcompute() / Isolate.spawn() 搬走重活Web Worker 移走耗时计算

常见场景 ​

1. 主线程尝试获取子线程持有的 synchronized 锁 ​

java
// 耗时子线程
new Thread(() -> {
    synchronized (mLock) {
        // 子线程持有锁并做耗时 File I/O
        readBigFile(); 
    }
}).start();

// 主线程
public void onClick(View v) {
    synchronized (mLock) { // 主线程在 onClick 中挂起等待 mLock,触发 Input ANR!
        doSomething();
    }
}

2. BroadcastReceiver 中做耗时网络操作 ​

在 onReceive() 中直接发网络请求,前台广播超出 10 秒即抛出 Broadcast ANR。

常见误配、事故后果与排障 ​

1. 如何分析 traces.txt 定位 ANR 根因 ​

从 /data/anr/traces.txt 中搜索 main 线程:

text
"main" prio=5 tid=1 Waiting
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x73243180 self=0x7f8d682000
  | sysTid=1234 nice=-10 cgrp=default sched=0/0 handle=0x7f91a50498
  | state=S schedstat=( 12345678 87654321 123 ) utm=1 stm=0 core=2 HZ=100
  | stack=0x7ffe420000-0x7ffe422000 stackSize=8MB
  | held mutexes=
  at com.example.MainActivity.onClick(MainActivity.java:45)
  - waiting to lock <0x0c3124ab> (a java.lang.Object) held by thread 12  <-- 明确指出被 thread 12 持有!

排障步骤:

  1. 观察 main 线程状态:如果为 Waiting / Blocked,寻找 - waiting to lock <id>。
  2. 顺藤摸瓜查找 tid=12 线程的堆栈,查看该线程在做何事(如 java.io.FileInputStream.read)。
  3. 结论:由于子线程持锁做 File I/O,阻塞了主线程 onClick 响应,导致 ANR。

2. 防范策略与工具链 ​

  • 开发阶段:开启 StrictMode 严格模式,违规主线程 I/O 直接在 Logcat 报错或 Crash。
  • 线上监控:接入 BlockCanary(基于 Handler Printer 监听 dispatchMessage 的前后时间差),若单条消息耗时超过 200ms 即打印卡顿调用栈。

与相近概念对比 ​

概念表现形式是否崩溃 (Crash)解决手段
ANR系统弹框“应用无响应”,用户可选择关掉或等待否 (系统主动杀进程)耗时逻辑移出主线程、解除死锁
Crash (OOM/Exception)进程直接闪退解体是捕获异常、优化内存
Jank (卡顿/掉帧)画面微卡、丢帧,但未达到 ANR 秒级阈值否优化布局嵌套、减少主线程轻微耗时

对应实验 ​

Lab说明源码
anr-mechanism-demo主线程阻塞 6 秒触发 ANR + adb 诊断命令AnrMechanismDemoActivity.kt

复习检查题 ​

  1. BroadcastReceiver 的 onReceive() 默认运行在哪条线程?超时时间是多少?

    答:默认运行在 App 的主线程。前台广播的超时限制为 10 秒,后台广播为 60 秒。如果在 onReceive() 中做耗时操作,需开启 GoAsync() 或交由 WorkManager 处理。

  2. 为什么 CPU Starvation (CPU 被占满) 也会导致没有任何锁阻塞的主线程触发 ANR?

    答:因为 Android 是多任务操作系统,当后台多个子线程在死循环或密集计算占满了所有 CPU 核心(CPU 使用率 100%)时,操作系统内核给主线程分配的 CPU 时间片急剧减少,导致主线程即便消息队列正常,也无法获得足够的 CPU 执行周期在规定时间内处理完输入事件,从而触发 ANR。

速记 ​

  • 四场景阈值:Input 5s,Broadcast 10s,Service 20s,ContentProvider 20s。
  • ANR 三根因:主线程直接耗时/IO、主线程锁等待 (Lock Contention)、CPU 满载抢占 (CPU Starvation)。
  • 排障口诀:先查 main 线程状态,Blocked 找持锁 thread,Waiting 找挂起点,Runnable 查 CPU 负载。

站点构建时间:2026/8/24 23:43:17