Kotlin协程完全教程-从基础实践到进阶再到专家(已完结)

98 阅读4分钟

我们的目标是:不要一次性读完所有理论,而是围绕一个核心概念,立即动手编码。

第一步:目标驱动,而非通读驱动

不要想着“我要先看完这100页文章”。相反,你应该:

  1. 确定一个小目标:比如,“我今天要学会如何启动一个协程并取消它”。
  2. 带着问题去阅读:在文章中快速定位到相关章节(如“协程的启动与取消”),只精读这一部分。
  3. 立即实践:读完后就打开IDE,把代码敲出来,并尝试修改、调试。

第二步:拆解核心概念,逐个击破

协程的核心概念并不多。把这篇文章当作你的字典和指南,每次攻克一个:

核心概念对应文章章节你的“代码生成”目标
1. 协程是什么?基础介绍写一个最简单的 launchrunBlocking
2. 协程的上下文CoroutineContext理解 Dispatchers.IO, Dispatchers.Main 等,并切换它们。
3. 启动模式launch, asyncasync 并发执行两个任务,并获取结果。
4. 挂起函数suspend 关键字自己写一个挂起函数,模拟网络请求。
5. 异常处理异常处理在协程中使用 try-catchCoroutineExceptionHandler
6. 取消与超时取消与超时启动一个协程,然后在特定条件下取消它。
7. 流Flow创建一个简单的 Flow 并收集它。

第三步:立即实践 - 使用代码模板

下面是一个综合性的代码模板,它涵盖了上面提到的多个核心概念。你可以把这个模板复制到 Android Studio 或 IntelliJ IDEA 中运行,然后逐一取消注释来观察现象,这是最快的学习方式。

确保在 build.gradle.kts (Module: app) 中添加依赖:

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
    // ... 其他依赖
}

第四步:迭代与扩展

当你对模板中的某个部分(比如 Flow)感兴趣时,回到文章中,精读 Flow 的章节,然后在模板的基础上添加更复杂的 Flow 操作符,如 map, filter, transform 等。


Kotlin 协程实战代码模板

将以下代码复制到你的 IDE 中运行和探索。

import kotlinx.coroutines.*
import kotlin.system.measureTimeMillis

/**
 * Kotlin 协程完全教程 - 实战代码模板
 * 通过运行和修改此代码来快速学习协程。
 */
fun main() {
    // 请逐一取消下面的注释来运行和观察不同概念

    // 1. 基础:协程的启动与结构化并发
    // demoBasics()

    // 2. 并发与结果:使用 async 和 await
    // demoAsync()

    // 3. 挂起函数:模拟网络请求
    // demoSuspendFunction()

    // 4. 取消协程:理解协程的取消
    // demoCancellation()

    // 5. 异常处理:如何在协程中捕获异常
    // demoExceptionHandling()

    // 6. 流:冷数据流的基本使用
    demoFlow()
}

/**
 * 1. 基础:launch, runBlocking 和协程上下文
 */
fun demoBasics() = runBlocking { // `runBlocking` 会阻塞当前线程,直到其内部的协程执行完毕。通常用于 main 函数和测试。
    println("主协程开始 - 运行在线程: ${Thread.currentThread().name}")

    // launch: 启动一个不会返回结果的新协程(干火就行)
    val job = launch {
        // 默认继承父协程的上下文 (runBlocking 的上下文)
        println("launch 协程开始 - 运行在线程: ${Thread.currentThread().name}")
        delay(1000L) // 挂起函数,非阻塞式延迟 1 秒
        println("World!")
    }

    // 切换到 IO 线程池
    launch(Dispatchers.IO) {
        println("IO 协程开始 - 运行在线程: ${Thread.currentThread().name}")
        delay(500L)
        println("在IO线程中执行任务")
    }

    println("Hello,")
    job.join() // 等待 launch 启动的协程执行完毕
    println("主协程结束")
}

/**
 * 2. 并发:使用 async 和 await 来获取结果
 */
