Android 7系统异常问题排查(六)应用异常(上)—ANR机制全解

系列目录第一篇:异常机制全景图 | [第二篇:Kernel Panic 与系统重启](#第二篇:Kernel Panic 与系统重启) | [第三篇:Tombstone 机制](#第三篇:Tombstone 机制) | [第四篇:System Server Watchdog](#第四篇:System Server Watchdog) | [第五篇:System Server 崩溃](#第五篇:System Server 崩溃) | [第六篇:ANR 机制](#第六篇:ANR 机制) | [第七篇:Java 层崩溃](#第七篇:Java 层崩溃) | [第八篇:Trace 机制](#第八篇:Trace 机制) | 第九篇:日志系统 | 第十篇:实战方法论


一、ANR 概述

你可能遇到过这些场景:

  • App 弹出"应用无响应"对话框,但不知道是主线程卡死还是 Binder 调用超时
  • 滑动界面时突然卡住,几秒后弹出 ANR 对话框
  • 后台广播接收器执行过久,系统判定 ANR

ANR (Application Not Responding)是 Android 系统在检测到应用主线程长时间无响应时,向用户展示的对话框。ANR 的底层机制是系统的超时检测------系统在某些关键操作上设置了一个时间上限,如果主线程在限定时间内没有完成操作,就触发 ANR。


二、ANR 分类与超时阈值

在 AOSP 7 中,ANR 分为四类,各有不同的超时阈值:

类型 超时阈值 检测组件 触发场景
Input ANR 5 秒 InputDispatcher 应用未及时处理输入事件(触摸/按键)
Broadcast ANR 前台 10s / 后台 60s BroadcastQueue 广播接收器 onReceive() 执行过久
Service ANR 前台 20s / 后台 200s ActiveServices 服务 onCreate()/onStartCommand() 执行过久
ContentProvider ANR 10 秒 ActivityManagerService Provider 发布超时(较少见)

三、Input ANR ------ 输入事件超时

3.1 InputDispatcher 的角色

Input ANR 的检测发生在 Native 层的 InputDispatcher

源码路径frameworks/native/services/inputflinger/InputDispatcher.cpp

复制代码
触摸事件流:
    InputReader(从驱动读事件)
        ↓
    InputDispatcher(分发到目标窗口)
        ↓
    目标 App 主线程(处理事件)
        ↓
    处理完成 → finishInputEvent() → 确认回执

3.2 超时检测机制

源码路径frameworks/native/services/inputflinger/InputDispatcher.cpp

cpp 复制代码
// 默认输入分发超时:5 秒
const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec

// InputDispatcher::dispatchOnce() 核心逻辑
void InputDispatcher::dispatchOnce() {
    // 从队列取出事件,发送给目标 App
    // 启动 5 秒超时计时器

    // 如果 App 在 5 秒内调用了 finishInputEvent()
    //   → 正常完成,取消计时器

    // 如果 5 秒内未收到确认
    //   → 触发 Input ANR
    //   → 通知 AMS 进行 ANR 处理
}

关键设计DEFAULT_INPUT_DISPATCHING_TIMEOUT 是 5000ms(5 秒),这个值定义了 Input ANR 的超时阈值。

3.3 特殊机制:聚焦窗口优先

InputDispatcher 会区分"前台窗口事件"和"后台窗口事件":

  • 前台聚焦窗口未及时响应一定触发 ANR
  • 后台窗口未及时响应 → 不直接触发 ANR,而是将这些事件丢弃,等待窗口重新聚焦时重发

这也是为什么后台 App 卡死不一定会直接弹 ANR 的原因。


四、Broadcast ANR ------ 广播超时

4.1 超时检测位置

源码路径frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java

java 复制代码
public final class BroadcastQueue {
    // ...
    // 超时常量定义在 AMS 中
    // static final int BROADCAST_FG_TIMEOUT = 10*1000;   // 10秒
    // static final int BROADCAST_BG_TIMEOUT = 60*1000;   // 60秒

    static final int BROADCAST_TIMEOUT_MSG =
            ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG + 1;

    final long mTimeoutPeriod;  // 由构造函数传入
    // ...
}

4.2 超时检测流程

源码路径frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java

java 复制代码
public final class BroadcastQueue {
    // ...
    final void processNextBroadcast(boolean fromMsg) {
        synchronized (mService) {
            // 遍历广播队列
            while (mParallelBroadcasts.size() > 0
                   || mOrderedBroadcasts.size() > 0) {

                // 取出一个广播接收者
                BroadcastRecord r = getNextBroadcast();

                // 设置超时检测
                if (r.receiver != null) {
                    // 发送超时检测消息
                    long timeoutTime = r.receiverTime + mTimeoutPeriod;
                    setBroadcastTimeoutLocked(timeoutTime);

                    // 发送广播给接收者
                    deliverToRegisteredReceiverLocked(...);
                }
            }
        }
    }

    final void broadcastTimeoutLocked(boolean fromMsg) {
        // 如果广播还没有处理完成 → ANR
        if (!didProcess) {
            // 触发 ANR
            mService.appNotResponding(...);
            // 重新调度超时检测
            setBroadcastTimeoutLocked(mTimeoutPeriod);
        }
    }
}

关键设计 :超时检测通过 Handler 延迟消息实现。BROADCAST_TIMEOUT_MSG 在超时时间到达时触发 broadcastTimeoutLocked(),如果此时广播仍未处理完成,就触发 ANR。

4.3 前台 vs 后台广播的超时差异

广播类型 超时 AOSP 7 判断条件
前台广播 10 秒 Intent.FLAG_RECEIVER_FOREGROUNDisFg
后台广播 60 秒 普通广播(默认)

源码路径frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

java 复制代码
public class ActivityManagerService extends ActivityManagerNative {
    // ...
    static final int BROADCAST_FG_TIMEOUT = 10*1000;  // 10秒
    static final int BROADCAST_BG_TIMEOUT = 60*1000;  // 60秒

    // 前台广播队列和后台广播队列分别使用不同超时
    mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
            "foreground", BROADCAST_FG_TIMEOUT, false);
    mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
            "background", BROADCAST_BG_TIMEOUT, true);
}

关键设计:前台广播的超时要求更严格(10 秒),因为用户正在等待操作完成(如短信发送、网络切换等)。


五、Service ANR ------ 服务超时

5.1 超时检测位置

源码路径frameworks/base/services/core/java/com/android/server/am/ActiveServices.java

java 复制代码
public final class ActiveServices {
    // ...
    static final int SERVICE_TIMEOUT = 20*1000;              // 20秒
    static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;  // 200秒
    // ...
}

5.2 超时检测流程

源码路径frameworks/base/services/core/java/com/android/server/am/ActiveServices.java

java 复制代码
public final class ActiveServices {
    // ...
    void realStartServiceLocked(ServiceRecord r, ProcessRecord app) {
        // 通知 App 进程创建 Service
        bumpServiceExecutingLocked(r, "create");
        app.thread.scheduleCreateService(r, ...);

        // 设置超时检测
        scheduleServiceTimeoutLocked(app);
    }

    void scheduleServiceTimeoutLocked(ProcessRecord proc) {
        Message msg = mAm.mHandler.obtainMessage(
            ActivityManagerService.SERVICE_TIMEOUT_MSG);
        msg.obj = proc;
        // 发送延迟消息
        // 前台服务:SERVICE_TIMEOUT (20s)
        // 后台服务:SERVICE_BACKGROUND_TIMEOUT (200s)
        mAm.mHandler.sendMessageDelayed(msg,
            proc.execServicesFg ? SERVICE_TIMEOUT
                               : SERVICE_BACKGROUND_TIMEOUT);
    }

    void serviceTimeout(ProcessRecord proc) {
        // 如果服务还没响应 → ANR
        if (proc.executingServices.size() > 0) {
            // 触发 ANR
            mAm.appNotResponding(proc, ...);
        }
    }
}

关键设计 :Service ANR 的超时也是通过 Handler 延迟消息实现。SERVICE_TIMEOUT 是 20 秒,SERVICE_BACKGROUND_TIMEOUT 是 200 秒(10 倍)。

5.3 前台 vs 后台服务超时差异

服务类型 超时 触发条件
前台服务 20 秒 startForeground() 调用的服务
后台服务 200 秒 普通 startService()

