ANR治理从入门到实战:主线程阻塞场景与解决方案 - 第1节

1.1 ANR 现象定义

ANR(Application Not Responding)是 Android 系统在检测到应用主线程(UI 线程)超时未处理输入事件或未完成关键操作时,触发的用户可见异常。系统会弹出 ANR 对话框,提示用户“应用无响应”,并提供“等待”或“关闭”选项。ANR 本质是主线程阻塞的强制中断信号。

ANR 类型 超时阈值 触发场景
输入事件分发超时 5 秒 按键、触摸事件在 5 秒内未被分发并处理完成
广播接收器超时 前台广播 10 秒 / 后台广播 60 秒 BroadcastReceiver 的 onReceive() 未在规定时间内返回
服务超时 前台服务 20 秒 / 后台服务 200 秒 Service 的 onCreate()、onStartCommand()、onBind() 未在时限内完成
ContentProvider 超时 10 秒 ContentProvider 的 query()、insert()、update()、delete() 执行超时
输入法(IME)超时 5 秒 输入法框架绑定或输入会话启动超时
关键理解:超时时间是系统从“事件开始”到“主线程空闲”的完整时间。如果主线程连续执行多个阻塞操作(例如同步网络请求+文件写入+大量计算),即使每个操作小于 5 秒,累计超时仍会触发 ANR。

1.2 典型表现与用户感知

用户视角的典型表现

  • 应用界面卡死,无法响应用户触摸、滑动、按键操作
  • 屏幕闪烁或部分区域残留重绘(出现“飞转的等待圈”或“冻结画面”)
  • 系统弹出 ANR 对话框,标题为“XX 应用无响应”,并提示“是否将其关闭?”
  • 点击“等待”后,应用可能恢复,也可能继续卡死;点击“关闭”则进程被杀死
  • 后台广播引发的 ANR 可能不弹出对话框,仅在系统日志中记录(用户无感知但影响后续行为)

用户感知的严重程度指标

感知等级 用户操作 结果 对用户体验的伤害
轻度 快速滑动列表卡顿超过 3 秒 掉帧但未触发 ANR 不信任应用流畅性
中度 点击按钮后 5 秒内无反应并弹出 ANR 对话框 用户选择“等待”,应用暂时恢复 中断操作,用户可能强制关闭
重度 频繁出现 ANR 对话框,用户无法正常使用 用户选择“关闭”或系统自动杀死进程 用户卸载应用、差评、投诉

1.3 系统日志特征(ANR 故障排查入口)

当 ANR 发生时,系统会在 /data/anr/traces.txt 文件中记录主线程与所有线程的堆栈快照。同时,logcat 中会输出 ANR 相关日志。以下是两种关键日志特征。

1.3.1 logcat 中的 ANR 标志

// logcat 输出示例(摘自主线程超时场景)
2025-02-11 14:23:11.456  1234-5678/? I/ActivityManager: ANR in com.example.app (com.example.app/.MainActivity)
    Reason: Input dispatching timed out (Waiting because no window has focus but there is a focused application that may eventually add a window when it finishes starting up.)
    Load: 7.82 / 6.12 / 4.33
    CPU usage: 4564ms user, 1230ms system, 89% total
    • com.example.app (pid 12345): 78% user, 12% system
    • system_server (pid 6789): 15% user, 5% system
    • logd (pid 1234): 1% user, 0.5% system
    ... (top 5 CPU consuming threads)
    • Thread 1 (main) state: WAIT
    • Thread 3 (RenderThread) state: RUNNABLE

logcat 关键字段分析:

  • Reason:直接说明超时类型(Input dispatching timed out / Broadcast of Intent ... timed out / Service ... timed out)
  • Load:系统负载(1/5/15 分钟均值),高负载可能暗示系统资源紧张
  • CPU usage:进程和系统线程的 CPU 占用百分比,用于判断是否因 CPU 饥饿导致阻塞
  • Thread 1 (main) state:主线程当前状态(WAIT / BLOCKED / RUNNABLE)

1.3.2 traces.txt 中的主线程堆栈

// /data/anr/traces.txt 示例
----- pid 12345 at 2025-02-11 14:23:11 -----
Cmd line: com.example.app

"main" prio=5 tid=1 Blocked
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x7f2345a0
  | sysTid=12345 nice=-10 cgrp=default sched=0/0 handle=0x7f123456
  | state=S | schedstat=( 12000000 500000 20 ) utm=8 stm=4 core=3
  | stack=0x7f100000-0x7f120000
  java.lang.Thread.sleep(Native Method)
  at android.os.SystemClock.sleep(SystemClock.java:120)
  at com.example.app.MainActivity.onClick(MainActivity.java:45)
  at android.view.View.performClick(View.java:7125)
  at android.view.View$PerformClick.run(View.java:28541)
  at android.os.Handler.handleCallback(Handler.java:938)
  at android.os.Handler.dispatchMessage(Handler.java:99)
  at android.os.Looper.loop(Looper.java:246)
  at android.app.ActivityThread.main(ActivityThread.java:8571)
  at java.lang.reflect.Method.invoke(Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)

traces.txt 关键字段:

  • Blocked / WAIT / RUNNABLE:线程状态直接指示阻塞类型
  • stack 调用链:从最新调用到 Looper.loop,可以定位到应用代码中导致超时的具体方法(本例中为 MainActivity.onClick 调用 SystemClock.sleep)
  • 先于 ANR 发生时的所有线程堆栈:不仅主线程,其他线程的堆栈也有助于发现死锁或资源竞争
排查规则:先在 logcat 中定位 ANR 的 Reason 和主线程状态,再根据 traces.txt 中主线程堆栈的顶部调用找到业务代码。如果主线程状态为 “Blocked”,则关注等待的锁对象;如果为 “WAIT”,则关注 sleep 或 wait 的原因。

1.4 ANR 现象故障树

ANR 现象识别故障树 ANR 现象 用户可感知 系统日志记录 指标阈值 界面卡死 ANR 对话框 logcat 原因 traces 超时类型 CPU/负载 说明: ANR 现象识别是治理的第一步,用户可感知 = 直接触发对话框;系统日志记录 = 即使未弹框也可能有 traces。 指标阈值帮助判断超时类型和系统资源瓶颈,为后续数据采集提供方向。

1.5 排查判断规则(快速定位 ANR 类型)

使用以下排查表,根据用户反馈和初步日志快速判断 ANR 场景类别:

用户反馈特征 logcat Reason 关键字 traces 主线程状态 大概率类型
点击后长时间无响应 Input dispatching timed out BLOCKED 或 RUNNABLE 输入事件分发超时
切换页面后卡住 Broadcast of Intent { ... } timed out WAIT 或 BLOCKED 广播接收器超时
后台推送后 ANR Service ... timed out BLOCKED 或 WAIT 服务超时
操作数据库/ContentProvider 后卡死 ContentProvider ... timed out BLOCKED ContentProvider 超时
输入法弹出时卡死 InputMethod ... timed out WAIT 或 BLOCKED 输入法超时
注意:上述判断规则起始阶段。实际中可能出现混合超时(例如多个广播同时到来导致主线程排队)。建议始终同步查看 traces.txt 中所有线程的堆栈,以排除死锁和资源竞争。

1.6 本章小结(数据采集前置能力)

在进入“数据采集”章节之前,你需要掌握以下能力:

  • 通过 logcat 获取 ANR 的 Reason 和 CPU 负载
  • 从 traces.txt 获取主线程堆栈及所有线程状态
  • 结合用户感知表现判断超时类型,缩小排查范围
  • 理解 ANR 阈值的差异性(前台 vs 后台,输入 vs 广播)