现在Android开发中使用异步通常都用上协程了,但是我们如何处理协程中的异常呢?
在谈协程处理异常方式之前,我们先来说下协程中发生异常后会出现什么情况.
当协程发生异常后
这里分为两种
1,使用的协程Context是Job.
默认情况下创建协程Scope中的Context就是Job,
这种情况下一个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全局捕获协程中的异常