一篇讲透掌握 Kotlin 协程

想系统掌握 Kotlin 协程,要层层递进学习,既覆盖新手必备的基础,也包含高级开发中常用的进阶知识点。

一、基础入门:从零开始理解协程

1. 协程的本质(再强化认知)

协程是 可暂停、可恢复的计算过程 ,由 Kotlin 协程框架(kotlinx-coroutines)管理,而非操作系统内核:

  • 线程:内核态,切换成本高(MB 级栈空间);
  • 协程:用户态,切换成本低(KB 级栈空间),一个线程可承载上万协程。

协程的核心:同步代码的写法,异步非阻塞的执行

2. 环境准备(必做)

Gradle 依赖(最新稳定版)

gradle

复制代码
// 核心库(JVM/桌面端)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
// 安卓适配(安卓项目)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0")
// 测试支持
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0")
核心语法:第一个可运行的协程

kotlin

复制代码
import kotlinx.coroutines.*

fun main() {
    // 1. runBlocking:阻塞当前线程的协程作用域(仅用于测试/主函数)
    runBlocking {
        println("主线程开始:${Thread.currentThread().name}")
        
        // 2. launch:启动无返回值的协程,返回 Job 控制生命周期
        val job = launch(Dispatchers.Default) {
            repeat(3) { i ->
                println("协程执行中 $i:${Thread.currentThread().name}")
                delay(500) // 挂起函数:暂停协程,不阻塞线程
            }
        }
        
        delay(800) // 主线程挂起800ms
        println("主线程取消协程")
        job.cancel() // 取消协程
        job.join()   // 等待协程终止
        
        println("主线程结束")
    }
}

输出

plaintext

复制代码
主线程开始:main @coroutine#1
协程执行中 0:DefaultDispatcher-worker-1 @coroutine#2
协程执行中 1:DefaultDispatcher-worker-1 @coroutine#2
主线程取消协程
主线程结束

3. 核心基础概念(入门必记)

