一篇讲透掌握 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 小时前
Jetpack Compose 实战:实现一个动态平滑折线图
android·折线图·compose
李艺为6 小时前
Fake Device Test作假屏幕分辨率分析
android·java
zh_xuan6 小时前
github远程library仓库升级
android·github
峥嵘life6 小时前
Android蓝牙停用绝对音量原理
android
小书房7 小时前
Kotlin的内联函数
java·开发语言·kotlin·inline·内联函数
czlczl200209258 小时前
IN和BETWEEN在索引效能的区别
android·adb
Volunteer Technology8 小时前
ES高级搜索功能
android·大数据·elasticsearch
北京自在科技8 小时前
Find Hub App 小更新
android·ios·安卓·findmy·airtag
lbb 小魔仙8 小时前
2026远程办公软件夏季深度横测:ToDesk、向日葵、网易UU远程全面对比,远控白皮书
android·服务器·网络协议·tcp/ip·postgresql
coding_fei9 小时前
AudioServer初始化过程
android