Android Kotlin 协程原理分析
协程原理、异常机制、结构化并发,结合 Android 代码说明
一、协程原理
1.1 无栈协程与状态机
Kotlin 协程是无栈协程,不依赖线程栈,通过状态机实现挂起与恢复:
- 挂起:释放当前线程,保存执行状态
- 恢复:在任意线程上根据保存的状态继续执行
1.2 CPS 转换(Continuation-Passing Style)
编译器将 suspend 函数转换为带 Continuation 回调的形式:
// 源码
suspend fun fetchUser(id: Int): User {
delay(1000)
return api.getUser(id)
}
// 编译器转换后(伪代码)
fun fetchUser(id: Int, continuation: Continuation<User>): Any {
when (continuation.label) {
0 -> {
continuation.label = 1
if (delay(1000, continuation) == COROUTINE_SUSPENDED)
return COROUTINE_SUSPENDED
}
1 -> {
val user = api.getUser(id)
continuation.resume(user)
return user
}
}
}
- 自动添加
Continuation参数 - 用
label标记挂起点 - 挂起时返回
COROUTINE_SUSPENDED,恢复时调用continuation.resumeWith()
1.3 状态机执行流程
// Android 示例:ViewModel 中发起网络请求
class MyViewModel : ViewModel() {
fun loadData() {
viewModelScope.launch {
val user = fetchUser(1) // 挂起点 1
val posts = fetchPosts(user) // 挂起点 2
updateUI(posts)
}
}
private suspend fun fetchUser(id: Int): User = withContext(Dispatchers.IO) {
delay(500)
api.getUser(id)
}
private suspend fun fetchPosts(user: User) = withContext(Dispatchers.IO) {
api.getPosts(user.id)
}
}
执行过程:
launch创建协程,首次执行到fetchUser时,内部delay挂起,返回COROUTINE_SUSPENDED- 协程释放线程,状态机保存
label = 1 delay到期后,调度器调用continuation.resumeWith()- 状态机从
label = 1继续,执行fetchPosts - 依此类推,直到协程结束
1.4 Continuation 接口
// kotlinx.coroutines 中的 Continuation
interface Continuation<in T> {
val context: CoroutineContext // 协程上下文(调度器、Job 等)
fun resumeWith(result: Result<T>) // 恢复执行,传入结果或异常
}
context:保存调度器、Job、异常处理器等resumeWith:挂起函数完成时由框架调用,继续执行后续代码
1.5 挂起不阻塞线程
// Activity 中:主线程启动协程,挂起时主线程可处理其他任务
lifecycleScope.launch {
val data = withContext(Dispatchers.IO) {
repository.fetchData() // 切到 IO 线程,主线程释放
}
textView.text = data // 切回主线程更新 UI
}
二、协程异常原理
2.1 异常传播方式
| 构建器 | 异常传播 | 说明 |
|---|---|---|
| launch | 自动向上传播 | 异常会传递给父协程,最终到 CoroutineExceptionHandler |
| async | 向用户暴露 | 异常在 await() 时抛出,需 try-catch 或 CoroutineExceptionHandler |
| supervisorScope | 隔离 | 子协程异常不传播给兄弟协程和父协程 |
2.2 launch 异常传播
// 异常会向上传播,若未处理则导致协程取消
viewModelScope.launch {
throw RuntimeException("error") // 传播到 viewModelScope
}
// viewModelScope 会取消,并调用 CoroutineExceptionHandler(若配置)
2.3 async 异常暴露
// async 中异常在 await 时抛出
viewModelScope.launch {
val deferred = async { fetchData() } // 内部异常暂不抛出
try {
val result = deferred.await() // 这里可能抛出异常
updateUI(result)
} catch (e: Exception) {
showError(e.message)
}
}
2.4 CoroutineExceptionHandler
// 全局捕获未处理的协程异常(仅对根协程有效)
val handler = CoroutineExceptionHandler { _, throwable ->
Log.e("Coroutine", "未捕获异常", throwable)
}
lifecycleScope.launch(handler) {
throw RuntimeException("test") // 被 handler 捕获,不会崩溃
}
注意:CoroutineExceptionHandler 必须作为根协程的 context 才能生效;子协程的异常会传播到根协程,由根协程的 handler 处理。
2.5 SupervisorJob 与 supervisorScope
// SupervisorJob:子协程失败不影响兄弟和父协程
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
scope.launch {
launch { throw RuntimeException("A 失败") } // A 失败
launch { delay(1000); println("B 完成") } // B 不受影响,继续执行
}
// supervisorScope:在挂起函数中创建隔离作用域
suspend fun loadMultiple() = supervisorScope {
val a = async { fetchA() } // 若失败,不影响 b
val b = async { fetchB() }
listOf(a.await(), b.await())
}
2.5.1 SupervisorJob 如何做到「子协程失败不影响兄弟和父协程」
核心机制:重写 childCancelled,返回 false 阻止异常向上传播。
// kotlinx.coroutines 源码(JobSupport.kt 简化)
class SupervisorJobImpl(parent: Job?) : JobImpl(parent), CompletableJob {
override fun childCancelled(cause: Throwable): Boolean {
return false // 关键:不向父级传播,父 Job 和兄弟 Job 不受影响
}
}
普通 Job 的行为:
- 子协程抛异常时,会调用父 Job 的
childCancelled(cause) - 普通 Job 返回
true,表示「已处理」,异常会继续向上传播,导致父 Job 和所有兄弟 Job 被取消 - 即「一个失败,全部取消」
SupervisorJob 的行为:
- 重写
childCancelled并返回false - 表示「不向上传播」,父 Job 认为子协程的失败已在本层处理
- 父 Job 和兄弟 Job 不会被取消,只有抛出异常的那个子协程自己失败
传播流程对比:
普通 Job:
子 A 失败 → childCancelled 返回 true → 父 Job 取消 → 兄弟 B、C 一并取消
SupervisorJob:
子 A 失败 → childCancelled 返回 false → 父 Job 不取消 → 兄弟 B、C 继续执行
注意:SupervisorJob 只隔离直接子协程。若在子协程内部再 launch,内层协程的父 Job 是中间层 Job,不是 SupervisorJob。中间层 Job 失败仍会向上传播,可能取消其父协程。要让内层也隔离,需在对应层级使用 supervisorScope 或 SupervisorJob。
2.6 Android 中的 SupervisorJob
// lifecycleScope 和 viewModelScope 内部都使用 SupervisorJob
// lifecycleScope 源码(简化)
val LifecycleOwner.lifecycleScope: CoroutineScope
get() = lifecycle.coroutineScope // 内部: SupervisorJob() + Dispatchers.Main.immediate
// viewModelScope 源码(简化)
val ViewModel.viewModelScope: CoroutineScope
get() = CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
因此,在 viewModelScope.launch { } 中,一个子协程抛异常不会取消其他子协程。
2.7 正确使用 try-catch
// 在协程内部捕获
viewModelScope.launch {
try {
val data = repository.fetchData()
_uiState.value = UiState.Success(data)
} catch (e: Exception) {
_uiState.value = UiState.Error(e.message)
}
}
// 注意:CancellationException 应重新抛出,不要吞掉
viewModelScope.launch {
try {
delay(1000)
} catch (e: CancellationException) {
throw e // 必须重新抛出,否则取消无法正确传播
}
}
三、结构化并发原理
3.1 核心思想
结构化并发:所有协程必须在明确的作用域内启动,形成父子关系,父协程负责管理子协程的生命周期。
- 无孤立协程:每个协程都有父协程
- 取消传播:父协程取消时,所有子协程自动取消
- 等待完成:父协程会等待所有子协程完成后才结束
3.2 父子关系
// launch 和 async 创建的子协程会继承父协程的 Job
viewModelScope.launch { // 父协程
launch { task1() } // 子协程 1
launch { task2() } // 子协程 2
// 父协程会等待 task1、task2 都完成
}
// viewModelScope 取消时,所有子协程一起取消
3.3 coroutineScope 与结构化并发
// coroutineScope:挂起直到所有子协程完成,任一子协程失败会取消整个作用域
suspend fun loadData() = coroutineScope {
val user = async { fetchUser() }
val posts = async { fetchPosts() }
// 挂起,等待 user 和 posts 都完成
Pair(user.await(), posts.await())
}
3.4 取消是协作式的
// cancel() 只是设置取消标志,协程需主动响应
viewModelScope.launch {
while (isActive) { // 检查取消状态
doWork()
}
}
// 挂起函数(delay、withContext 等)会自动检查取消
viewModelScope.launch {
delay(5000) // 若协程被取消,会抛出 CancellationException
}
// 计算密集型任务需手动检查
viewModelScope.launch {
for (i in 0 until 1_000_000) {
if (!isActive) return@launch
heavyComputation(i)
}
}
3.5 viewModelScope 与 lifecycleScope
// ViewModel 中:ViewModel 销毁时自动取消
class MyViewModel : ViewModel() {
fun loadData() {
viewModelScope.launch {
val data = repository.fetchData()
_liveData.value = data
}
// Activity 销毁 → ViewModel.onCleared() → viewModelScope.cancel()
}
}
// Activity/Fragment 中:生命周期 DESTROYED 时自动取消
class MineActivity : AppCompatActivity() {
private fun refreshAfterDelay() {
refreshJob = lifecycleScope.launch {
delay(500)
refreshData()
}
// Activity 销毁 → lifecycleScope.cancel() → refreshJob 取消
}
}
3.6 资源清理
viewModelScope.launch {
try {
val stream = openFile()
use(stream) { /* 使用 */ }
} finally {
// 取消时 finally 仍会执行
releaseResource()
}
}
四、Android 代码示例汇总
4.1 ViewModel 中使用 viewModelScope
class MainViewModel : BaseViewModel() {
private val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
fun getHomeList(page: Int) {
post<HomeListApi, HomeVideoEntity>(...) // 使用回调式 API
}
// 若使用协程
fun loadWithCoroutine() {
viewModelScope.launch {
val data = withContext(Dispatchers.IO) {
repository.fetchData()
}
mHomeLiveData.value = data
}
}
}
4.2 Activity 中使用 lifecycleScope
// MineActivity 中的延迟刷新
private fun delayAndExecute(delayMillis: Long, action: () -> Unit) {
refreshJob?.cancel()
refreshJob = lifecycleScope.launch {
delay(delayMillis)
action.invoke()
}
}
4.3 取消与清理
// 在 onDestroy 中取消,lifecycleScope 会自动处理
// 若需手动取消自定义 Job:
override fun onDestroy() {
refreshJob?.cancel()
super.onDestroy()
}
五、总结对照表
| 主题 | 要点 |
|---|---|
| 协程原理 | 无栈协程、CPS 转换、状态机、Continuation、挂起不阻塞线程 |
| 异常传播 | launch 向上传播、async 在 await 暴露、SupervisorJob 隔离 |
| 异常处理 | CoroutineExceptionHandler(根协程)、try-catch、CancellationException 需重新抛出 |
| 结构化并发 | 父子关系、取消传播、协作式取消、coroutineScope 等待子协程 |
| Android 作用域 | viewModelScope(ViewModel 销毁取消)、lifecycleScope(DESTROYED 取消) |
参考:Kotlin 协程官方文档、Android 架构组件、kotlinx.coroutines 源码