一、案件登记: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),形成完整的案件报告- 调用栈就像侦探绘制的 "嫌疑人行动路线图",从异常发生点追溯到根本原因
- 理解这些机制后,开发者可以像侦探一样,从崩溃报告中快速定位问题根源,解决应用中的 "案件"