问题

先看两个问题:

  1. 在一个纯 Java 程序(没有任何其他框架)中如果在非主线程发生了一个未捕获的异常, 整个程序会 crash 吗?
  2. 如果在 Android 中发生同样的情况, app 会 crash 吗?

测试

Java

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
System.out.println("A");
new Thread() {
@Override
public void run() {
Object o = null;
System.out.println(o.toString());
}
}.start();
Thread.sleep(500);
System.out.println("B");
}

输出:

1
2
3
4
A
Exception in thread "Thread-0" java.lang.NullPointerException
at Test$1.run(Test.java:12)
B

可以看到在没有单独设置 UncaughtExceptionHandler 时, 非主线程出现了异常也不会阻断主线程的运行, 只是输出了异常.

Android

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("TAG", "A");
new Thread() {
@Override
public void run() {
Object o = null;
Log.d("TAG", o.toString());
}
}.start();
// 这里为了节目效果, 休眠了 500 毫秒
Thread.sleep(500);
Log.d("TAG", "B");
}

输出:

1
2
3
4
5
6
D/TAG: A

E/AndroidRuntime: FATAL EXCEPTION: Thread-2
Process: io.github.stefanji.playground, PID: 4815
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String java.lang.Object.toString()' on a null object reference
at ExceptionTest$1.run(ExceptionTest.java:22)

结果运行到这里 App 直接 Crash 了.

测试表明 Android 在线程的异常处理上与 Java 默认上是有区别的

Java 异常处理

Java 默认的异常处理机制流程如下图:

当一个线程抛出异常时, JVM 会调用线程的 dispatchUncaughtException 方法, 如果线程有设置 UncaughtExceptionHandler 则会调用设置的 Handler 处理; 否则判断 parent ThreadGroup 是否存在, 如果存在则调用 parentuncaughtException 方法, 否则判断是否设置过全局的 defaultUncaughtExceptionHandler, 如果设置过就调用全局的 Handler 处理; 否则就只输出.

Android 异常处理

讲道理, Android 应用层是基于 Java 的, 应该沿袭 Java 的处理机制的. 但是它却直接 Crash 了, 说明它单独设置了全局 Handler 或者为每个线程设置了 Handler. 为每个线程单独设置应该不大可能, 应用中那么多开发者自己创建的线程. 看来只有设置全局的 Handler 这条路了.

那 Android 是在哪里设置的呢? =_= 搜索大法: 在 SDK 源码中搜索 Crash 时输出的日志 E/AndroidRuntime: FATAL EXCEPTION: Thread-2....

在 Android-28 com/android/internal/os/RuntimeInit.java 中发现了踪影:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private static class LoggingHandler implements Thread.UncaughtExceptionHandler {
public volatile boolean mTriggered = false;

@Override
public void uncaughtException(Thread t, Throwable e) {
mTriggered = true;

// Don't re-enter if KillApplicationHandler has already run
if (mCrashing) return;

// 系统进程异常
if (mApplicationObject == null && (Process.SYSTEM_UID == Process.myUid())) {
Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
} else {
StringBuilder message = new StringBuilder();
// The "FATAL EXCEPTION" string is still used on Android even though
// apps can set a custom UncaughtExceptionHandler that renders uncaught
// exceptions non-fatal.
// 下面刚好能匹配到应用 Crash 时的日志
message.append("FATAL EXCEPTION: ").append(t.getName()).append("\n");
final String processName = ActivityThread.currentProcessName();
if (processName != null) {
message.append("Process: ").append(processName).append(", ");
}
message.append("PID: ").append(Process.myPid());
Clog_e(TAG, message.toString(), e);
}
}
}

LogingHandler 里只看的日志的的输出, 没有应用 Crash 相关的. 再接着看 LogingHandler 在哪里使用的, 在 RuntimeInit.java 中搜索发现在 RuntimeInit 的一个私有内部类 KillApplicationHandler 中使用了:

1
2
3
4
5
6
7
private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
private final LoggingHandler mLoggingHandler;

public KillApplicationHandler(LoggingHandler loggingHandler) {
this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
}
}

KillApplicationHandler 又是在 commonInit 类方法中创建, 然后设置为线程的全局 ExceptionHandler.

1
2
3
4
5
6
7
8
9
10
11
12
protected static final void commonInit() {
/*
* set handlers; these apply to all threads in the VM. Apps can replace
* the default handler, but not the pre handler.
*/
LoggingHandler loggingHandler = new LoggingHandler();
// setUncaughtExceptionPreHandler 这个方法 Thread 里没有呀?
Thread.setUncaughtExceptionPreHandler(loggingHandler);
// KillApplicationHandler 作为全局 Handler
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
//...
}

commonInit 是在 RuntimeInit.java 的静态 main 方法中被调用.

RuntimeInit 的入口方法(main) 会在 zygote 创建新的应用进程之后被调用. 所以 KillApplicationHandler 是在应用进程启动以后就设置的. 如果我们没有单独设置过全局的 DefaultUncaughtExceptionHandler, 那么应用中任何线程出现了未捕获的异常, 且发生异常的线程没有设置过 UnCaughtExceptionHandler 时, 异常就会走到 KillApplicationHandler 的 uncaughtException 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void uncaughtException(Thread t, Throwable e) {
try {
// 调用 loggingHandler 输出日志
ensureLogging(t, e);

// Don't re-enter -- avoid infinite loops if crash-reporting crashes.
// 已经在 crash 中时, 就不再处理了
if (mCrashing) return;
mCrashing = true;
//...
} catch (Throwable t2) {
if (t2 instanceof DeadObjectException) {
// System process is dead; ignore
} else {
try {
Clog_e(TAG, "Error reporting crash", t2);
} catch (Throwable t3) {
// Even Clog_e() fails! Oh well.
}
}
} finally {
// 通知内核杀掉应用进程
Process.killProcess(Process.myPid());
// 停止 VM
System.exit(10);
}
}

总结

Android 应用之所以遇到非主线程抛出的未捕获异常也会 Crash 的原因是, 在应用进程被创建之后, RuntimeInit 就为线程设置 KillApplicationHandler 为全局的 UncaughtExceptionHandler. KillApplicationHandler 的处理机制是先输出异常日志, 然后杀掉应用进程.

延伸

有时当应用发生异常 crash 之后, 应用自己又自动重启了. 这又是为什么?

参考