我们的目标是:不要一次性读完所有理论,而是围绕一个核心概念,立即动手编码。
第一步:目标驱动,而非通读驱动
不要想着“我要先看完这100页文章”。相反,你应该:
- 确定一个小目标:比如,“我今天要学会如何启动一个协程并取消它”。
- 带着问题去阅读:在文章中快速定位到相关章节(如“协程的启动与取消”),只精读这一部分。
- 立即实践:读完后就打开IDE,把代码敲出来,并尝试修改、调试。
第二步:拆解核心概念,逐个击破
协程的核心概念并不多。把这篇文章当作你的字典和指南,每次攻克一个:
| 核心概念 | 对应文章章节 | 你的“代码生成”目标 |
|---|---|---|
| 1. 协程是什么? | 基础介绍 | 写一个最简单的 launch 和 runBlocking。 |
| 2. 协程的上下文 | CoroutineContext | 理解 Dispatchers.IO, Dispatchers.Main 等,并切换它们。 |
| 3. 启动模式 | launch, async | 用 async 并发执行两个任务,并获取结果。 |
| 4. 挂起函数 | suspend 关键字 | 自己写一个挂起函数,模拟网络请求。 |
| 5. 异常处理 | 异常处理 | 在协程中使用 try-catch 和 CoroutineExceptionHandler。 |
| 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) // 发射一个值
}
}
如何有效使用这个模板?
- 从
demoBasics()开始:逐行阅读代码,运行它,观察控制台输出的线程名和顺序。理解launch和runBlocking的作用。 - 然后尝试
demoAsync():理解async和await如何用于并发任务并聚合结果。 - 挑战
demoCancellation():尝试把循环中的delay(500L)换成Thread.sleep(500),看看会发生什么?你会理解为什么协程的取消是“协作式”的。 - 精读文章对应章节:当你在代码中遇到不理解的行为时(比如为什么取消不了?为什么
Flow是冷的?),带着这个具体问题回到《扔物线》文章中,去寻找答案。
通过这种 “小目标 -> 阅读 -> 编码 -> 调试 -> 深入阅读” 的循环,你学习协程的速度和效率会远高于被动地通读全文。祝你学习顺利!