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

一、挂起函数源码分析

1.编译器转换(CPS 转换)
挂起函数,只是比普通的函数多了suspend关键字。有了这个suspend关键字以后,Kotlin编译器就会特殊对待这个函数,将其转换成一个带有Callback的函数,这里的Callback就是Continuation接口。而这个过程,我们称之为CPS转换

Kotlin 复制代码
suspend fun fetchData(url: String): String {
    delay(1000) // 挂起点1
    val result = api.request(url) // 挂起点2
    return result
}


//编译器会将其转换为带有 Continuation 参数的普通函数,函数签名变为:

fun fetchData(url: String, continuation: Continuation<String>): Any?
  • 新增的Continuation<String>参数:本质是一个 "回调对象",保存了挂起函数暂停时的执行状态(如局部变量、程序计数器、调用栈信息等),用于后续恢复执行。
  • 参数返回值变为Any?:有两种可能结果:
    1. 若函数正常执行完成(未挂起),返回实际结果(如String);
    2. 若函数需要挂起,返回一个特殊标记COROUTINE_SUSPENDED,告知调用者 "当前函数已挂起,需等待后续恢复"。

Continuation是挂起函数状态保存与恢复的核心接口,源码定义简化如下:

Kotlin 复制代码
interface Continuation<in T> {
    val context: CoroutineContext // 协程上下文(含调度器等信息)
    fun resumeWith(result: Result<T>) // 恢复执行的触发方法
}
  • context:保存协程的调度器(Dispatcher)、Job 等信息,决定恢复执行时使用哪个线程。
  • resumeWith:当挂起条件满足(如delay时间到、网络请求返回)时,通过此方法触发挂起函数的恢复,result为挂起期间的执行结果(成功或异常)。

2.状态机实现
当挂起函数经过反编译以后,它会变成由switch和label组成的状态机结构。编译器会生成一个状态枚举(如State),每个状态对应一个挂起点前后的逻辑:

Kotlin 复制代码
private enum class FetchDataState {
    INITIAL, // 初始状态(未执行)
    AFTER_DELAY, // 执行完delay后的状态
    AFTER_REQUEST // 执行完api.request后的状态
}

同时,函数会被改造为一个状态机处理函数,大致逻辑如下:

Kotlin 复制代码
fun fetchData(url: String, continuation: Continuation<String>): Any? {
    // 从continuation中获取或初始化状态机
    val stateMachine = continuation as? FetchDataStateMachine ?: FetchDataStateMachine(continuation)
    val context = continuation.context

    return when (stateMachine.state) {
        FetchDataState.INITIAL -> {
            // 第一次执行:处理初始逻辑,调用第一个挂起函数(delay)
            stateMachine.url = url // 保存局部变量
            stateMachine.state = FetchDataState.AFTER_DELAY // 更新状态
            // 调用delay,将当前状态机作为其continuation传递
            val delayResult = delay(1000, stateMachine)
            if (delayResult == COROUTINE_SUSPENDED) {
                return COROUTINE_SUSPENDED // 触发挂起
            } else {
                // 若delay未挂起(如时间为0),直接进入下一个状态
                stateMachine.state = FetchDataState.AFTER_DELAY
                // 继续执行下一段逻辑(相当于"伪恢复")
                fetchData(url, stateMachine)
            }
        }

        FetchDataState.AFTER_DELAY -> {
            // 从delay恢复后:执行第二个挂起函数(api.request)
            stateMachine.state = FetchDataState.AFTER_REQUEST
            val requestResult = api.request(stateMachine.url, stateMachine) // 传递状态机
            if (requestResult == COROUTINE_SUSPENDED) {
                return COROUTINE_SUSPENDED // 再次挂起
            } else {
                // 若request未挂起,直接进入下一个状态
                stateMachine.state = FetchDataState.AFTER_REQUEST
                fetchData(url, stateMachine)
            }
        }

        FetchDataState.AFTER_REQUEST -> {
            // 从api.request恢复后:返回结果
            val result = stateMachine.result // 从状态机中取出request的结果
            stateMachine.continuation.resumeWith(Result.success(result)) // 通知上层恢复
            null // 执行结束
        }
    }
}

// 状态机类:保存局部变量和当前状态
private class FetchDataStateMachine(
    val continuation: Continuation<String>
) : Continuation<String> by continuation {
    var state: FetchDataState = FetchDataState.INITIAL
    var url: String = "" // 保存局部变量url
    var result: String = "" // 保存api.request的结果
}