六、ANR 触发后的处理流程

6.1 AMS.appNotResponding() ------ 核心处理

源码路径frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

java 复制代码
public class ActivityManagerService extends ActivityManagerNative
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
    // ...
    final void appNotResponding(ProcessRecord proc,
            ActivityRecord activity, ...) {

        // 1. 记录 ANR 日志
        Slog.e(TAG, "ANR in " + proc.processName);

        // 2. 收集 CPU 使用情况
        updateCpuStatsNow();

        // 3. dump 所有线程堆栈到 /data/anr/traces.txt
        File tracesFile = ActivityManagerService.dumpStackTraces(
            true, firstPids, ...);

        // 4. 记录到 DropBoxManager
        addErrorToDropBox("anr", proc, ...);

        // 5. 根据 ANR 类型决定是否弹对话框
        if (!isSilentAnr) {
            // 弹出 ANR 对话框(前台应用)
            Message msg = mHandler.obtainMessage(SHOW_NOT_RESPONDING_UI_MSG);
            msg.obj = ...;
            mHandler.sendMessage(msg);
        }

        // 6. 广播 ANR 事件(供调试工具监听)
        Intent intent = new Intent("android.intent.action.ANR");
        broadcastIntentLocked(...);
    }
}

关键设计 :ANR 处理的核心是 dumpStackTraces()------通过向目标进程发送 SIGQUIT 信号,让各进程的 Signal Catcher 线程输出堆栈到 /data/anr/traces.txt

6.2 /data/anr/traces.txt 的生成

复制代码
dumpStackTraces() 流程:
    1. 确定需要 dump 的进程:
       - 触发 ANR 的进程(优先)
       - 关键系统进程(system_server、surfaceflinger、mediaserver)
       - 其他可能有关系的进程
    2. 向每个目标进程发送 SIGQUIT 信号
    3. 各进程的 Signal Catcher 线程响应 SIGQUIT
    4. 输出各线程的堆栈到 /data/anr/traces.txt

七、traces.txt 解读方法

7.1 文件结构

复制代码
----- pid 1234 at 2024-01-01 12:00:00 -----
Cmd line: com.example.app
Build fingerprint: 'Android/aosp_...'
ABI: 'arm64'

"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 obj=0x12c0e0a0
  | sysTid=1234 nice=-2 cgrp=default sched=0/0
  | state=S schedstat=( ... )
  at android.os.BinderProxy.transactNative(Native Method)
  at android.os.BinderProxy.transact(Binder.java:456)
  at com.example.SomeService$Stub$Proxy.doWork(SomeService.java:100)
  at com.example.MainActivity.onCreate(MainActivity.java:50)
  ...

----- end 1234 -----

7.2 线程状态字典

状态 含义 定位线索
Native 正在执行 Native 代码(JNI 或系统调用) 可能是 Binder 调用、IO 等待
Runnable 正在运行或等待 CPU 检查是否有密集计算
Blocked 等待获取对象锁 死锁/锁竞争的重点排查对象
Waiting 调用了 Object.wait() 检查条件变量是否被 notify
TimedWaiting 调用了 Thread.sleep() 或带超时的 wait() 检查 sleep 时间是否合理
Sleeping Thread.sleep() 主线程不应 sleep

7.3 主线程 Blocked 判断

traces.txt 中"主线程(tid=1)处于 Blocked 状态"是最典型的 ANR 根因:

复制代码
"main" prio=5 tid=1 Blocked
  at com.example.Utils.expensiveOperation(Utils.java:50)
  - waiting to lock <0x12345678> held by "Binder:1234_2" tid=10

"Binder:1234_2" tid=10 Runnable
  at com.example.Utils.expensiveOperation(Utils.java:45)
  - locked <0x12345678>

解读:Binder 线程持有锁,主线程等待锁 → 主线程被阻塞 → ANR。


八、常见 ANR 根因与规避

8.1 主线程执行 IO 操作

java 复制代码
// 错误做法:主线程读取文件
@Override
protected void onCreate(Bundle savedInstanceState) {
    FileInputStream fis = new FileInputStream("/sdcard/large_file.bin");
    fis.read(buffer);  // 可能耗时几秒 → ANR!
}

