理清 Java 和 Android 的线程异常处理
问题
先看两个问题:
- 在一个纯 Java 程序(没有任何其他框架)中如果在非主线程发生了一个未捕获的异常, 整个程序会 crash 吗?
- 如果在 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");
}
|
输出:
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");
}
|
输出:
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 是否存在, 如果存在则调用 parent
的 uncaughtException
方法, 否则判断是否设置过全局的 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 之后, 应用自己又自动重启了. 这又是为什么?
参考