总结下如何处理Android协程的异常

557 阅读2分钟

现在Android开发中使用异步通常都用上协程了,但是我们如何处理协程中的异常呢?

在谈协程处理异常方式之前,我们先来说下协程中发生异常后会出现什么情况.

当协程发生异常后

这里分为两种

1,使用的协程Context是Job.

默认情况下创建协程Scope中的Context就是Job,

image.png

这种情况下一个Scope中的某个协程发生异常,这个Scope中的所有Child都会取消,并且异常会向上传递。如果不希望某个子协程发生异常所有的协程都取消的话就要用到SupervisorJob

2,使用的协程Context是SupervisorJob.

如果我们创建CoroutineScope使用CoroutineScope(SupervisorJob()),那么它的子协程发生异常对别协程是没有影响的.

如何处理协程的异常

启动协程有两种方式,一种launch另外一种的async. 这两种启动方式处理异常是有区别的。

使用try catch

当我们使用launch创建协程的时候可以直接使用try catch去捕获异常

scope.launch {  
    try {  
        codeThatCanThrowExceptions()  
    } catch(e: Exception) {  
        // Handle exception  
    }  
}

使用async的时候,try catch需要放在启动的时候调用(这里调用await代表开始允许协程)

supervisorScope {
    val deferred = async {
        codeThatCanThrowExceptions()
    }    try {
        deferred.await()
    } catch(e: Exception) {
        // 在这里处理异常
    }
}

注意这里使用了supervisorScope,如果我们使用coroutineScope,情况会有些不同

coroutineScope {
    try {
        val deferred = async {
            codeThatCanThrowExceptions()
        }
        deferred.await()
    } catch(e: Exception) {
        // 这里不会捕获异常,会传递异常到scope
    }
}

至于这种情况我们需要处理coroutineScope抛出的异常就要用到CoroutineExceptionHandler了.

使用CoroutineExceptionHandler

CoroutineExceptionHandler也是属于Coroutine Context中的一个元素,用于处理协程中抛出的未处理异常.

val handler = CoroutineExceptionHandler {
    context, exception -> println("Caught $exception")
}

举个例子如何使用它

val scope = CoroutineScope(Job())
scope.launch(handler) {
    launch {
        throw Exception("Failed coroutine")
    }
}

注意CoroutineExceptionHandler必须要放在正确的位置才能捕获异常,例如放在如下的位置是不能捕获住异常的.

val scope = CoroutineScope(Job())
scope.launch {
    launch(handler) {
        throw Exception("Failed coroutine")
    }
}

因为之前提到使用CoroutineScope中的异常是具有向上传递的. 除非我们这里换成SupervisorJob。

val scope = SupervisorJob(Job())
scope.launch {
    launch(handler) {
        throw Exception("Failed coroutine")
    }
}

总结

通常我们之前写程序过程中都是在使用try catch在捕获异常,但是当我们使用协程后情况就有些不同,我们要考虑异常的传递性,还需要考虑是否需要使用CoroutineExceptionHandler全局捕获协程中的异常