协程

Feb. 03, 2026 / #Android #Kotlin #协程
  1. 轻量:占用内存少
  2. 减少回调:以同步的方式执行异步代码(会将函数挂起等待结果)

状态机原理

suspend fun getData(): String {
    delay(1000)
    return "hello coroutines"
}

反编译

// suspend挂起函数,会主动在方法中添加Continuation类型参数
@Nullable
public final Object getData(@NotNull Continuation $completion) {
    Continuation $continuation;
    label20: {
        /**
​         * 如果传入的Continuation是<undefinedtype>类型,说明是第一次调用
​         * 如果不是,则说明是恢复调用,从$continuation中获取状态机的状态
​         */
​        ​if ($completion instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)$completion;
            if (($continuation.label & Integer.MIN_VALUE) != 0) {
                $continuation.label -= Integer.MIN_VALUE;
                break label20;
            }
        }

        $continuation = new ContinuationImpl($completion) {
            // $FF: synthetic field
            Object result; // 存储中间结果
            int label; // 标签状态

            // 自己invoke方法,实现状态机恢复逻辑
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
                this.result = $result;
                this.label |= Integer.MIN_VALUE;
                return MainActivity.this.getData((Continuation)this);
            }
        };
    }
    
    // 开始执行逻辑
    Object $result = $continuation.result;
    Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
    // 根据continuation的标签位,进行对应的操作
    switch ($continuation.label) {
        case 0:
            // 检查结果是否是异常,如果是则抛出
            ResultKt.throwOnFailure($result);
            // 设置下一个标签
            $continuation.label = 1;
            // 执行delay挂起操作,delay操作里面会执行continuation.resumeWith(Result.success(Unit))操作,这里会调用continuation的invokeSuspend方法
            if (DelayKt.delay(1000L, $continuation) == var4) {
                return var4;
            }
            break;
        case 1:
            // 继续检查,无误跳出switch,执行后续逻辑
            ResultKt.throwOnFailure($result);
            break;
        default:
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
    }

    return "hello coroutines";
}

上述操作被称为挂起函数的CSP操作

协程依赖于底层的状态机实现挂起和恢复,当遇到挂起点时,会去将当前协程挂起,并更新其内部的状态,将continuationlabel标签设置为下一个要执行的代码块编号,当要恢复的时候,协程内部会调用​**continuationresumWith方法,去恢复协程运行,状态机根据保存的label值跳转到对应位置继续执行后续代码。**

挂起函数

delay

delay(1000)

挂起1s

public suspend fun delay(timeMillis: Long) {
    if (timeMillis <= 0) return // don't delay
    return suspendCancellableCoroutine sc@ { ​cont: CancellableContinuation<Unit> ->
​        ​// if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.
        if (timeMillis < Long.MAX_VALUE) {
            cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
        }
    }
}

withContext

withContext(Dispatchers.IO) {
​    ​
}

切换协程执行上下文(改变其运行的线程)

async和await

val dataDeferred = GlobalScope.async {
    "xxx"
}
val data = dataDeferred .await()

开启并发协程并获取结果

launch

GlobalScope.launch ​{
​    ​
}

并不是传统的挂起函数,可以设置协程上下文,以及协程启动方式

协程上下文

  1. Dispatchers.Main:Android专属,主线程,协程代码通过封装成Runnable执行
  2. Dispatchers.IO:IO密集,网络请求,数据库操作,文件读取等IO操作
  3. Dispatchers.Default:CPU密集,如大量计算,处理数据(注意:不能放到IO里面,会阻塞网络请求)
  4. Dispatchers.Unconfined:纯挂起逻辑、无耗时的协程调度
  5. Dispatchers.Main.immediate:Android专属,主线程内的挂起后立即执行逻辑

注意:协程中唯一推荐的切换上下文的方法就是withContext

协程的启动方式

  1. DEFAULT - 立即调度
launch(start = CoroutineStart.DEFAULT) {
    // 创建后立即进入调度队列
}
  1. LAZY - 懒启动
val job = launch(start = CoroutineStart.LAZY) {
    // 需要手动启动
}
job.start()  // 启动协程,进入 ACTIVE 状态
  1. ATOMIC - 原子启动(不可取消)
launch(start = CoroutineStart.ATOMIC) {
    // 启动后立即执行,不可取消
}
  1. UNDISPATCHED - 立即在当前线程执行
launch(start = CoroutineStart.UNDISPATCHED) {
    // 立即在当前线程执行,直到第一个挂起点
}