协程状态机代码:其中,when
表达式实现了协程状态机,而continuation.label 则代表了当前状态机的具体状态,continuation.label 改变一次,就代表了挂起函数被调用了一次;

3.挂起函数触发后的挂起和恢复流程

  1. 编译转换 :挂起函数被转换为带Continuation参数的函数,逻辑拆分为状态机。
  2. 初始执行 :从INITIAL状态开始,执行到第一个挂起点(如delay)。
  3. 挂起触发 :若挂起函数返回COROUTINE_SUSPENDED,当前函数返回该标记,线程释放。
  4. 等待恢复 :挂起期间,Continuation(状态机)被保存,等待条件满足(如时间到)。
  5. 恢复执行 :条件满足后,Continuation.resumeWith被调用,状态机从上次保存的状态(如AFTER_DELAY)继续执行。
  6. 执行完成 :当所有状态执行完毕,通过最上层的Continuation返回结果,整个挂起函数执行结束。

阶段总结

挂起函数因带有suspend关键字会被 Kotlin 编译器进行 CPS 转换,即转换为带有Continuation参数的普通函数,Continuation作为回调对象保存挂起时的执行状态(如局部变量、调用栈等),函数返回值变为Any?,要么是实际结果,要么是特殊标记COROUTINE_SUSPENDED表示挂起;

转换后的函数会形成由switchlabel组成的状态机结构,编译器生成对应挂起点的状态枚举,状态机通过when表达式实现状态流转,状态机类保存局部变量和当前状态,每次状态改变代表挂起函数被调用一次;

其挂起和恢复流程为:编译转换后从INITIAL状态开始执行,到挂起点时若返回COROUTINE_SUSPENDED则触发挂起并释放线程,挂起期间Continuation保存状态等待条件满足,条件满足后通过Continuation.resumeWith恢复执行,状态机从上次保存的状态继续,直至所有状态执行完毕,通过最上层Continuation返回结果。

二、lauch的启动源码分析

底层都依赖着协程构建器(Coroutine Builder)协程上下文(CoroutineContext)

1.协程启动的入口函数:CoroutionScope.launch

launchCoroutineScope的扩展函数,用于启动一个不返回结果的协程。其源码定义如下:

Kotlin 复制代码
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    // 获取当前协程作用域的上下文
    val newContext = newCoroutineContext(context)
    // 创建一个新的协程实例(根据不同的启动模式)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block)
    else
        StandaloneCoroutine(newContext, active = true)
    // 根据启动模式决定是立即执行还是延迟执行
    coroutine.start(start, coroutine, block)
    return coroutine
}
  1. context: CoroutineContext

    协程上下文,包含调度器(Dispatcher)、Job、异常处理器等元素。默认值为EmptyCoroutineContext,表示使用父协程的上下文。

  2. start: CoroutineStart

    协程启动模式,枚举类型,主要包括:

    • DEFAULT:立即调度协程执行
    • LAZY:懒启动,需显式调用start()join()触发
    • ATOMIC:立即执行,但在调度前无法取消
    • UNDISPATCHED:立即在当前线程执行,直到第一个挂起点
  3. block: suspend CoroutineScope.() -> Unit

    协程体,是一个挂起函数,可在其中调用其他挂起函数。

2.协程上下文的构建与继承

newCoroutineContext(context)是关键方法,用于合并当前作用域的上下文和用户传入的上下文:

Kotlin 复制代码
public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
    // 获取当前作用域的上下文(如GlobalScope、lifecycleScope的上下文)
    val combined = this.coroutineContext + context
    // 如果没有显式指定Job,则添加一个父Job(确保结构化并发)
    val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
    return if (combined[Job] != null) debug else debug + Job()
}

关键点:

  • 上下文合并 :通过+操作符合并上下文元素,相同类型的元素后添加的会覆盖前面的(如重复指定 Dispatcher)。
  • 默认 Job:若用户未显式指定 Job,会自动添加一个,确保协程有父子关系,实现结构化并发。

3.协程示例的创建与启动模式

根据start参数的不同,会创建不同类型的协程实例:

Kotlin 复制代码
val coroutine = if (start.isLazy)
    LazyStandaloneCoroutine(newContext, block)
else
    StandaloneCoroutine(newContext, active = true)

两种协程实现:

  1. LazyStandaloneCoroutine

    用于LAZY模式,协程体不会立即执行,直到调用start()join()

  2. StandaloneCoroutine

    用于其他模式,协程体会立即调度执行。

