Skip to content

协程快速上手

源:Kotlin 协程官方指南

Kotlin 协程不仅是轻量级线程,更是一种非阻塞式的编程范式。在开始深入底层原理之前,我们需要掌握如何在实际项目中正确地启动和管理它们。

环境准备

协程核心库分为两个部分。对于大多数 Android 项目,需要引入以下依赖:

kotlin
dependencies {
    // 协程核心库(包含标准 API、Flow 等)
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
    // Android 平台支持(提供 Dispatchers.Main 等)
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
}

你的第一个协程

启动协程的最基本方式是使用 launchasync 构建器。

kotlin
// 在一个作用域中启动协程
fun main() = runBlocking { // runBlocking 常用于 main 函数,阻塞直到内部执行完
    launch { // 启动一个新协程
        delay(1000L) // 非阻塞等待 1 秒
        println("World!")
    }
    println("Hello,")
}
kotlin
fun main() = runBlocking {
    val deferred = async {
        delay(1000L)
        "Kotlin Coroutines"
    }
    // 模拟其他工作...
    println("Waiting for result...")
    // 调用 await() 获取结果(会挂起直到结果就绪)
    println("Received: ${deferred.await()}")
}

协程的三大支柱

要玩转协程,必须理解以下三个核心概念:

1. 作用域 (CoroutineScope)

协程必须依附于一个作用域,作用域决定了协程的生命周期。

  • UI 开发:在 Android 中常用 lifecycleScopeviewModelScope,它们会在组件销毁时自动取消协程。
  • 通用开发:使用 coroutineScopeMainScope

2. 上下文 (CoroutineContext)

它是协程的“数据包”,包含了协程的名字、Job 以及最关键的调度器。

3. 调度器 (Dispatchers)

调度器决定了协程在哪个线程上运行:

  • Dispatchers.Main:UI 线程。用于更新视图。
  • Dispatchers.IO:IO 线程池。用于网络请求、文件读写。
  • Dispatchers.Default:计算密集型线程池。用于复杂算法或 JSON 解析。

实战:线程切换与异步请求

在 Android 开发中,最常见的场景是:在后台请求数据,然后切回主线程显示。

kotlin
class MyViewModel : ViewModel() {
    fun loadUserData() {
        // viewModelScope 自动在 ViewModel 清理时取消协程
        viewModelScope.launch {
            try {
                // 1. 在 IO 线程执行耗时操作
                val user = withContext(Dispatchers.IO) {
                    api.fetchUser() // 挂起函数
                }
                // 2. 自动恢复到主线程,安全更新 UI
                userNameView.text = user.name
            } catch (e: Exception) {
                // 3. 异常处理
                showError(e)
            }
        }
    }
}

挂起函数:协程的原子

如果你想把一段异步代码抽离出来,必须使用 suspend 关键字。

kotlin
// 挂起函数只能在协程或其他挂起函数中调用
suspend fun fetchFromNetwork(): String {
    return withContext(Dispatchers.IO) {
        // 执行实际网络操作
        "Network Result"
    }
}

编写准则

挂起函数应该是主线程安全的。这意味着即使在主线程调用它,它也应该通过 withContext 内部切换线程,而不应该阻塞主线程。