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),形成完整的案件报告
  • 调用栈就像侦探绘制的 "嫌疑人行动路线图",从异常发生点追溯到根本原因
  • 理解这些机制后,开发者可以像侦探一样,从崩溃报告中快速定位问题根源,解决应用中的 "案件"
相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui