Android 异常

138 阅读2分钟

UncaughtExceptionHandler

java 中可以为当前线程设置 UncaughtExceptionHandler,也可以通过 Thread 的静态方法设置一个默认的 UncaughtExceptionHandler。

  1. 通过 Thread 的静态 getDefaultUncaughtExceptionHandler() 可以拿到设置的默认 handler

  2. 通过 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 的调用链,则出现异常时就有可能拿不到堆栈信息。