概念 作用
CoroutineScope 协程作用域,管理协程生命周期(核心:结构化并发
Job 协程的句柄,可 cancel()/join()/isActive 控制协程
suspend 挂起函数标记,只能在协程 / 其他挂起函数中调用,可暂停 / 恢复
Dispatchers 调度器,指定协程运行线程(Main/IO/Default/Unconfined)
delay() 挂起函数,暂停协程(非阻塞),仅用于协程内
runBlocking 阻塞当前线程的协程作用域(仅测试用,禁止业务代码使用)

二、核心进阶:掌握协程的关键能力

1. 结构化并发(协程的核心设计原则)

核心思想:协程的生命周期绑定到作用域,父协程取消 → 所有子协程自动取消,杜绝协程泄漏。

错误示例(GlobalScope 导致泄漏)

kotlin

复制代码
// 禁止使用!GlobalScope 是全局作用域,协程不受生命周期管理
GlobalScope.launch {
    delay(10000) // 长时间任务
    println("协程执行(即使页面已销毁)")
}
正确示例(自定义作用域)

kotlin

复制代码
// 1. 自定义作用域(推荐方式:CoroutineScope + 上下文)
val myScope = CoroutineScope(Dispatchers.IO + SupervisorJob())

// 2. 启动子协程
val parentJob = myScope.launch {
    val childJob1 = launch { fetchData() }
    val childJob2 = launch { processData() }
}

// 3. 销毁时取消作用域(如Activity onDestroy)
myScope.cancel() // 父协程取消 → 所有子协程自动取消

2. 挂起函数(suspend)深度理解

  • 挂起函数不是线程切换:只是标记 "这个函数可能暂停协程";
  • 挂起 ≠ 阻塞:挂起时线程可执行其他协程,阻塞时线程空闲;
  • 挂起函数的协作式取消 :挂起函数(如 delay/withContext)会检查协程是否活跃,非活跃则抛出 CancellationException
自定义挂起函数(CPU 密集型任务的取消兼容)

kotlin

复制代码
suspend fun longRunningTask() = withContext(Dispatchers.Default) {
    for (i in 0 until 10000) {
        // 手动检查协程是否活跃(CPU密集型任务必须加,否则无法取消)
        ensureActive() // 等价于 if (!isActive) throw CancellationException()
        Thread.sleep(1) // 模拟CPU计算(非挂起函数,需手动检查)
    }
}

3. 带返回值的协程:async/await

async 启动协程并返回 Deferred<T>,通过 await() 获取结果(会挂起直到结果返回)。

并行执行多个任务(核心场景)

kotlin

复制代码
suspend fun loadData(): Pair<String, Int> = coroutineScope {
    // 并行启动两个协程
    val userDeferred = async { fetchUser() }
    val scoreDeferred = async { fetchScore() }
    
    // 等待结果(总耗时≈最长的那个任务,而非累加)
    val user = userDeferred.await()
    val score = scoreDeferred.await()
    
    return@coroutineScope Pair(user, score)
}

// 模拟耗时任务
suspend fun fetchUser(): String {
    delay(1000)
    return "张三"
}

suspend fun fetchScore(): Int {
    delay(1500)
    return 95
}

4. 协程的异常处理

协程的异常处理分两种场景:

场景 1:单个协程的异常捕获(try-catch)

kotlin

复制代码
coroutineScope.launch {
    try {
        val data = withContext(Dispatchers.IO) {
            throw RuntimeException("网络请求失败")
            fetchData()
        }
    } catch (e: Exception) {
        println("捕获异常:${e.message}")
    }
}
场景 2:作用域全局异常处理(CoroutineExceptionHandler)

kotlin

复制代码
// 1. 定义异常处理器
val exceptionHandler = CoroutineExceptionHandler { context, throwable ->
    println("全局捕获异常:${throwable.message},协程:${context[CoroutineName]}")
}

// 2. 作用域绑定异常处理器
val scope = CoroutineScope(SupervisorJob() + exceptionHandler + CoroutineName("MyScope"))

// 3. 启动协程(异常会被全局处理器捕获)
scope.launch {
    throw RuntimeException("协程异常")
}
关键:SupervisorJob vs Job
  • Job:父协程会因子协程异常而取消,且取消所有其他子协程(默认);
  • SupervisorJob:子协程异常不影响父协程和其他子协程(适合多任务并行,如列表多 item 加载)。

5. 协程的取消与超时

(1)协程取消(协作式)

kotlin

复制代码
val job = scope.launch {
    while (isActive) { // 检查协程是否活跃
        println("协程运行中")
        delay(100)
    }
}

// 取消协程
job.cancel()
job.join() // 等待取消完成(可简写为 job.cancelAndJoin())
(2)超时处理(withTimeout/withTimeoutOrNull)

kotlin

复制代码
suspend fun fetchDataWithTimeout(): String? {
    // withTimeout:超时抛出 TimeoutCancellationException
    // withTimeoutOrNull:超时返回 null(更安全)
    return withTimeoutOrNull(1000) {
        delay(1500) // 模拟超时
        "数据"
    }
}

// 使用
val data = fetchDataWithTimeout() ?: "默认数据(超时)"

6. 协程通道(Channel):协程间通信

通道是协程间的 "管道",用于协程间传递数据(类似阻塞队列,但非阻塞)。

基础用法

kotlin

复制代码
suspend fun channelDemo() = coroutineScope {
    // 创建通道,指定容量(无缓冲则发送方挂起直到接收方接收)
    val channel = Channel<Int>(capacity = 2)
    
    // 发送方协程
    launch {
        repeat(3) {
            println("发送数据 $it")
            channel.send(it) // 发送数据,满了则挂起
            delay(500)
        }
        channel.close() // 关闭通道
    }
    
    // 接收方协程
    launch {
        // 遍历通道(通道关闭后退出循环)
        for (data in channel) {
            println("接收数据 $data")
        }
    }
}

7. 协程流(Flow):响应式数据流(核心进阶)

Flow 是协程的响应式流,用于处理异步序列数据(如分页加载、实时数据推送),替代 RxJava。

基础 Flow 示例

kotlin

复制代码
// 1. 定义Flow(冷流:订阅时才执行)
fun fetchNumbers(): Flow<Int> = flow {
    println("Flow开始执行")
    repeat(3) {
        delay(500)
        emit(it) // 发射数据
    }
}

// 2. 收集Flow(必须在协程内)
suspend fun collectFlow() {
    fetchNumbers()
        .flowOn(Dispatchers.IO) // 指定发射数据的线程
        .collect { // 收集数据(在当前协程线程执行)
            println("收集到:$it,线程:${Thread.currentThread().name}")
        }
}
Flow 常用操作符(实战高频)

kotlin

复制代码
fetchNumbers()
    .map { it * 2 } // 数据转换
    .filter { it > 2 } // 数据过滤
    .debounce(300) // 防抖(如搜索框输入)
    .distinctUntilChanged() // 去重(仅当数据变化时发射)
    .catch { e -> println("捕获异常:${e.message}") } // 异常处理
    .onCompletion { println("Flow完成") } // 完成回调
    .collect { println("最终结果:$it") }

三、实战精通:协程在项目中的最佳实践

1. 安卓项目中的协程用法(ViewModel + CoroutineScope)

kotlin

复制代码
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

class MyViewModel : ViewModel() {
    // 1. ViewModel 内置作用域(自动绑定生命周期,页面销毁自动取消)
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiState

    // 2. 业务逻辑:加载数据
    fun loadData() {
        viewModelScope.launch(Dispatchers.IO) {
            try {
                val data = fetchDataFromApi()
                _uiState.value = UiState.Success(data)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message ?: "未知错误")
            }
        }
    }

    private suspend fun fetchDataFromApi(): String {
        delay(1000)
        return "服务器返回数据"
    }
}