规避 :使用 AsyncTaskHandlerThreadIntentServiceRxJava 等异步方案。

8.2 主线程 Binder 同步调用

java 复制代码
// 错误做法:主线程同步调用跨进程服务
@Override
protected void onResume() {
    IRemoteService service = getService();
    service.doHeavyWork();  // 远程服务耗时长 → 主线程等待 → ANR!
}

规避 :将跨进程调用移到后台线程,或使用异步 Binder(oneway)。

8.3 BroadcastReceiver 中执行耗时操作

java 复制代码
// 错误做法:广播接收器中执行耗时操作
public class MyReceiver extends BroadcastReceiver {
    public void onReceive(Context context, Intent intent) {
        // 这个操作必须在 10s(前台)/ 60s(后台)内完成
        doNetworkRequest();  // 耗时操作 → Broadcast ANR!
    }
}

规避onReceive() 中启动 Service 处理,自身快速返回。

8.4 死锁

java 复制代码
// 经典死锁场景
// 线程A: synchronized(objA) { synchronized(objB) { ... } }
// 线程B: synchronized(objB) { synchronized(objA) { ... } }

规避 :统一锁的获取顺序,使用 java.util.concurrent 的锁工具。

8.5 预防工具:StrictMode

java 复制代码
// 开发阶段开启 StrictMode 检测
if (BuildConfig.DEBUG) {
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
        .detectDiskReads()
        .detectDiskWrites()
        .detectNetwork()
        .penaltyLog()
        .build());
}

九、ANR 定位实战流程

复制代码
1. 获取 traces.txt
   → adb pull /data/anr/traces.txt
   (如果现场已丢失,从 bugreport 中提取)

2. 定位主线程状态
   → 搜索 "Cmd line: com.example.app"
   → 找到 "main" tid=1 的状态

3. 分析阻塞原因
   ├─ Blocked → 找持有的锁和等待的锁
   ├─ Native → 看 Binder 调用栈,找对端进程
   ├─ Runnable → 看是否密集计算或等待 CPU
   └─ Waiting → 看 wait/join/sleep 调用

4. 结合 CPU 使用情况
   → traces.txt 开头有各进程的 CPU 占用
   → 判断是 CPU 不足还是逻辑阻塞

5. 查看 logcat 上下文
   → logcat -d | grep "ANR in"
   → 查看 ANR 前后 1 分钟的日志

十、总结

  1. ANR 是主线程的"超时罚单":系统给了主线程明确的时间窗口,超时就触发。

  2. 四类 ANR 各有不同的检测者:InputDispatcher(5s)、BroadcastQueue(10s/60s)、ActiveServices(20s/200s)。

  3. 前台 vs 后台的超时差异巨大:前台 5-20s,后台 60-200s------主线程问题在前台更容易暴露。

  4. traces.txt 是 ANR 分析的"宝典":线程状态 + 堆栈 + CPU 使用,三位一体定位根因。

  5. 预防胜于治疗:StrictMode + 异步化 + 代码审查,在开发阶段就规避 ANR 风险。

下一篇将分析应用异常的另一面------APK Java 层崩溃。


本文基于 AOSP 7(Android Nougat)源码编写

相关推荐
极客猴子3 小时前
Android录音转写频繁卡顿?多款APP长时间会议场景稳定性实测
android·人工智能·智能手机·飞书
Kapaseker3 小时前
Boolean 变量到底该怎么命名?
android·kotlin
雾屿_Mistisle3 小时前
文件包含漏洞(File Inclusion)
android
小孔龙4 小时前
Android 图形系统全景
android·计算机图形学
2501_915921434 小时前
详细解析,iOS 应用上架 App Store 的完整流程与指南
android·ios·小程序·https·uni-app·iphone·webview
qq_425516184 小时前
录音转文字工具免费下载:免费额度与功能限制对比
android·人工智能·智能手机·powerpoint
plainGeekDev5 小时前
Robolectric → 分层测试:测试策略重构
android·java·kotlin
plainGeekDev5 小时前
Instrumentation → Compose Testing
android·java·kotlin
sz_denny5 小时前
android aab导出apk
android