Android-重学kotlin(协程源码第二阶段)新学习总结

一、Dispatchers是如何工作的?

概述:

CoroutineContext 是容器,ContinuationInterceptor 是其中的关键元素接口,CoroutineDispatcher 是该接口的主要实现,而 Dispatchers 是预定义调度器的工具类。

1.CoroutineContext

CoroutineContext 是一个不可变的元素集合,每个元素都实现了 Element 接口。它的核心作用是存储和传递协程的上下文信息(如调度器、Job、异常处理器等)。

Kotlin 复制代码
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 中的一种特殊元素,负责拦截协程的执行并改变其调度方式。

Kotlin 复制代码
public interface ContinuationInterceptor : CoroutineContext.Element {
    companion object Key : CoroutineContext.Key<ContinuationInterceptor>
    
    // 拦截并返回一个新的Continuation
    public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
}

它的唯一实现是 CoroutineDispatcher,即调度器是拦截器的具体实现。通过 interceptContinuation 方法,可以在协程恢复执行时插入自定义逻辑(如线程切换)。

3.CoroutineDispatcher

CoroutineDispatcher 继承自 ContinuationInterceptor,负责决定协程代码块在哪个线程或线程池执行。

Kotlin 复制代码
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 是一个单例对象,提供常用的调度器实例,方便开发者直接使用。

Kotlin 复制代码
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线程);协程执行到挂起点时保存状态暂停执行,恢复时再次由调度器判断并处理线程切换;

协程结束后,相关资源(如线程池中的空闲线程)会被自动清理,整个过程通过上下文和调度器的协同工作,实现了协程在不同执行环境间的高效切换与管理。

整体关系图:

Kotlin 复制代码
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 管理协程的 "根节点":

Kotlin 复制代码
public interface CoroutineScope {
    public val coroutineContext: CoroutineContext // 必然包含Job
}

2. 协程创建:构建 Job 的 N 叉树结构

当通过launchasync等构建器(CoroutineScope的扩展函数)创建协程时,会触发以下源码逻辑,形成 Job 的层级关系:

  • 创建协程实例 :构建器会实例化AbstractCoroutine的子类(如StandaloneCoroutineDeferredCoroutine),这些子类是协程的具体载体,内部持有一个Job实例(子 Job)。
  • 绑定父子 Job :在AbstractCoroutineinitParentJob()方法中,会将子 Job 与CoroutineScope的 Job(根 Job)或上级协程的 Job(父 Job)绑定,核心逻辑如下:
Kotlin 复制代码
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...)。

  1. 取消与异常传播:基于树结构的递归机制

Job 的 N 叉树结构决定了取消事件和异常的传播规则,源码中通过JobSupport(Job 的核心实现类)的事件通知机制实现:

  • 父 Job 取消:向下递归取消所有子 Job

    CoroutineScope的根 Job 被取消(如scope.cancel()),会触发JobSupport.cancel()方法,该方法会遍历所有子 Job 并调用其cancel(),形成递归:

Kotlin 复制代码
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元素,该元素作为根节点,在协程创建时通过launchasync等构建器实例化AbstractCoroutine子类,这些子类在initParentJob()方法中会将自身持有的子Job与根Job或上级协程的父Job绑定,形成以根Job为顶点的 N 叉树结构;

当取消事件或异常发生时,基于该树结构和JobSupport的事件通知机制,CancellationException仅向下传播取消当前Job的所有子Job且父Job会忽略该异常,而其他异常则先向下取消所有子Job,再通过责任链模式向上传播至父Job,最终可能导致根Job取消,从而实现对协程的结构化管理。

相关推荐
霖002 小时前
C++学习笔记三
运维·开发语言·c++·笔记·学习·fpga开发
巴伦是只猫3 小时前
【机器学习笔记 Ⅲ】1 无监督学习
笔记·学习·机器学习
wzj_what_why_how3 小时前
Android网络层架构:统一错误处理的问题分析到解决方案与设计实现
android·架构
千里马学框架3 小时前
User手机上如何抓取界面的布局uiautomatorviewer
android·智能手机·aosp·uiautomator·布局抓取·user版本
阿巴~阿巴~4 小时前
操作系统核心技术剖析:从Android驱动模型到鸿蒙微内核的国产化实践
android·华为·harmonyos
武昌库里写JAVA4 小时前
vue3面试题(个人笔记)
java·vue.js·spring boot·学习·课程设计
hsx6664 小时前
使用 MaterialShapeDrawable 自定义各种形状的 View
android
用户2018792831675 小时前
滑动城堡的奇妙管家 ——ViewPager故事
android
用户2018792831675 小时前
📜 童话:魔法卷轴与 ScrollView 的奥秘
android
hsx6665 小时前
Kotlin 协程中的 Dispatchers
kotlin