runBlocking

runBlocking ​{

}

阻塞线程的挂起函数

生命周期

New (新建) → Active (活跃) → Completing (完成中) → Completed (已完成)
                ↓
            Cancelling (取消中) → Cancelled (已取消)
suspend fun getData() {
    val job = CoroutineScope(Dispatchers.IO).launch(
        // 懒启动
        start = CoroutineStart.LAZY
​    ​) {
​        ​Log.d(TAG, "getData() is running..." + Thread.currentThread().name)
    }

​    ​// 来启动协程,此时其状态为NEW

    val job1 = CoroutineScope(Dispatchers.IO).launch​ ​{
​        ​// 处于ACTIVE状态
        Log.d(TAG, "getData() is running..." + Thread.currentThread().name)

        delay(1000) // 挂起点,仍然处于ACTIVE状态

        Log.d(TAG, "getData() is running..." + Thread.currentThread().name)

        // 如果没有异常和取消,进入 COMPLETING状态
    }

​    ​val job2 = CoroutineScope(Dispatchers.IO).launch​ ​{
​        ​try {
            repeat(100) { ​i ->
​                ​Log.d(TAG, "job: I am $i")
                delay(500)
            }
​        ​} finally {
            // 协程取消仍然会执行
            Log.d(TAG, "job: I am running finally")

            withContext(NonCancellable) {
​                ​delay(1000)
            }
​        ​}
    }

​    ​delay(1300)
    job2.cancel() // 取消协程,进入 CANCELING 状态
    job2.join() // 等待协程结束,进入 CANCELLED 状态
}

状态监听

val job = launch ​{
​    ​// 协程执行
}

// 1. 监听完成
job.invokeOnCompletion { ​cause ->
​    ​println("协程完成,原因: $cause")
}

// 2. 检查状态
println("是否活跃: ${job.isActive}")
println("是否完成: ${job.isCompleted}")
println("是否取消: ${job.isCancelled}")

// 3. 等待完成
runBlocking ​{
​    ​job.join()  // 挂起直到协程完成
}

协程作用域

@Suppress("FunctionName")
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
    ContextScope(if (context[Job] != null) context else context + Job())

定义协程的生命周期以及执行的上下文

  1. 生命周期管理:作用域取消时,内部所有协程自动取消
  2. 结构化并发:组织协程的父子关系
  3. 上下文传播:提供默认的调度器、异常处理器等

GlobalScope

生命周期与应用一样,尽量避免使用,容易造成内存泄漏

MainScope

用于非Android环境的主线程作用域

private val scope = MainScope()  // ​Dispatchers.Main + SupervisorJob()

ViewModelScope

适用于viewmodel的作用域

implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0")
viewModelScope.launch ​{
​    ​// 里面的内容会随着viewmodel被clear而取消
}

class MainViewModel: ViewModel() {
    val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    
    // 如果要自定义的话,记得取消作用域
    override fun onCleared() {
        super.onCleared()
        scope.cancel()
    }
}

LifecycleScope

适用于activity的作用域

implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.0")
lifecycleScope.launch ​{
​    ​// 里面的内容会随着activity被destory而取消
}

// 以下方法在生命周期离开对应状态时会挂起协程
lifecycleScope.launchWhenCreated { }   // CREATED状态
lifecycleScope.launchWhenStarted { }   // STARTED状态  ​
lifecycleScope.launchWhenResumed { }   // RESUMED状态

自定义Scope

class MyCustomScope : CoroutineScope {
    private val job = SupervisorJob()

    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job + CoroutineName("MyScope")

    fun cleanup() {
        job.cancel()  // 取消所有子协程
    }
}

SupervisorJob vs Job

// 1. 使用 Job(默认)- 子协程异常会传播
val scope1 = CoroutineScope(Job() + Dispatchers.Main)
scope1.launch {
    launch ​{
​        ​throw Exception()  // 会取消父协程和其他子协程
    }
​    ​launch ​{
​        ​delay(1000)  // 这个不会执行,因为被异常取消了
    }
}

// 2. 使用 SupervisorJob - 子协程异常隔离
val scope2 = CoroutineScope(SupervisorJob() + Dispatchers.Main)
scope2.launch {
    launch ​{
​        ​throw Exception()  // 只影响自己
    }
​    ​launch ​{
​        ​delay(1000)  // 这个正常执行
        println("我还活着")
    }
}