一、协程概述
1.1 协程的定义与优势
协程是 Kotlin 中处理异步操作的核心特性,它轻量、高效,允许以同步方式编写异步代码,避免回调地狱,提升代码可读性和可维护性。
核心优势:
- 轻量级:单个应用可创建数千个协程,内存占用极低
- 非阻塞:通过挂起函数实现线程切换,避免阻塞主线程
- 结构化并发:自动管理协程生命周期,防止内存泄漏
- 无缝集成:与 Android 生命周期完美结合,替代传统 AsyncTask、Handler 等
1.2 协程与线程对比
特性 | 协程 | 线程 |
内存占用 | 约 1KB / 协程 | 约 1MB / 线程 |
启动速度 | 纳秒级 | 毫秒级 |
并发能力 | 数千个 | 数百个 |
资源消耗 | 极低 | 高 |
(数据来源:Kotlin 官方文档及测试数据)
二、协程基础
2.1 协程的启动方式
2.1.1 launch(无返回值)
// 在主线程启动协程 viewModelScope.launch(Dispatchers.Main) { // 执行UI操作 } // 在后台线程执行耗时任务 viewModelScope.launch(Dispatchers.IO) { val data = repository.fetchData() withContext(Dispatchers.Main) { textView.text = data } } |
2.1.2 async(带返回值)
val deferred = viewModelScope.async(Dispatchers.IO) { repository.fetchUserData() } // 等待结果并在主线程处理 viewModelScope.launch(Dispatchers.Main) { val user = deferred.await() updateUI(user) } |
在 Kotlin 协程中,async函数的核心特性是异步启动但支持同步获取结果,它的执行机制可以拆解为两部分:
- 异步启动特性:async会立即启动一个协程,该协程在指定的调度器上异步执行
- 可等待性:通过await()方法可以阻塞当前协程,直到async启动的协程完成并返回结果
从启动方式看:绝对的异步特性
async的启动机制与launch一致,都是通过协程调度器实现非阻塞启动:
- 不会阻塞调用线程
- 会在指定调度器(如 IO/Default)的线程池中分配执行资源
- 适合用于并行计算任务(如同时获取多个网络接口数据)
从结果获取看:await () 的同步阻塞特性
Deferred.await()方法具有以下特点:
- 会阻塞当前协程(注意:不是阻塞线程!)
- 等待期间会释放当前线程资源给其他协程
- 本质是 "挂起当前协程直到结果可用",而非传统同步编程的线程阻塞
与同步 / 异步的对比表格
特性 | 传统同步方法 | async+await 组合 | 纯异步回调 |
调用线程阻塞 | 是(线程级阻塞) | 否(协程级挂起) | 否 |
结果获取方式 | 立即返回 | 显式 await () 获取 | 回调函数 |
并行能力 | 无 | 强(支持 async 并行) | 强 |
代码可读性 | 高 | 高(接近同步写法) | 低(可能出现回调地狱) |
2.2 协程作用域
2.2.1 常用作用域
- viewModelScope:与 ViewModel 生命周期绑定,自动取消
- lifecycleScope:与 Activity/Fragment 生命周期绑定
- GlobalScope:全局作用域,需谨慎使用,避免内存泄漏
2.2.2 自定义作用域
class MyRepository(private val scope: CoroutineScope) { fun fetchData() { scope.launch(Dispatchers.IO) { // 执行数据库操作 } } } |
2.3 挂起函数
2.3.1 定义与使用
// 挂起函数必须标记suspend suspend fun fetchUserData(userId: String): User { delay(1000L) // 模拟网络请求 return User(userId, "John Doe") } // 在协程中调用挂起函数 viewModelScope.launch { val user = fetchUserData("123") updateUI(user) } |
2.3.2 挂起与阻塞的区别
- 挂起:协程暂停执行,释放线程资源,不阻塞线程
- 阻塞:线程被占用,无法执行其他任务(如 Thread.sleep)
2.4 调度器(Dispatchers)
2.4.1 常用调度器
- Dispatchers.Main:主线程,用于 UI 操作
- Dispatchers.IO:优化 IO 操作(网络、数据库)
- Dispatchers.Default:CPU 密集型任务
- Dispatchers.Unconfined:不指定线程,当前线程执行
2.4.2 线程切换
viewModelScope.launch(Dispatchers.IO) { // 执行耗时操作 val data = database.loadData() //从IO线程切换到Main线程 withContext(Dispatchers.Main) { // 更新UI } } |
三、协程进阶
3.1 结构化并发
3.1.1 coroutineScope
//所有子协程完成后才会结束 //任一子协程异常会取消整个作用域 suspend fun fetchMultipleData(): CombinedData { return coroutineScope { val userDeferred = async { fetchUser() } val profileDeferred = async { fetchProfile() } CombinedData(userDeferred.await(), profileDeferred.await()) } } |
coroutineScope 是 Kotlin 协程中实现结构化并发的核心 API,其设计目标是解决传统异步编程中任务生命周期管理混乱的问题。它的核心特性包括:
- 作用域绑定:所有在 coroutineScope 内启动的子协程都与该作用域绑定
- 完成保证:coroutineScope 会等待所有子协程完成后才返回
- 异常传播:任一子协程抛出异常会导致整个作用域取消
自动资源释放:确保协程执行完毕后释放相关资源
结构化并发的实现原理
coroutineScope 通过以下机制实现结构化并发:
- 作用域树结构:每个 coroutineScope 创建一个新的作用域节点,子协程作为子节点
- 完成回调链:父作用域等待所有子作用域完成后才结束
- 异常冒泡:子协程异常会向上传播至父作用域
// 作用域树结构示例 suspend fun parentScope() = coroutineScope { println("父作用域开始")
// 第一个子作用域 coroutineScope { println("子作用域1开始") launch { delay(1000) println("子作用域1任务完成") } println("子作用域1等待任务完成") }
// 第二个子作用域 coroutineScope { println("子作用域2开始") launch { delay(500) println("子作用域2任务完成") } println("子作用域2等待任务完成") }
println("父作用域所有子任务完成") } |
异常处理机制
coroutineScope 的异常处理遵循以下规则:
- 子协程抛出异常会导致整个 coroutineScope 取消
- 异常会被传播给 coroutineScope 的调用者
- 未处理的异常会导致协程崩溃
suspend fun exceptionHandling() = coroutineScope { val job1 = launch { delay(1000) println("任务1完成") }
val job2 = launch { delay(500) throw IOException("网络错误") // 抛出异常 }
// 以下代码不会执行,因为job2抛出异常 job1.join() job2.join() println("所有任务完成") } // 调用方式 try { exceptionHandling() } catch (e: IOException) { println("捕获异常: ${e.message}") // 输出:捕获异常: 网络错误 } |
与其他作用域的对比
特性 | coroutineScope | GlobalScope | viewModelScope |
结构化并发 | 是(强制等待子协程) | 否 | 是(与生命周期绑定) |
异常处理 | 严格(异常传播) | 无 | 自动取消(随组件销毁) |
生命周期管理 | 自动(完成即结束) | 手动 | 自动(随 ViewModel 销毁) |
适用场景 | 并行任务聚合 | 后台长时间运行任务 | ViewModel 内异步操作 |
3.1.2 supervisorScope
supervisorScope是 Kotlin 协程中用于实现非结构化并发的关键构建器,它与coroutineScope共同构成了结构化并发的两大支柱。其核心特性在于:
- 子协程异常隔离:子协程的异常不会传播到兄弟协程和父协程
- 灵活的异常处理:每个子协程可独立处理自身异常
- 非阻塞式异常传播:异常不会立即中断整个作用域的执行
// supervisorScope基本结构 suspend fun supervisorScopeDemo() { supervisorScope { // 启动多个子协程 val job1 = launch { /* 任务1 */ } val job2 = launch { /* 任务2 */ } val job3 = launch { /* 任务3 */ }
// 等待所有子协程完成 job1.join() job2.join() job3.join() } } |
supervisorScope 与 coroutineScope 的核心差异
1. 异常处理机制对比
特性 | coroutineScope | supervisorScope |
异常传播 | 子协程异常会取消整个作用域及所有兄弟协程 | 子协程异常仅影响自身,不影响其他协程 |
异常处理责任 | 父协程负责统一处理所有子协程异常 | 每个子协程需自行处理自身异常 |
任务完整性 | 所有子任务必须全部完成 | 部分子任务失败不影响其他任务执行 |
适用场景 | 强一致性要求的场景(如数据库事务) | 独立任务并行执行的场景(如日志收集) |
2 执行流程对比示例
// coroutineScope异常传播示例 suspend fun coroutineScopeException() { try { coroutineScope { launch { delay(100) throw Exception("子协程1异常") } launch { delay(200) println("子协程2是否执行?") // 不会执行 } } } catch (e: Exception) { println("捕获到异常: ${e.message}") } } // supervisorScope异常隔离示例 suspend fun supervisorScopeException() { try { supervisorScope { launch { delay(100) throw Exception("子协程1异常") } launch { delay(200) println("子协程2正常执行") // 会执行 } } } catch (e: Exception) { println("捕获到异常: ${e.message}") // 不会捕获到异常 } } |
supervisorScope 的异常处理机制
子协程异常的传播路径
graph TD A[supervisorScope] --> B[子协程1] A --> C[子协程2] B -->|抛出异常| B1[异常仅在子协程1内传播] C -->|正常执行| C1[子协程2不受影响] |
子协程自处理异常的最佳实践
suspend fun selfHandleException() { supervisorScope { // 子协程1:自行处理异常 launch { try { // 可能抛出异常的操作 throw IOException("网络异常") } catch (e: IOException) { logError("网络请求失败: ${e.message}") } }
// 子协程2:未处理异常会导致自身取消,但不影响其他协程 launch { // 未处理的异常 throw IllegalStateException("状态异常") }
// 子协程3:正常执行 launch { delay(500) println("子协程3完成") } } } |
3.2 Flow(响应式数据流)
3.2.1 基本使用
// 定义Flow fun getUpdates(): Flow<Update> = flow { while (true) { emit(fetchUpdate()) // 发射数据 delay(1000L) } } // 收集Flow lifecycleScope.launch { getUpdates() .flowOn(Dispatchers.IO) .collect { update -> // 更新UI } } |
3.2.2 操作符
- map:转换数据
- filter:过滤数据
- debounce:防抖处理
- collectLatest:处理最新数据
3.3 Channel(协程间通信)
3.3.1 基础用法
val channel = Channel<Int>() // 生产者 launch { for (i in 1..5) { channel.send(i * i) } channel.close() } // 消费者 launch { for (value in channel) { println(value) } } |
3.3.2 缓冲区策略
// 容量为2,溢出时丢弃最旧数据 val channel = Channel<Int>(2, BufferOverflow.DROP_OLDEST) |
四、最佳实践
4.1 推荐做法
- 使用生命周期绑定作用域:如 viewModelScope、lifecycleScope
- 依赖注入调度器:避免硬编码 Dispatchers
- 结构化并发优先:使用 coroutineScope/supervisorScope 管理协程
- 处理异常:使用 try-catch 或 CoroutineExceptionHandler
- Flow 处理多值异步:替代回调和 LiveData
4.2 避免做法
- 滥用 GlobalScope:可能导致内存泄漏
- 在主线程执行耗时操作:始终使用 Dispatchers.IO 或 Default
- 手动管理协程生命周期:依赖作用域自动取消
- 阻塞挂起函数:避免使用 Thread.sleep
4.3 性能优化
- 限制并发数量:使用 Semaphore 控制同时运行的协程数
- 合理选择调度器:IO 任务用 Dispatchers.IO,CPU 任务用 Default
- Flow 背压处理:使用 buffer、conflate 等操作符控制流速
五、示例代码
5.1 ViewModel 中使用协程
class MyViewModel : ViewModel() { private val _uiState = MutableStateFlow<UiState>(UiState.Loading) val uiState: StateFlow<UiState> = _uiState.asStateFlow() fun fetchData() { viewModelScope.launch { _uiState.value = UiState.Loading try { val data = withContext(Dispatchers.IO) { repository.fetchData() } _uiState.value = UiState.Success(data) } catch (e: Exception) { _uiState.value = UiState.Error(e.message ?: "未知错误") } } } } |
5.2 Flow 与 LiveData 结合
class MyViewModel : ViewModel() { val userData: LiveData<User> = repository.getUserData() .flowOn(Dispatchers.IO) .catch { e -> Log.e("MyViewModel", "获取数据失败", e) } .asLiveData(viewModelScope.coroutineContext) } |
5.3 并行执行多个请求
suspend fun fetchMultipleData(): CombinedData { return coroutineScope { val userDeferred = async(Dispatchers.IO) { userService.getUser() } val profileDeferred = async(Dispatchers.IO) { profileService.getProfile() } CombinedData(userDeferred.await(), profileDeferred.await()) } } |
六、测试协程
6.1 使用 TestDispatcher
import kotlinx.coroutines.test.* @Test fun testFetchData() = runTest { val testDispatcher = StandardTestDispatcher() Dispatchers.setMain(testDispatcher) val viewModel = MyViewModel(repository = mockRepository) viewModel.fetchData() // 推进时间让协程执行 testDispatcher.advanceUntilIdle() assertEquals(UiState.Success(expectedData), viewModel.uiState.value) } |
6.2 模拟耗时操作
@Test fun testNetworkRequest() = runTest { val mockRepository = MockRepository() val viewModel = MyViewModel(repository = mockRepository)
// 模拟网络请求延迟 mockRepository.mockDelay(1000L)
viewModel.fetchData() advanceTimeBy(1000L) // 推进时间
assertTrue(viewModel.uiState.value is UiState.Success) } |
七、资源推荐
- Kotlin 官方文档:协程指南
- Android 开发者文档:协程最佳实践
- Kotlin 协程 GitHub:kotlinx.coroutines
八、总结
Kotlin 协程是 Android 异步编程的革命性工具,通过轻量级、结构化并发和响应式编程,极大提升了代码的可读性和可维护性。掌握协程的基础、进阶技巧和最佳实践,能够显著提高开发效率,减少内存泄漏和线程管理问题。建议结合官方文档和实际项目不断实践,逐步深入理解协程的核心原理和应用场景。