1.引言
kotlin的异常处理很不容易理解,看了好久,好久慢慢才明白,因为有必要写一篇文章搞清楚一下问题:
- try-catch捕获异常
- CoroutineExceptionHandler
- supervisorScope 和SupervisorJob
2.正题
先看看这个例子:
//协程异常处理 1.在协程作用域中捕捉 2.协程异常处理器
fun coroutineBuildRunBolck7() {
try {
Thread() {
throw NullPointerException()
}.start()
} catch (e: Exception) {
e.printStackTrace()
}
}
结果:运行崩溃
try-catch 只能捕捉当前线程的堆栈信息。对于非当前线程无法实现捕捉 。既然这样下面代码应该会被捕捉到:
fun coroutineBuildRunBolck7() = runBlocking(Dispatchers.IO) {
try {
launch {
throw NullPointerException()
}
} catch (e: Exception) {
e.printStackTrace()
Log.d("wangxuyang", "" + e.message)
}
}
结果:运行崩溃。此时这里应该有一个大大的问号?为什么会这样?
结论:launch启动的协程,是不会传播异常的。
此时肯定会问什么叫传播异常?
所谓的传播异常,是指能够将异常主动往外抛到启动顶层协程所在的线程。因为launch启动的协程,是不会将异常抛到线程,所以try-catch无法捕捉.
为了让这种异常能够捕捉到。协程引入了CoroutineExceptionHandler
2.2 CoroutineExceptionHandler
CoroutineExceptionHandler是协程用来解决launch启动协程,异常不传播的问题。当出现这种异常不传播问题。协程内部是如何处理的呢?
当一个协程发生了异常,它将把异常传播给它的父协程,父协程会做以下几件事:
- 取消其他子协程
- 取消自己
- 将异常传播给自己的父协程
异常最终将传播至继承结构的根部。通过该 CoroutineScope 创建的所有协程都将被取消。
//插入一张图片
让CoroutineExceptionHandler 拦截异常不传播,需要满足以下条件:
何时 :是被可以自动抛异常的协程抛出的(launch,而不是 async)
何地 :GlobalScope全局作用域.launch 启动一个根协程,或者自定义一个
所以由上可以得知。要想解决上述的异常。我们得在根协程中做异常处理。
val handler = CoroutineExceptionHandler { _, exception ->
Log.d("wangxuyang", "CoroutineExceptionHandler")
}
fun coroutineBuildRunBolck7() = runBlocking(Dispatchers.IO) {
//方式1
GlobalScope.launch(handler) {
launch {
throw NullPointerException()
}
}
//方式2,
CoroutineScope(Job()+handler)
.launch {
throw NullPointerException()
}
}
结果:打印2021-12-26 17:08:32.332 14031-14278/com.heytap.tv.myapplication D/wangxuyang: CoroutineExceptionHandler
再看一个案例:
fun coroutineBuildRunBolck7() = runBlocking(Dispatchers.IO) {
GlobalScope.launch {
launch(handler) {
launch {
throw NullPointerException()
}
}
}
}
这种依旧会崩溃。它不等同于那样try-catch到了就停止上报。协程中的传播异常,最终都会上报到根协程中。中途无法处理异常。所以上面依旧不行
2.3 supervisorScope 和 SupervisorJob
上面提到,当一个协程报异常了,会终止它的兄弟姐妹协程。这样很不合理。为此kotlin 协程开发团队提供了一个supervisorScope方法。用来解决这个问题。使用它,当子协程出现异常,不会影响其兄弟姐妹的协程运行。
val handler = CoroutineExceptionHandler { _, exception ->
Log.d("wangxuyang", "CoroutineExceptionHandler")
}
//协程异常处理 1.在协程作用域中捕捉 2.协程异常处理器
fun coroutineBuildRunBolck7() = runBlocking(Dispatchers.IO) {
CoroutineScope(Job() + handler)
.launch {
supervisorScope {
launch {
Log.d("wangxuyang", "start job1 delay")
delay(1000)
Log.d("wangxuyang", "end job1 delay")
}
launch {
Log.d("wangxuyang", "job2 throw execption")
throw NullPointerException()
}
}
}
}
输出结果:
D/wangxuyang: start job1 delay
D/wangxuyang:job2 throw execption
D/wangxuyang:CoroutineExceptionHandler
D/wangxuyang:end job1 delay
上面的例子可以使用SupervisorJob 达到同样的效果。supervisorScope ==SupervisorJob .
//协程异常处理 1.在协程作用域中捕捉 2.协程异常处理器
val handler = CoroutineExceptionHandler { _, exception ->
Log.d("wangxuyang", "CoroutineExceptionHandler")
}
with(CoroutineScope(SupervisorJob() + handler)) {
launch {
Log.d("wangxuyang", "start job1 delay")
delay(1000)
Log.d("wangxuyang", "end job1 delay")
}
launch {
Log.d("wangxuyang", "job2 throw execption")
throw NullPointerException()
}
}
以上就是我对协程异常处理的理解