fun demoAsync() = runBlocking {
    println("\n--- 并发演示 ---")

    val time = measureTimeMillis {
        // async: 启动一个可以返回结果的新协程 (Deferred<T>)
        val deferred1: Deferred<Int> = async { doSomethingUsefulOne() }
        val deferred2: Deferred<Int> = async { doSomethingUsefulTwo() }

        // await() 是一个挂起函数,它会等待结果
        println("结果是: ${deferred1.await() + deferred2.await()}")
    }
    println("完成时间: ${time}ms") // 注意观察,两个任务是并发的,总时间远小于 1500ms
}

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // 模拟一个耗时 1 秒的网络请求
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(800L) // 模拟另一个耗时 800 毫秒的网络请求
    return 29
}

/**
 * 3. 挂起函数:suspend 关键字
 */
fun demoSuspendFunction() = runBlocking {
    println("\n--- 挂起函数演示 ---")
    val user = fetchUserData() // 看起来像是同步代码,但实际上是异步的!
    println("用户数据: $user")
}

// suspend 关键字标记这是一个挂起函数,它可以在协程内部被调用,内部可以调用其他挂起函数(如 delay)
suspend fun fetchUserData(): String {
    println("开始获取用户数据...")
    delay(2000L) // 模拟网络延迟
    println("数据获取完毕!")
    return "Kotlin Coroutine User"
}

/**
 * 4. 取消协程:协程的取消是协作式的
 */
fun demoCancellation() = runBlocking {
    println("\n--- 取消演示 ---")
    val job = launch {
        repeat(1000) { i ->
            println("job: 我正在睡觉 $i ...")
            delay(500L)
            // 检查协程是否处于活跃状态,如果已被取消,会抛出 CancellationException
            // 确保在循环中检查,否则协程无法被取消
        }
    }
    delay(1300L) // 延迟一段时间
    println("main: 我等够了!")
    job.cancelAndJoin() // 取消任务并等待它结束
    println("main: 现在我可以退出了。")
}

/**
 * 5. 异常处理
 */
fun demoExceptionHandling() = runBlocking {
    println("\n--- 异常处理演示 ---")

    // 方式1:传统的 try-catch
    val job1 = launch {
        try {
            delay(100L)
            throw RuntimeException("在 launch 中出错了!")
        } catch (e: Exception) {
            println("捕获到异常: ${e.message}")
        }
    }
    job1.join()

    // 方式2:CoroutineExceptionHandler (用于根协程,即最顶层的协程)
    val exceptionHandler = CoroutineExceptionHandler { _, exception ->
        println("CoroutineExceptionHandler 捕获到: ${exception.message}")
    }

    val job2 = launch(exceptionHandler) {
        throw RuntimeException("一个未被捕获的异常!")
    }
    job2.join()
}

/**
 * 6. Flow: 冷数据流,按需生产
 */
fun demoFlow() = runBlocking {
    println("\n--- Flow 演示 ---")

    // 创建一个 Flow
    simpleFlow().collect { value -> // `collect` 是一个挂起函数,用于收集 Flow 发出的值
        println("收集到的值: $value")
    }
    println("Flow 收集完成")
}

// 返回一个 Flow 对象,它可以异步地返回多个值
fun simpleFlow(): kotlinx.coroutines.flow.Flow<Int> = kotlinx.coroutines.flow.flow {
    // flow 构建器
    println("Flow 开始")
    for (i in 1..3) {
        delay(500L) // 模拟异步工作,比如从网络或数据库获取数据
        emit(i) // 发射一个值
    }
}

如何有效使用这个模板?

  1. demoBasics() 开始:逐行阅读代码,运行它,观察控制台输出的线程名和顺序。理解 launchrunBlocking 的作用。
  2. 然后尝试 demoAsync():理解 asyncawait 如何用于并发任务并聚合结果。
  3. 挑战 demoCancellation():尝试把循环中的 delay(500L) 换成 Thread.sleep(500),看看会发生什么?你会理解为什么协程的取消是“协作式”的。
  4. 精读文章对应章节:当你在代码中遇到不理解的行为时(比如为什么取消不了?为什么 Flow 是冷的?),带着这个具体问题回到《扔物线》文章中,去寻找答案。

通过这种 “小目标 -> 阅读 -> 编码 -> 调试 -> 深入阅读” 的循环,你学习协程的速度和效率会远高于被动地通读全文。祝你学习顺利!