Android 侦探事务所:Throwable 侦探的案件追踪手册

一、案件登记:Throwable 侦探的初步调查

在 Android 系统的 "犯罪现场调查科" 里,Throwable 是专门负责异常案件的侦探。当应用发生崩溃时,它会第一时间到达现场,记录案件的基本信息。

java

kotlin 复制代码
// Throwable 侦探的案件登记流程
public Throwable(String detailMessage, Throwable cause) {
    // 记录案件详情(比如"文件读取失败")
    this.detailMessage = detailMessage;
    // 记录案件的直接原因(可能是另一个侦探负责的小案件)
    this.cause = cause;
    // 初始化线索本
    this.stackTrace = EmptyArray.STACK_TRACE_ELEMENT;
    // 前往现场收集证据(调用栈)
    fillInStackTrace();
}

故事解析

  • detailMessage 是案件的简要描述,比如 "数组越界异常"
  • cause 是导致当前案件的 "幕后案件",形成案件链
  • fillInStackTrace() 就像侦探拿出笔记本,开始记录现场的线索(方法调用路径)

现场证据收集:fillInStackTrace 的侦探工作

java

kotlin 复制代码
public Throwable fillInStackTrace() {
    if (stackTrace == null) {
        return this; // 没有线索本就无法记录
    }
    // 调用本地调查手段获取原始线索(native 层的调用栈)
    stackState = nativeFillInStackTrace();
    // 清空临时线索本,准备整理正式线索
    stackTrace = EmptyArray.STACK_TRACE_ELEMENT;
    return this;
}

故事解析

  • 侦探通过 nativeFillInStackTrace() 调用 "技术科",获取底层的原始线索
  • 这些线索会被整理成 StackTraceElement 数组,就像侦探把现场脚印、指纹等证据分类记录

二、案件报告:打印堆栈的线索整理过程

侦探的案情汇报:printStackTrace 的工作流程

java

typescript 复制代码
public void printStackTrace() {
    // 向系统错误日志(System.err)汇报案情
    printStackTrace(System.err);
}

public void printStackTrace(PrintStream err) {
    try {
        // 详细汇报案情,包括线索、原因和相关案件
        printStackTrace(err, "", null);
    } catch (IOException e) {
        throw new AssertionError(); // 汇报过程出错,这是严重事故
    }
}

故事解析

  • printStackTrace() 是侦探向上级汇报案情的主要方法
  • 汇报内容会输出到系统错误日志,就像侦探提交的书面报告

详细案情报告:printStackTrace 的核心逻辑

java

scss 复制代码
private void printStackTrace(Appendable err, String indent, StackTraceElement[] parentStack) {
    // 首先输出案件基本信息(案件类型+详情)
    err.append(toString());
    err.append("\n");
    
    // 获取当前案件的所有线索(调用栈)
    StackTraceElement[] stack = getInternalStackTrace();
    if (stack != null) {
        // 计算与父案件重复的线索数量
        int duplicates = parentStack != null ? countDuplicates(stack, parentStack) : 0;
        // 输出不重复的线索
        for (int i = 0; i < stack.length - duplicates; i++) {
            err.append(indent);
            err.append("\tat "); // "在...地点发现线索"
            err.append(stack[i].toString());
            err.append("\n");
        }
        // 如果有重复线索,用"... N more"简化表示
        if (duplicates > 0) {
            err.append(indent);
            err.append("\t... ");
            err.append(Integer.toString(duplicates));
            err.append(" more\n");
        }
    }
    
    // 处理被抑制的相关案件(suppressed exceptions)
    if (suppressedExceptions != null) {
        for (Throwable throwable : suppressedExceptions) {
            err.append(indent);
            err.append("\tSuppressed: ");
            throwable.printStackTrace(err, indent + "\t", stack);
        }
    }
    
    // 追查案件的根本原因(Caused by)
    Throwable cause = getCause();
    if (cause != null) {
        err.append(indent);
        err.append("Caused by: ");
        cause.printStackTrace(err, indent, stack); // 递归汇报根本原因的案情
    }
}

故事解析

  • toString() 生成案件标题,如 "RuntimeException: 接收广播时出错"
  • getInternalStackTrace() 获取案件的所有线索(方法调用路径)
  • countDuplicates() 检查当前案件与父案件的重复线索,避免重复汇报
  • "Caused by" 部分是案件的根本原因,就像侦探发现 "小偷撬锁" 是 "屋内失窃" 的原因
  • "... N more" 是重复的线索,比如多个案件都经过相同的走廊,用省略号简化

案件标题生成:toString 方法

java

typescript 复制代码
public String toString() {
    // 获取案件详情
    String msg = getLocalizedMessage();
    // 获取案件类型(如"入室盗窃案")
    String name = getClass().getName();
    if (msg == null) {
        return name; // 没有详情时,只显示案件类型
    }
    return name + ": " + msg; // 组合成"案件类型: 详情"
}

实例

  • 案件类型:java.lang.RuntimeException
  • 案件详情:Error receiving broadcast Intent
  • 生成标题:java.lang.RuntimeException: Error receiving broadcast Intent

三、案件链分析:Caused by 和 ... more 的含义

案件链的形成:Caused by 的本质

java

php 复制代码
// 主案件:RuntimeException(接收广播错误)
java.lang.RuntimeException: Error receiving broadcast Intent
at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:1134)
at android.os.Handler.handleCallback(Handler.java:751)
...(中间线索)...
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:764)

// 根本原因:IndexOutOfBoundsException(数组越界)
Caused by: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.get(ArrayList.java:411)
at com.android.server.notification.NotificationManagerService$NotificationRankers.onUserSwitched(...)
at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:1124)
... 8 more

故事解析

  • 主案件是 "接收广播时出错",但根本原因是 "数组越界"
  • 两个案件的线索链有 8 个重复的线索(如 Handler 处理、Looper 循环等)
  • "... 8 more" 表示省略这 8 个重复线索,避免报告冗长

重复线索计算:countDuplicates 方法

java

ini 复制代码
private static int countDuplicates(StackTraceElement[] currentStack, StackTraceElement[] parentStack) {
    int duplicates = 0;
    int parentIndex = parentStack.length;
    // 从线索链的末尾开始比较(栈底)
    for (int i = currentStack.length; --i >= 0 && --parentIndex >= 0;) {
        StackTraceElement parentFrame = parentStack[parentIndex];
        // 如果线索相同,计数加一
        if (parentFrame.equals(currentStack[i])) {
            duplicates++;
        } else {
            break; // 遇到不同线索,停止比较
        }
    }
    return duplicates;
}

故事解析

  • 侦探从线索链的最后一步开始比较(比如 "离开现场的脚印")
  • 发现 8 个相同的线索(如都经过 "客厅→走廊→大门")
  • 这些重复线索在报告中用 "... 8 more" 表示,就像侦探写报告时说 "之后 8 步与之前案件相同"

四、完整案件报告实例:从异常到根本原因

实际案件报告

plaintext

css 复制代码
java.lang.RuntimeException: Error receiving broadcast Intent{}
in com.android.server.notification.NotificationManagerService$3@ef5346c
at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:1134)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at com.android.server.SystemServer.run(SystemServer.java:352)
at com.android.server.SystemServer.main(SystemServer.java:220)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:874)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:764)

Caused by: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.get(ArrayList.java:411)
at com.android.server.notification.NotificationManagerService$NotificationRankers.onUserSwitched(NotificationManagerService.java:3912)
at com.android.server.notification.NotificationManagerService$3.onReceive(NotificationManagerService.java:804)
at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:1124)
... 8 more

案件还原(展开重复线索后)

plaintext

scss 复制代码
// 主案件线索链
1. LoadedApk$ReceiverDispatcher$Args.run(1134行)
2. Handler.handleCallback(751行)
3. Handler.dispatchMessage(95行)
4. Looper.loop(154行)
5. SystemServer.run(352行)
6. SystemServer.main(220行)
7. Method.invoke( native方法)
8. ZygoteInit$MethodAndArgsCaller.run(874行)
9. ZygoteInit.main(764行)

// 根本原因线索链
1. ArrayList.get(411行)
2. NotificationRankers.onUserSwitched(3912行)
3. NotificationManagerService$3.onReceive(804行)
4. LoadedApk$ReceiverDispatcher$Args.run(1124行)
5. Handler.handleCallback(751行)  // 从这里开始与主案件线索重复
6. Handler.dispatchMessage(95行)
7. Looper.loop(154行)
8. SystemServer.run(352行)
9. SystemServer.main(220行)
10. Method.invoke(native方法)
11. ZygoteInit$MethodAndArgsCaller.run(874行)
12. ZygoteInit.main(764行)

// 报告中的省略表示
... 8 more // 表示省略第5-12步,共8个重复线索

故事总结

  • Throwable 侦探通过记录案件详情、追查根本原因(Caused by)和整理重复线索(... more),形成完整的案件报告
  • 调用栈就像侦探绘制的 "嫌疑人行动路线图",从异常发生点追溯到根本原因
  • 理解这些机制后,开发者可以像侦探一样,从崩溃报告中快速定位问题根源,解决应用中的 "案件"
相关推荐
码农果果12 分钟前
Google 提供的一组集成测试套件----XTS
android
火柴就是我15 分钟前
每日见闻之Rust中的引用规则
android
雨白29 分钟前
SQLite 数据库的事务与无损升级
android
_一条咸鱼_41 分钟前
Android Runtime即时编译触发条件与阈值深度解析(38)
android·面试·android jetpack
Aridvian43 分钟前
如何使用EventBus?
android
用户20187928316744 分钟前
SystemUI 开发实战故事:手机 "公共设施总管" 的修炼手册
android
火柴就是我1 小时前
每日见闻之Rust中的引用
android
ajassi20001 小时前
开源 java android app 开发(十一)调试、发布
android·java·linux·开源
敲代码的剑缘一心2 小时前
手把手教你学会写 Gradle 插件
android·gradle
青蛙娃娃3 小时前
漫画Android:动画是如何实现的?
android·android studio