协程最重要的就是协程上下文对象,因为通过上下文可以获取到协程相关的任何东西(Job、Dispatcher、Interceptor、Name、ExceptionHandler),所以有必要了解清楚常用的协程作用域对象中coroutineContext的组成。
lifecycleScope 的协程上下文是 SupervisorJob + Dispatchers.Main.immediate
viewModelScope 的协程上下文是 SupervisorJob + Dispatchers.Main.immediate,和lifecycleScope的一样
GlobalScope 的协程上下文是 EmptyCoroutineContex
MainScope 的协程上下文是 SupervisorJob + Dispatchers.Main
总结就是lifecycleScope、viewModelScope、MainScope这三种协程作用域的上下文组成是 SupervisorJob 加主线程调度器, 而 GlobalScope协程作用域的上下文则是一个空的上下文
CoroutineScope 有一个扩展函数 cancel() 可以用来取消内部启动的协程,比如Lifecycle 会在生命周期状态为 DESTROYED 时调用 cancel() 函数取消掉该作用域启动的协程,ViewModel 则会在 clear() 方法中调用 cancel() 函数,对于 Activity 和 Fragment 的 ViewModel ,clear() 方法也是在 destroy 回调里触发的。
来看一下 CoroutineScope.cancel() 方法的实现:
可以看到它是从协程上下文中获取Job对象,然后调用job对象的cancel方法进行取消的,而上面分析的四种Scope中,只有GlobalScope的协程上下文是空的,因此无法从中查询到Job对象,并且它的协程上下文只能获取,也不能通过+运算符设值(val属性),所以GlobalScope无法被cancel,这就是为什么在 Android 中不推荐使用GlobalScope这个作用域来启动协程的原因。
因为launch是CoroutineScope的一个扩展方法,因此以上四种作用的launch都是同一个方法,如下:
其中 newCoroutineContext(context) 返回的是 scope作用域上下文 + context参数上下文 + Dispatchers.Default(如果未指定才添加)
上面 coroutine.start 的调用涉及到运算符重载,这里会比较绕,我们只需要知道这里实际上最终会调的是 CoroutineStart.invoke() 方法,在这个方法中根据当前的启动模式去分别执行不同方法,默认的启动模式是DEFAULT, 因此这里走第一个分支。
同时注意到,DEFAULT模式下,这里startCoroutineCancellable方法的receiver和completion参数都是StandaloneCoroutine对象。 在协程体的逻辑执行完后会调用到 completion 的 resume 方法恢复后面的续体代码执行(CPS)。
StandaloneCoroutine 是 AbstractCoroutine的实现类( AbstractCoroutine中会调用initParentJob方法,与父Job建立关联,当调用cancel方法或者子Job有异常时,可以将取消或者异常事件往上传播), 重写了父类