从 traces.txt 深入分析 ANR 问题

0 阅读3分钟

从 traces.txt 深入分析 ANR 问题

前言

ANR(Application Not Responding)是 Android 开发中最常见也最头疼的问题之一。最近在项目中遇到一个诡异的 ANR,通过深入分析 traces 文件和系统日志,最终定位到是死锁导致的。这篇文章分享下 ANR 分析的完整方法论。

ANR 的本质

ANR 不是崩溃,而是 Android 系统的一种保护机制。当应用的关键线程(通常是主线程)长时间无响应时,系统会强制弹出对话框,让用户选择等待或关闭应用。

ANR 的触发条件

场景超时时间触发位置
Input 事件5 秒InputDispatcher
BroadcastReceiver前台 10s / 后台 60sActivityManagerService
Service 启动前台 20s / 后台 200sActiveServices
ContentProvider10 秒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线程状态⭐⭐⭐
schedstatCPU 调度统计⭐⭐⭐
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();
            }
        }
    }
}

死锁形成过程:

  1. 主线程获取 lockA,准备获取 lockB
  2. 工作线程获取 lockB,准备获取 lockA
  3. 互相等待,形成死锁

解决方案

方案一:统一锁顺序

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();
    }
}