// UI状态密封类
sealed class UiState {
    object Loading : UiState()
    data class Success(val data: String) : UiState()
    data class Error(val message: String) : UiState()
}

2. 并发任务的最佳实践

(1)并行执行 + 结果合并

kotlin

复制代码
suspend fun loadAllData(): Result<Data> = coroutineScope {
    val userDeferred = async { fetchUser() }
    val orderDeferred = async { fetchOrder() }
    val goodsDeferred = async { fetchGoods() }

    return@coroutineScope try {
        val user = userDeferred.await()
        val order = orderDeferred.await()
        val goods = goodsDeferred.await()
        Result.success(Data(user, order, goods))
    } catch (e: Exception) {
        // 取消所有未完成的协程
        listOf(userDeferred, orderDeferred, goodsDeferred).forEach { it.cancel() }
        Result.failure(e)
    }
}

data class Data(val user: String, val order: String, val goods: String)
(2)限流执行(如批量请求)

kotlin

复制代码
suspend fun batchRequest(ids: List<Int>) = coroutineScope {
    // 限制并发数为3
    val semaphore = kotlinx.coroutines.sync.Semaphore(3)
    ids.forEach { id ->
        launch {
            semaphore.acquire() // 获取许可,满了则挂起
            try {
                fetchItem(id)
            } finally {
                semaphore.release() // 释放许可
            }
        }
    }
}

3. 协程测试(单元测试)

kotlin

复制代码
import kotlinx.coroutines.test.runTest
import org.junit.Test

class CoroutineTest {
    @Test
    fun testFetchData() = runTest { // 协程测试专用作用域
        val data = fetchData()
        assert(data == "模拟从服务器获取的数据")
    }
}

四、避坑指南:新手常犯的错误

  1. 滥用 runBlocking:仅用于测试 / 主函数,业务代码中使用会阻塞线程(如安卓主线程);
  2. 使用 GlobalScope:全局作用域无生命周期管理,必然导致协程泄漏;
  3. CPU 密集型任务未检查 isActive:协程无法取消,导致资源浪费;
  4. 忽略协程异常:未捕获的异常会导致协程崩溃,甚至崩溃进程;
  5. Flow 未指定 flowOn:发射数据的线程错误(如在主线程执行耗时操作)。

五、精通进阶:协程底层原理(可选)

1. 协程的实现原理

  • 协程基于 状态机 实现:挂起函数编译后会被拆分为多个状态,暂停时保存状态,恢复时恢复状态;
  • 协程调度基于 线程池 + 任务队列Dispatchers.IO/Default 底层是线程池,协程任务提交到队列中执行;
  • 挂起函数的本质:通过 Continuation(续体)实现暂停 / 恢复,suspend 是语法糖。

2. 核心源码关键点

  • CoroutineScope:本质是 CoroutineContext 的封装;
  • CoroutineContext:协程上下文,包含 Job、Dispatchers、异常处理器等;
  • Continuation:续体,保存协程的执行状态和恢复逻辑。

总结

核心要点回顾

  1. 基础核心 :协程是轻量级线程,通过 CoroutineScope 实现结构化并发,launch/async 启动协程,Dispatchers 指定线程;
  2. 进阶能力:掌握挂起函数的协作式取消、异常处理、Channel 通信、Flow 响应式数据流;
  3. 实战原则 :协程必须绑定生命周期(如 ViewModelScope),杜绝 GlobalScope,CPU 密集型任务手动检查 isActive
  4. 避坑关键:合理处理异常、正确使用调度器、避免阻塞主线程。

从入门到精通的路径:先掌握基础用法 → 结合项目实战(如安卓 ViewModel)→ 理解核心进阶特性(Flow/Channel)→ 最后了解底层原理。建议先动手写代码,再逐步深入原理,这样更容易掌握。

相关推荐
开发者小天2 小时前
python中的Dictionaries
android·开发语言·python
芒鸽2 小时前
鸿蒙应用自动化资源同步:Kuikly框架资源复制解决方案
华为·kotlin·自动化·harmonyos·kuikly
_F_y2 小时前
MySQL表的内连和外连
android·数据库·mysql
肖。35487870942 小时前
窗口半初始化导致的BadTokenException闪退!解决纯Java开发的安卓软件开局闪退!具体表现为存储中的缓存为0和数据为0。
android·java·javascript·css·html
2601_9495758612 小时前
Flutter for OpenHarmony二手物品置换App实战 - 商品卡片实现
android·flutter
2601_9495758614 小时前
Flutter for OpenHarmony二手物品置换App实战 - 表单验证实现
android·java·flutter
龚礼鹏16 小时前
图像显示框架八——BufferQueue与BLASTBufferQueue(基于android 15源码分析)
android·c语言
1登峰造极17 小时前
uniapp 运行安卓报错reportJSException >>>> exception function:createInstanceContext, exception:white screen
android·java·uni-app
木易 士心17 小时前
Android Handler 机制原理详解
android