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 AsupervisorScope --> 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 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo1 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077001 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
ai2work1 天前
ch23 综合复刻:从零做一个最小可用版本(capstone)
kotlin
其实防守也摸鱼1 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
ai2work1 天前
ch21 签名、校验与发版
kotlin
AFinalStone1 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui