1. ANR现象识别与用户感知
1.1 什么是ANR?卡顿与冻结的边界
ANR(Application Not Responding)是Android系统在检测到应用主线程在规定时间内未完成指定操作时,强制向用户展示的系统级弹窗。它与普通卡顿的关键区别在于:卡顿是可恢复的短暂掉帧,而ANR意味着用户操作被彻底挂起,系统将给出“关闭 or 等待”的强制选择。
卡顿边界判断规则:
- 输入事件(Input Dispatching)超时:主线程处理按键/触摸事件超过
5s(Android 10+ 为 3.5s)→ ANR - 广播接收器(BroadcastReceiver)超时:前台广播
10s、后台广播60s未执行完 → ANR - 服务(Service)超时:前台服务
20s、后台服务200s→ ANR - ContentProvider 超时:Provider 启动/查询超过
10s→ ANR
1.2 用户感知维度:用户反馈特征
用户对ANR的感知一般经历三个阶段:
- 冻结阶段:触摸屏幕无响应、滑动卡死、动画静止。
- 弹窗阶段:系统弹出“应用无响应”对话框,用户可选择“等待”或“关闭应用”。
- 退出阶段:若用户选择关闭或系统自动杀死进程,应用闪退。
| 用户反馈特征 | 典型描述 | 对应技术指标 |
|---|---|---|
| 点击无反应 | 点了按钮没动静 | InputDispatchingTimingOut(>500ms 已感知) |
| 页面停留卡死 | 一直转圈圈,不消失 | 主线程消息队列堆积 >5s |
| 弹窗出现 | 弹出一个框问我要不要关掉 | ANR 弹窗触发(对应各超时类型) |
| 应用自动关闭 | 闪退到桌面 | ANR 后系统杀进程 |
1.3 系统弹窗识别与日志关键字
系统弹窗是ANR最直接的标志,但其文字因Android版本而异。同时,logcat 中会产生特定的关键字,是排查的重要入口。
日志关键字快速检索表:
| 关键字 | 含义 | 出现位置 |
|---|---|---|
ANR in |
标记发生ANR的进程 | am_anr log |
Reason: |
超时原因(如 Input dispatching timed out) | traces.txt / logcat |
Long wait |
主线程等待某个锁超长 | traces.txt |
"main" thread |
主线程堆栈的关键标记 | traces.txt |
CPU usage |
CPU 负载信息 | traces.txt 末尾 |
Watchdog |
系统看门狗检测到死锁或严重问题 | event log / kernel log |
1.4 故障树:ANR现象识别排查
下图展示了从用户感知到日志确认的排查流程,帮助工程师在接到反馈时快速定位ANR现象。
1.5 日志获取命令与trace示例
当确认ANR后,应第一时间抓取以下数据:
# 获取当前ANR trace文件(需要root或adb 权限)
adb pull /data/anr/traces.txt
# 实时过滤ANR日志
adb logcat -b main -b events -b system | grep -E "ANR|am_anr"
# 查看上次ANR的时间戳(Android 10+)
adb logcat -b events -d | grep "am_anr"
下面是一段典型的 traces.txt 堆栈摘录,展示了主线程因文件读写阻塞导致的ANR:
----- pid 12345 at 2025-03-21 14:30:22 -----
Cmd line: com.example.app
...
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1 obj=0x7fc8f120
| sysTid=12345 nice=0 cgrp=default sched=0/0 handle=0x7f8e0c4000
| state=B schedstat=( 12345678 23456789 123 ) utm=120 stm=80 core=4 HZ=100
| stack:
at java.io.FileOutputStream.write(Native Method)
at java.io.FileOutputStream.write(FileOutputStream.java:326)
- locked <0x08a7f1a0> (a java.io.FileOutputStream)
at com.example.app.utils.FileLogger.writeLog(FileLogger.java:45)
- waiting to lock <0x08a7f200> (a java.io.FileOutputStream)
at com.example.app.MainActivity.onCreate(MainActivity.java:30)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3420)
...
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7682)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:517)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:961)
CPU usage: 45% total, 12% app, 33% system
Load: 4.5 / 3.8 / 2.6
注意: 上述trace中“waiting to lock”表明主线程在等待文件锁,而锁被其他线程持有。该情形属于同步锁竞争导致的ANR,需要进一步分析持锁线程。
1.6 排查表:快速判断ANR类型
| 现象 | 可能原因 | 优先检查点 |
|---|---|---|
| 点击按钮一直无响应,5秒后弹窗 | 主线程耗时操作(IO、网络、锁等待) | 主线程堆栈,查看是否被CPU/IO阻塞 |
| 从后台切回前台时卡死 | BroadcastReceiver onReceive 耗时 | logcat 搜索 “Broadcast of intent”,trace中找“onReceive” |
| 启动页后白屏,然后弹ANR | Service 启动或 ContentProvider 初始化超时 | trace中找“handleCreateService”或“publishContentProviders” |
| 系统截图提示“应用无响应”但未弹窗 | 系统服务看门狗检测,通常伴随 log “Watchdog” | dmesg 或 event log 搜索“Watchdog” |