UncaughtExceptionHandler
java 中可以为当前线程设置 UncaughtExceptionHandler,也可以通过 Thread 的静态方法设置一个默认的 UncaughtExceptionHandler。
-
通过 Thread 的静态 getDefaultUncaughtExceptionHandler() 可以拿到设置的默认 handler
-
通过 Thread 的实例方法 getUncaughtExceptionHandler() 可以拿到当前线程设置的 handler,如果
没有会返回默认的 handler
普通代码
当普通程序出现异常时,会调用到 Thread::dispatchUncaughtException
public final void dispatchUncaughtException(Throwable e) {
// BEGIN Android-added: uncaughtExceptionPreHandler for use by platform.
// 先调用 pre 处理
Thread.UncaughtExceptionHandler initialUeh =
Thread.getUncaughtExceptionPreHandler();
if (initialUeh != null) {
try {
initialUeh.uncaughtException(this, e);
} catch (RuntimeException | Error ignored) {
// Throwables thrown by the initial handler are ignored
}
}
// END Android-added: uncaughtExceptionPreHandler for use by platform.
// 再调用 UncaughtExceptionHandler 处理
getUncaughtExceptionHandler().uncaughtException(this, e);
}
在进程启动时会调用 RuntimeInit::commonInit(),该方法会设置上面用到的两个 handler: 前者输出异常堆栈,后者 kill 进程。当我们只调用后者时,它也会首先调用 LoggingHandler::uncaughtExceptionPreHandler() 保证会输出异常堆栈。
// RuntimeInit::commonInit() 节选
LoggingHandler loggingHandler = new LoggingHandler();
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
因此,当我们在代码中重新设置了 UncaughtExceptionHandler 且没有调用旧的 handler 时,应用虽然会崩,但进程不会被杀死(仅对 android 模拟器而言,各厂商会修改 rom,导致该结论不成立)。
协程
协程天然带 try-catch,为何? 因为协程会切线程,而且在最外层的 try-catch 并不能 catch 住内层切完线程后抛出的异常,为了让 CoroutineExceptionHandler 生效,只能在各层添加 try-catch,并将捕获的异常交由 CoroutineExceptionHandler 处理。
协程的异常处理最终会调用到 CoroutineExceptionHandler.kt 中的 handleCoroutineException() 方法,如下
public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
// Invoke an exception handler from the context if present
// 如果自定义有 CoroutineExceptionHandler,就调用它的 handleException() 处理
// 如果处理过程中出现异常或没有定义 handler,则调用 handleUncaughtCoroutineException() 处理
try {
context[CoroutineExceptionHandler]?.let {
it.handleException(context, exception)
return
}
} catch (t: Throwable) {
handleUncaughtCoroutineException(context, handlerException(exception, t))
return
}
// If a handler is not present in the context or an exception was thrown, fallback to the global handler
handleUncaughtCoroutineException(context, exception)
}
// handleUncaughtCoroutineException() 会调用 propagateExceptionFinalResort()
// 它最终是拿当前线程的 UncaughtExceptionHandler 进行处理
internal actual fun propagateExceptionFinalResort(exception: Throwable) {
// use the thread's handler
val currentThread = Thread.currentThread()
currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception)
}
从上面逻辑可以发现,协程的异常处理依赖当前线程的 UncaughtExceptionHandler,如果应用中没有指定自己的 UncaughtExceptionHandler,那么协程出现异常时也会打印出堆栈,并且 kill 掉进程
自定义 UncaughtExceptionHandler
一旦自定义了 UncaughtExceptionHandler,一定要调用旧的 UncaughtExceptionHandler,这样才能保证在出现异常时,异常堆栈可以正常打印且进程被杀死。
一旦打破 UncaughtExceptionHandler 的调用链,则出现异常时就有可能拿不到堆栈信息。