Android Kotlin 协程详解

一、协程概述

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一致,都是通过协程调度器实现非阻塞启动:

  1. 不会阻塞调用线程
  2. 会在指定调度器(如 IO/Default)的线程池中分配执行资源
  3. 适合用于并行计算任务(如同时获取多个网络接口数据)
从结果获取看:await () 的同步阻塞特性

Deferred.await()方法具有以下特点:

  1. 会阻塞当前协程(注意:不是阻塞线程!)
  2. 等待期间会释放当前线程资源给其他协程
  3. 本质是 "挂起当前协程直到结果可用",而非传统同步编程的线程阻塞
与同步 / 异步的对比表格

|--------|----------|----------------|-------------|
| 特性 | 传统同步方法 | 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 推荐做法

  1. 使用生命周期绑定作用域:如 viewModelScope、lifecycleScope
  2. 依赖注入调度器:避免硬编码 Dispatchers
  3. 结构化并发优先:使用 coroutineScope/supervisorScope 管理协程
  4. 处理异常:使用 try-catch 或 CoroutineExceptionHandler
  5. Flow 处理多值异步:替代回调和 LiveData

4.2 避免做法

  1. 滥用 GlobalScope:可能导致内存泄漏
  2. 在主线程执行耗时操作:始终使用 Dispatchers.IO 或 Default
  3. 手动管理协程生命周期:依赖作用域自动取消
  4. 阻塞挂起函数:避免使用 Thread.sleep

4.3 性能优化

  1. 限制并发数量:使用 Semaphore 控制同时运行的协程数
  2. 合理选择调度器:IO 任务用 Dispatchers.IO,CPU 任务用 Default
  3. 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) } |

七、资源推荐

  1. Kotlin 官方文档协程指南
  2. Android 开发者文档协程最佳实践
  3. Kotlin 协程 GitHubkotlinx.coroutines

八、总结

Kotlin 协程是 Android 异步编程的革命性工具,通过轻量级、结构化并发和响应式编程,极大提升了代码的可读性和可维护性。掌握协程的基础、进阶技巧和最佳实践,能够显著提高开发效率,减少内存泄漏和线程管理问题。建议结合官方文档和实际项目不断实践,逐步深入理解协程的核心原理和应用场景。

相关推荐
花阴偷移1 小时前
kotlin语法(上)
android·java·开发语言·kotlin
Smart-佀1 小时前
Android初学必备:选Kotlin 还是Java ?
android·android studio·安卓
XuanRanDev1 小时前
【编程语言】Kotlin快速入门 - 泛型
开发语言·kotlin
普通网友1 小时前
Android kotlin Jetpack mvvm 项目
android·开发语言·kotlin
Crogin1 小时前
快速简单入门Kotlin——基础语法(第一天)
android·开发语言·kotlin
国服第二切图仔2 小时前
Electron for鸿蒙PC实战项目之简易绘图板应用
android·electron·开源鸿蒙·鸿蒙pc
BD_Marathon2 小时前
【IDEA】常用插件——3
android·java·intellij-idea
北海道浪子2 小时前
Android 开发中的图片格式全指南
android·架构
lichong9512 小时前
RelativeLayout 根布局里有一个子布局预期一直展示,但子布局RelativeLayout被 覆盖了
android·java·前端