Android-重学kotlin(协程源码第二阶段)新学习总结
一、Dispatchers是如何工作的?
概述:
CoroutineContext 是容器,ContinuationInterceptor 是其中的关键元素接口,CoroutineDispatcher 是该接口的主要实现,而 Dispatchers 是预定义调度器的工具类。

1.CoroutineContext
CoroutineContext 是一个不可变的元素集合,每个元素都实现了 Element 接口。它的核心作用是存储和传递协程的上下文信息(如调度器、Job、异常处理器等)。
public interface CoroutineContext {// 通过Key获取上下文中的元素public operator fun <E : Element> get(key: Key<E>): E?// 合并两个上下文public operator fun plus(context: CoroutineContext): CoroutineContext = ...// 遍历上下文中的所有元素public fun <R> fold(initial: R, operation: (R, Element) -> R): R// 其他方法...// 上下文中元素的Key接口public interface Key<E : Element>// 上下文元素的基接口public interface Element : CoroutineContext {public val key: Key<*>}
}- CoroutineContext 是一个接口,其默认实现是
CombinedContext(链表结构)或EmptyCoroutineContext。 - 每个元素都有唯一的
Key,用于快速查找和替换。
2.ContinuationInterceptor
ContinuationInterceptor 是 CoroutineContext 中的一种特殊元素,负责拦截协程的执行并改变其调度方式。
public interface ContinuationInterceptor : CoroutineContext.Element {companion object Key : CoroutineContext.Key<ContinuationInterceptor>// 拦截并返回一个新的Continuationpublic fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
} 它的唯一实现是 CoroutineDispatcher,即调度器是拦截器的具体实现。通过 interceptContinuation 方法,可以在协程恢复执行时插入自定义逻辑(如线程切换)。
3.CoroutineDispatcher
CoroutineDispatcher 继承自 ContinuationInterceptor,负责决定协程代码块在哪个线程或线程池执行。
public abstract class CoroutineDispatcher : ContinuationInterceptor {override val key: CoroutineContext.Key<*> get() = ContinuationInterceptor// 判断是否需要调度(即是否需要切换线程)public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true// 将任务分派到指定线程执行public abstract fun dispatch(context: CoroutineContext, block: Runnable)// 拦截协程的恢复操作,实现线程切换override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =DispatchedContinuation(this, continuation)
}dispatch方法是核心,不同的调度器(如 Main、IO、Default)通过不同的实现决定任务执行位置。DispatchedContinuation是一个包装类,负责在协程恢复时调用调度器的dispatch方法。
Dispatchers 是一个单例对象,提供常用的调度器实例,方便开发者直接使用。
public actual object Dispatchers {// 主调度器(用于UI线程,如Android的MainLooper)public actual val Main: CoroutineDispatcher get() = MainDispatcherLoader.dispatcher// 默认调度器(用于CPU密集型任务,默认使用JVM的公共线程池)public actual val Default: CoroutineDispatcher = createDefaultDispatcher()// IO调度器(用于IO密集型任务,使用弹性线程池)public actual val IO: CoroutineDispatcher = DefaultIoScheduler// 未受限调度器(立即在当前线程执行,直到第一个挂起点)public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
}- 这些调度器都是 CoroutineDispatcher 的具体实现。
- 通过
Dispatchers可以快速获取预定义的调度器,也可以自定义调度器。
阶段总结:
Kotlin协程调度机制的工作流程为:首先通过`Dispatchers`工具类获取预定义的`CoroutineDispatcher`实例(如`Dispatchers.IO`),并与其他上下文元素(如`Job`)组合成`CoroutineContext`;启动协程时,协程构建器(如`launch`)将此上下文与协程绑定,其中的`ContinuationInterceptor`(由`CoroutineDispatcher`实现)会拦截协程的`Continuation`,注入调度逻辑;
调度器通过`isDispatchNeeded()`判断是否需要切换线程,若需要则调用`dispatch()`将任务提交到目标线程/线程池(如IO线程池或UI线程);协程执行到挂起点时保存状态暂停执行,恢复时再次由调度器判断并处理线程切换;
协程结束后,相关资源(如线程池中的空闲线程)会被自动清理,整个过程通过上下文和调度器的协同工作,实现了协程在不同执行环境间的高效切换与管理。
整体关系图:
CoroutineContext (容器)
├─ Job (协程的生命周期管理)
├─ CoroutineName (协程名称)
└─ ContinuationInterceptor (拦截器接口)└─ CoroutineDispatcher (调度器实现)├─ Dispatchers.Main (UI线程调度器)├─ Dispatchers.Default (默认调度器)├─ Dispatchers.IO (IO调度器)└─ Dispatchers.Unconfined (未受限调度器)二、CoroutineScope是如何管理协程的?
1. CoroutineScope 的创建:强制绑定 Job 元素
CoroutineScope的核心特性是确保其coroutineContext中必然存在 Job 元素,这是管理协程的基础。源码中,CoroutineScope的创建通常通过两种方式保证 Job 存在:
- 显式传入包含 Job 的上下文,如
CoroutineScope(Job() + Dispatchers.Main); - 若传入的上下文不含 Job,会自动添加默认 Job(如
GlobalScope的实现中,coroutineContext默认包含EmptyCoroutineContext + Job())。
其接口定义明确依赖coroutineContext,而 Job 作为上下文的强制元素,成为 Scope 管理协程的 “根节点”:
public interface CoroutineScope {public val coroutineContext: CoroutineContext // 必然包含Job
}2. 协程创建:构建 Job 的 N 叉树结构
当通过launch、async等构建器(CoroutineScope的扩展函数)创建协程时,会触发以下源码逻辑,形成 Job 的层级关系:
- 创建协程实例:构建器会实例化
AbstractCoroutine的子类(如StandaloneCoroutine、DeferredCoroutine),这些子类是协程的具体载体,内部持有一个Job实例(子 Job)。 - 绑定父子 Job:在
AbstractCoroutine的initParentJob()方法中,会将子 Job 与CoroutineScope的 Job(根 Job)或上级协程的 Job(父 Job)绑定,核心逻辑如下:
internal abstract class AbstractCoroutine<in T>(override val context: CoroutineContext,active: Boolean
) : JobSupport(active), Continuation<T>, CoroutineStackFrame {init {// 初始化父Job关系initParentJob(context[Job]) }private fun initParentJob(parent: Job?) {if (parent != null) {// 将当前Job(子Job)注册到父Job中,形成父子关联parent.attachChild(this) }}
}每个父 Job 会通过attachChild维护一个子 Job 列表,最终所有协程的 Job 以CoroutineScope的根 Job 为顶点,形成N 叉树结构(根 Job→子 Job→孙 Job...)。
3. 取消与异常传播:基于树结构的递归机制
Job 的 N 叉树结构决定了取消事件和异常的传播规则,源码中通过JobSupport(Job 的核心实现类)的事件通知机制实现:
父 Job 取消:向下递归取消所有子 Job
当CoroutineScope的根 Job 被取消(如scope.cancel()),会触发JobSupport.cancel()方法,该方法会遍历所有子 Job 并调用其cancel(),形成递归:
internal open class JobSupport(...) : Job {override fun cancel(cause: CancellationException?) {// 标记当前Job为取消状态val token = markCancelled(cause)if (token != null) {// 遍历所有子Job,触发取消cancelChildren(cause, token) // 通知父Job当前Job已取消(向上传播)parent?.childCancelled(this, cause)}}
} 子 Job 异常:区分异常类型的双向传播
当子 Job 发生异常时,传播方向取决于异常类型:
这种传播机制本质是责任链模式的体现:每个 Job 既是子 Job 的管理者(处理向下传播),又是父 Job 的责任节点(处理向上传播),确保异常能沿着树结构扩散至整个 Scope。
- 若为
CancellationException(主动取消):仅向下传播(取消当前 Job 的所有子 Job),父 Job 会忽略该异常(源码中childCancelled方法对CancellationException直接返回false,不触发父 Job 取消)。 - 若为其他异常(如
IOException):先向下取消所有子 Job,再向上传播至父 Job,父 Job 接收异常后会触发自身取消,并继续向上传播,最终导致根 Job(CoroutineScope的 Job)取消,所有关联协程终止。
阶级总结:
CoroutineScope管理协程的核心在于其coroutineContext中必然存在的Job元素,该元素作为根节点,在协程创建时通过launch、async等构建器实例化AbstractCoroutine子类,这些子类在initParentJob()方法中会将自身持有的子Job与根Job或上级协程的父Job绑定,形成以根Job为顶点的 N 叉树结构;
当取消事件或异常发生时,基于该树结构和JobSupport的事件通知机制,CancellationException仅向下传播取消当前Job的所有子Job且父Job会忽略该异常,而其他异常则先向下取消所有子Job,再通过责任链模式向上传播至父Job,最终可能导致根Job取消,从而实现对协程的结构化管理。
