一、协程的上下文
1.1 协程的上下文是什么
CoroutineContext是一组用户定义协程行为的元素。它由如下几项构成
- Job:控制协程的生命周期
- CoroutineDispatcher: 向合适的线程分发任务
- CoroutineName: 协程的名称,调试的时候很有用
- CoroutineExceptionHandler: 处理未被捕获的异常
1.2 组合上下文中的元素
有时我们需要在协程上下文中定义多个元素。我们可以使用+操作符来实现。比如说,我们可以显式指定一个调度器来启动协程并且同时显式指定一个命名
1.3 协程上下文的继承
对于新创建的协程,它的CoroutineContext会包含一个全新的Job实例,它会帮助我们控制协程的生命周期,而剩下的元素会从CoroutineContext的父类继承,该父类可能是另外一个协程或者创建该协程的CoroutineScope。
协程的上下文 = 默认值 + 继承的CoroutineContext + 参数
- 一些元素包含默认值:Dispatchers.Default是默认的CoroutineDispatcher, 以及"coroutine"作为默认的CoroutineName;
- 继承的CoroutineContext是CoroutineScope或者其父协程的CoroutineContext;
- 传入协程构建器的参数的优先级高于继承的上下文参数,因此会覆盖对应的参数值。
最终的父级CoroutineContext会内涵Dispatchers.IO而不是scope对象里的Dispatchers.Main, 因为它被协程的构建器里的参数覆盖了。此外,注意一下父级CoroutineContext里的Job是scope对象的Job(红色),而新的Job实例(绿色)会赋值给新的协程的CoroutineContext.
二、协程的异常处理
2.1 异常处理的必要性
当应用出现一些意外情况时,给用户提供合适的体验非常重要,一方面,目睹应用崩溃是个很糟糕的体验,另一方面,在用户操作失败时,也必须要能给出正确的提示信息。
2.2 异常的传播
协程构建器有两种形式:自动传播异常(launch与actor),向用户暴露异常(async与produce)当这些构建器用于创建一个根协程时(该协程不是另一个协程的子协程),前者这类构建器,异常会在它发生的第一时间被抛出,而后者则依赖用户来最终消费异常,例如通过await或receive。
其他协程所创建的协程中,产生的异常总是会被传播
2.3 异常的传播特性
当一个协程由于一个异常而运行失败时,它会传播这个异常并传递给它的父级。接下来,父级会进行下面几步操作:
- 取消它自己的子级
- 取消它自己
- 将异常传播并传递给它的父级
2.4 SupervisorJob
- 使用SupervisorJob时,一个子协程的运行失败不会影响到其他子协程。SupervisorJob不会传播异常给它的父级,它会让子协程自己处理异常。
- 这种需求常见于在作用域内定义作业的UI组件,如果任何一个UI的子作业执行失败了,它并不总是有必要取消整个UI组件,但是如果UI组件被销毁了,由于它的结果不再被需要了,它就有必要使所有的子作业执行失败。
2.5 supervisorScope
当作业自身执行失败的时候,所有子作业将会被全部取消
2.6 异常的捕获
- 使用CoroutineExceptionHandler对协程的异常进行捕获
- 当以下条件被满足时,异常就会被捕获
- 时机:异常是被自动抛出异常的协程所抛出的(使用launch,而不是async)
- 位置:在CoroutineScope的CoroutineContext中或在一个根协程(CoroutineScope或者supervisorScope的直接子协程)中。
2.7 Android中全局异常处理
- 全局异常处理器可以获取到所有协程未处理的未捕获异常,不过它并不能对异常进行捕获,虽然不能阻止程序崩溃,全局异常处理器在程序调试和异常上报等场景中仍然有非常大的用处。
- 我们需要在classpath下面创建META-INF/services目录,并在其中创建一个名为kotlinx.coroutines.CoroutineExceptionHandler的文件,文件内容就是我们的全局异常处理器的全类名。
2.8 取消和异常
- 取消和异常紧密相关,协程内部使用CancellationException来进行取消,这个异常会被忽略
- 当子协程被取消时,不会取消它的父协程
- 如果一个协程遇到了CancellationException以外的异常,它将使用该异常取消它的父协程。当父协程的所有子协程都结束后,异常才会被父协程处理。
2.9 异常聚合
当协程的多个子协程因为异常而失败时,一般情况下取第一个异常进场处理。在第一个异常之后发生的所有其他异常,都将被绑定到第一个异常之上。