Java 在默认在线程中发生不可捕获异常时,如果不对异常进行处理,则会抛出异常,但是不会导致整个进程奔溃,这得益于异常退出JVM仅在一个线程栈中有效。而对于其他线程栈的执行来说是没有影响的。
但是在Java执行程序中,当异常发生在不同线程时,对于整个进程的返回值是不同的结果。
Java 的异常表现
当未捕获异常发生在main
入口时。
public static void main(String[] args) {
throw new RuntimeException("throw main thread exception");
}
控制台输出。
Exception in thread "main" java.lang.RuntimeException: throw main thread exception
at com.bevis.java.example.ThrowableTest.main(ThrowableTest.java:24)
Process finished with exit code 1
当异常发生在异步线程时。
public static void main(String[] args) {
new Thread(() -> {
throw new RuntimeException("throw asynchronous thread exception");
}).start();
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("exit process");
}
控制台输出。
Exception in thread "Thread-0" java.lang.RuntimeException: throw asynchronous thread exception
at com.bevis.java.example.ThrowableTest.lambda$main$0(ThrowableTest.java:7)
at java.lang.Thread.run(Thread.java:748)
exit process
Process finished with exit code 0
对比以上结果,当由main入口进入的线程发生异常时,则进程退出状态码为1(异常状态),而异步线程则返回 0 (正常状态)。
Android 的异常表现
Android 中并不是终端程序,因此不能对比状态码,但是无论是在主线程或者异步线程下,都会发生App崩溃,并抛出异常。
2020-04-08 20:45:45.618 5200-5247/com.bevis.test E/AndroidRuntime: FATAL EXCEPTION: Thread-2
Process: com.bevis.test, PID: 5200
java.lang.RuntimeException: throw asynchronism thread exception
at com.bevis.test.MainActivity$onCreate$1$1.run(MainActivity.kt:25)
UncaughtExceptionHandler 捕获
UncaughtExceptionHandler
是Java平台提供的一套对未捕获异常的处理机制,对于在线程中所发生的未捕获异常,最终都将在这里面进行处理。如果未设置则通过对应线程的 ThreadGroup
进行处理。
对应 Android 和 Java 平台的不同表现,猜测是通过该拦截进行处理。
ThreadGroup 的处理
public
class ThreadGroup implements Thread.UncaughtExceptionHandler {
...
public void uncaughtException(Thread t, Throwable e) {
// 如果 ThreadGroup 存在 parent ThreadGroup 则交由 parent 处理。否则由Thread.getDefaultUncaughtExceptionHandler 进行处理。在没有复写ThreadGroup方法的情况下,root ThreadGroup 最终还是由 getDefaultUncaughtExceptionHandler 进行处理
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
...
}
从以上可以得到,对应 root ThreadGroup最终处理该异常的还是 Thread.getDefaultUncaughtExceptionHandler
。
通过Java程序获得,该对象为null 而 Android 则为
com.android.internal.os.RuntimeInit$KillApplicationHandler@804b454
KillApplicationHandler
KillApplicationHandler
主要处理三个工作:
- 处理日志输出
- 通知AMS进行Applicatin Crash 处理
- 杀死App发生Crash的进程(多进程下其他进程不影响)
private static class LoggingHandler implements Thread.UncaughtExceptionHandler {
public volatile boolean mTriggered = false;
@Override
public void uncaughtException(Thread t, Throwable e) {
mTriggered = true;
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();
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);
}
}
}
private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
// 作为异常日志输出
private final LoggingHandler mLoggingHandler;
public KillApplicationHandler(LoggingHandler loggingHandler) {
this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
try {
// 调用 LoggingHandler 输出异常信息
ensureLogging(t, e);
// 防止重复进入异常处理
if (mCrashing) return;
mCrashing = true;
if (ActivityThread.currentActivityThread() != null) {
ActivityThread.currentActivityThread().stopProfiling();
}
// 通知AMS Application崩溃处理
ActivityManager.getService().handleApplicationCrash(
mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
} catch (Throwable t2) {
if (t2 instanceof DeadObjectException) {
} else {
try {
Clog_e(TAG, "Error reporting crash", t2);
} catch (Throwable t3) {
// Even Clog_e() fails! Oh well.
}
}
} finally {
// 杀死进程(APP退出)
Process.killProcess(Process.myPid());
System.exit(10);
}
}
private void ensureLogging(Thread t, Throwable e) {
// 避免重复触发 mLoggingHandler
if (!mLoggingHandler.mTriggered) {
try {
mLoggingHandler.uncaughtException(t, e);
} catch (Throwable loggingThrowable) {
// Ignored.
}
}
}
}
总结
对于Android在Crash的处理差异,是由于Android系统对默认未捕获进行了额外的处理,如果不希望这么处理,则可以重新对 Thread.setDefaultUncaughtExceptionHandler
进行设置,改变Android系统对未捕获异常的默认行为。