4.协程的真正启动:coroutine.start()

start()方法是协程执行的核心入口,源码如下:

Kotlin 复制代码
public fun <R, T> CoroutineStart.invoke(coroutine: R, block: suspend R.() -> T) {
    when (this) {
        DEFAULT -> coroutine.startCoroutineCancellable(block, coroutine)
        ATOMIC -> coroutine.startCoroutine(block, coroutine)
        UNDISPATCHED -> coroutine.startCoroutineUndispatched(block, coroutine)
        LAZY -> Unit // 懒启动模式下不执行,等待用户显式调用start()
    }
}

不同启动模式的差异:

  1. DEFAULT模式

    调用startCoroutineCancellable(),允许在调度前取消协程:

    Kotlin 复制代码
    public fun <T> (suspend () -> T).startCoroutineCancellable(
        completion: Continuation<T>
    ) {
        createCoroutineUnintercepted(completion).intercepted().resumeCancellableWith(Result.success(Unit))
    }
  2. ATOMIC模式

    调用startCoroutine(),不允许在调度前取消:

    Kotlin 复制代码
    public actual fun <T> (suspend () -> T).startCoroutine(
        completion: Continuation<T>
    ) {
        createCoroutineUnintercepted(completion).intercepted().resumeWith(Result.success(Unit))
    }
  3. UNDISPATCHED模式

    调用startCoroutineUndispatched(),直接在当前线程执行直到第一个挂起点:

    Kotlin 复制代码
    internal fun <R, T> (suspend R.() -> T).startCoroutineUndispatched(
        receiver: R,
        completion: Continuation<T>
    ) {
        val continuation = createCoroutineUnintercepted(receiver, completion)
        val context = continuation.context
        val uCont = continuation.intercepted()
        if (context[ContinuationInterceptor] == Dispatchers.Unconfined) {
            // 无限制调度器:直接在当前线程执行
            uCont.resumeWith(Result.success(Unit))
        } else {
            // 其他调度器:先切换到指定线程再执行
            with(context) {
                uCont.resumeWith(Result.success(Unit))
            }
        }
    }

5.async与launch的源码核心差异

asynclaunch的源码结构相似,但有两个关键区别:

Kotlin 复制代码
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    // 创建一个携带结果的协程(继承自Job,但多了结果缓存和await()方法)
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block)
    else
        DeferredCoroutine<T>(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

差异点:

  1. 返回值类型

    • launch返回Job(只能控制生命周期,无法获取结果)
    • async返回Deferred<T>(继承自Job,但可通过await()获取结果)
  2. 结果缓存
    DeferredCoroutine内部维护了一个Result<T>缓存,确保await()可重复获取结果,且支持多协程并发等待。

阶段总结

Kotlin 协程的启动依赖于协程构建器和协程上下文,其中CoroutineScope.launch作为入口函数,通过合并当前作用域上下文与用户传入的上下文创建新的协程上下文,若未显式指定 Job 则自动添加以确保结构化并发;

根据启动模式(如 DEFAULT 立即调度、LAZY 懒启动、ATOMIC 立即执行但调度前不可取消、UNDISPATCHED 立即在当前线程执行至首个挂起点)创建不同的协程实例(如 LazyStandaloneCoroutine 或 StandaloneCoroutine),并通过coroutine.start()触发协程执行,不同启动模式对应不同的执行策略;

asynclaunch源码结构相似,但async返回继承自 Job 的 Deferred<T>类型,可通过 await () 获取结果,而launch仅返回 Job 用于控制生命周期。

相关推荐
Kapaseker7 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
A0微声z2 天前
Kotlin Multiplatform (KMP) 中使用 Protobuf
kotlin
alexhilton3 天前
使用FunctionGemma进行设备端函数调用
android·kotlin·android jetpack
lhDream3 天前
Kotlin 开发者必看!JetBrains 开源 LLM 框架 Koog 快速上手指南(含示例)
kotlin
RdoZam3 天前
Android-封装基类Activity\Fragment,从0到1记录
android·kotlin
Kapaseker3 天前
研究表明,开发者对Kotlin集合的了解不到 20%
android·kotlin
郑州光合科技余经理4 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
feifeigo1234 天前
matlab画图工具
开发语言·matlab
西岸行者4 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
dustcell.4 天前
haproxy七层代理
java·开发语言·前端