从 traces.txt 深入分析 ANR 问题
前言
ANR(Application Not Responding)是 Android 开发中最常见也最头疼的问题之一。最近在项目中遇到一个诡异的 ANR,通过深入分析 traces 文件和系统日志,最终定位到是死锁导致的。这篇文章分享下 ANR 分析的完整方法论。
ANR 的本质
ANR 不是崩溃,而是 Android 系统的一种保护机制。当应用的关键线程(通常是主线程)长时间无响应时,系统会强制弹出对话框,让用户选择等待或关闭应用。
ANR 的触发条件
| 场景 | 超时时间 | 触发位置 |
|---|---|---|
| Input 事件 | 5 秒 | InputDispatcher |
| BroadcastReceiver | 前台 10s / 后台 60s | ActivityManagerService |
| Service 启动 | 前台 20s / 后台 200s | ActiveServices |
| ContentProvider | 10 秒 | ContentProviderHelper |
ANR 触发流程
以 Input ANR 为例:
sequenceDiagram
participant User as 用户
participant Input as InputDispatcher
participant AMS as ActivityManagerService
participant App as 应用进程
User->>Input: 触摸屏幕
Input->>App: 分发事件
Note over Input: 启动 5 秒定时器
alt 5秒内未响应
Input->>AMS: appNotResponding()
AMS->>App: kill -3 (生成traces)
AMS->>User: 弹出ANR对话框
end
关键代码:
// InputDispatcher.cpp
void InputDispatcher::onANRLocked(...) {
// 通知 AMS 发生 ANR
mPolicy->notifyANR(applicationHandle, windowHandle, reason);
}
Traces 文件解读
获取 traces 文件
# 方法1:从设备拉取
adb pull /data/anr/traces.txt
# 方法2:通过 bugreport
adb bugreport bugreport.zip
# traces 在 FS/data/anr/ 目录下
Traces 文件结构
----- pid 12345 at 2024-03-16 14:20:30 -----
Cmd line: com.example.app
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1 obj=0x74b96080
| sysTid=12345 nice=-10 cgrp=default sched=0/0 handle=0x7f9c8a49a8
| state=S schedstat=( 3250000000 1450000000 8920 ) utm=245 stm=80 core=2
at com.example.app.DataManager.getData(DataManager.java:45)
- waiting to lock <0x0bebc1e8> (a java.lang.Object) held by thread 15
关键字段解释
| 字段 | 含义 | 重要性 |
|---|---|---|
| state | 线程状态 | ⭐⭐⭐ |
| schedstat | CPU 调度统计 | ⭐⭐⭐ |
| waiting to lock | 等待的锁 | ⭐⭐⭐ |
| held by thread | 锁的持有者 | ⭐⭐⭐ |
| utm/stm | 用户态/内核态时间 | ⭐⭐ |
线程状态详解:
- Runnable:正在执行,可能是耗时操作
- Blocked:等待锁,可能是死锁或锁竞争
- Waiting:调用了 wait(),等待唤醒
- Native:执行 native 代码,可能是 Binder 调用
- Sleeping:调用了 sleep()
schedstat 解读:
schedstat=( 3250000000 1450000000 8920 )
↑ ↑ ↑
运行时间 等待时间 调度次数
- 运行时间 3.25 秒:线程实际占用 CPU 的时间
- 等待时间 1.45 秒:线程等待 CPU 调度的时间
- 如果运行时间很长,说明在执行耗时操作
- 如果等待时间很长,说明 CPU 资源紧张
实战案例一:死锁导致的 ANR
问题现象
应用在特定操作后必现 ANR,用户点击任何按钮都无响应。
Traces 分析
"main" prio=5 tid=1 Blocked
at com.example.app.CacheManager.getData(CacheManager.java:32)
- waiting to lock <0x0bebc1e8> (a java.lang.Object) held by thread 15
"WorkThread" prio=5 tid=15 Blocked
at com.example.app.CacheManager.updateData(CacheManager.java:58)
- waiting to lock <0x0bebc200> (a java.lang.Object) held by thread 1
关键信息:
- 主线程等待 thread 15 持有的锁
0x0bebc1e8 - thread 15 等待主线程持有的锁
0x0bebc200 - 典型的死锁
问题代码
class CacheManager {
private final Object lockA = new Object();
private final Object lockB = new Object();
// 主线程调用
public void getData() {
synchronized(lockA) {
// 业务逻辑
synchronized(lockB) {
return data;
}
}
}
// 工作线程调用
public void updateData() {
synchronized(lockB) {
// 业务逻辑
synchronized(lockA) {
update();
}
}
}
}
死锁形成过程:
- 主线程获取 lockA,准备获取 lockB
- 工作线程获取 lockB,准备获取 lockA
- 互相等待,形成死锁
解决方案
方案一:统一锁顺序
public void getData() {
synchronized(lockA) {
synchronized(lockB) {
return data;
}
}
}
public void updateData() {
synchronized(lockA) { // 改为先获取 lockA
synchronized(lockB) {
update();
}
}
}
方案二:使用单一锁
private final Object lock = new Object();
public void getData() {
synchronized(lock) {
return data;
}
}
public void updateData() {
synchronized(lock) {
update();
}
}