第一章:Kotlin协程基础——从概念到实战

协程,说白了就是轻量级的线程。你想想看,线程切换开销大到吓人,而协程的挂起几乎不消耗资源。我在项目中第一次用协程时,以为它就是个语法糖,直到看到内存占用从几百MB降到了几十MB……嗯,从那以后我就再也不敢小看它了。

1.1 协程的核心概念

协程不是线程,它是一种可挂起的计算。你可以挂起一个协程,等某个耗时操作(比如网络请求)完成后恢复它,而不会阻塞当前线程。Kotlin 的协程完全由编译器处理挂起和恢复,你只需要在函数前加一个 suspend 关键字。

关键区别:线程是操作系统调度的,协程是用户态调度的。一个线程可以运行成千上万个协程,这就是“轻量级”的真正含义。

1.2 协程作用域:GlobalScope

GlobalScope 是整个应用级别的协程作用域。它的生命周期与进程一致,应用不退出,它就一直活。

// 不推荐在生产代码中使用
GlobalScope.launch {
    delay(1000L)
    println("Hello from global scope")
}
我曾经在早期项目里大量使用 GlobalScope,结果协程泄漏导致内存吃紧。你的协程启动后如果无人接管,它可能永远不结束。所以现在我的习惯是:除非写测试或快速验证,否则别碰 GlobalScope。

1.3 阻塞桥接:runBlocking

runBlocking 会阻塞当前线程直到内部协程执行完毕。它通常用在 main 函数或单元测试中,作为普通代码和协程世界的桥梁。

fun main() {
    runBlocking {
        delay(1000L)
        println("runBlocking 里的协程执行完")
    }
    println("main 线程继续")
}
测试协程代码时,我经常用 runBlocking 替代 Thread.sleep()。但要记住:把它用在 Android 主线程里?那就等着 ANR 吧——这坑我踩过。

1.4 启动协程:launch

launch 会启动一个新协程并立即返回一个 Job 对象。它不等待协程结束,所以属于“fire-and-forget”风格。如果你需要等待,可以用 join()。

runBlocking {
    val job = launch {
        delay(500L)
        println("launch 任务完成")
    }
    job.join()
    println("等待结束后执行")
}

1.5 带返回值的协程:async

async 与 launch 类似,但它返回 Deferred<T>,你可以在需要的时候调用 await() 获取结果。这很适合并行执行多个任务再汇总结果。

runBlocking {
    val deferred = async {
        delay(1000L)
        "结果来了"
    }
    println("等待结果...")
    val result = deferred.await()
    println(result)
}

1.6 launch vs async 对比

特性launchasync
返回值JobDeferred<T>
是否等待结果不等待,用 join()用 await() 获取
典型场景日志、通知、无返回值操作网络请求、计算、需要结果的任务
结构化并发作用域结束后自动取消同样遵守结构化并发

1.7 结构化并发原则

协程必须在一个作用域内启动,这样当作用域结束时,所有子协程都会被自动取消。这就是 Kotlin 协程的结构化并发。GlobalScope 之所以危险,就是因为它不属于任何作用域,容易失控。

// 推荐的方式:使用 coroutineScope 或 supervisorScope
suspend fun fetchData() = coroutineScope {
    launch { doTask1() }
    launch { doTask2() }
}
结构化并发让协程变得可管理、可预测。你只需关心作用域的生命周期,剩下的交给协程框架。
Kotlin 协程基础概念图 协程(轻量级线程) suspend 挂起/恢复 全局作用域 GlobalScope (不推荐业务使用) 阻塞桥接 runBlocking (阻塞当前线程) 协程构建器 launch (返回Job,不等待) async (返回Deferred,可等待) 结构化并发 作用域取消自动退出 注意事项 · GlobalScope 不推荐用于业务代码 · runBlocking 仅在测试/ main 中使用 · launch/async 应配合结构化作用域 经验之谈 · 用 coroutineScope 包裹异步代码 · 协程挂起不阻塞线程,性能极高 · 记住:async+await 可以并行
我个人习惯在测试中使用 runTest 替代 runBlocking,它更智能,能自动处理延时。但别急,后续章节会专门讲测试,这里先打好基础。

好了,第一章就这些。协程的概念其实不复杂——轻量、可挂起、结构化。后面我们会把这些基础运用到真正的异步测试中,你会发现,掌握这些,Flow 什么的也就水到渠成了。