1. 协程作用域核心概念
协程作用域(CoroutineScope)是 Kotlin 协程框架的基石,它定义了协程的运行边界、生命周期以及与 Job 的绑定关系。理解作用域是正确管理协程、避免内存泄漏和任务失控的前提。
1.1 作用域定义
作用域是一个结构化的协程容器,它持有自己的 CoroutineContext,并负责启动、追踪和取消其内部的所有协程。任何协程创建函数(如 launch、async)都必须在作用域内调用,否则编译失败。
- 全局作用域:
GlobalScope,与应用进程同生命周期,不推荐用于常规业务,易泄漏。 - 自定义作用域:通过
CoroutineScope(coroutineContext)手动创建,需主动管理。 - 框架作用域:如
lifecycleScope(Android)、viewModelScope、coroutineScope(挂起函数内结构化并发)。
1.2 协程生命周期
每个协程在执行过程中经历一系列状态,这些状态由 Job 对象维护。Job 是协程的句柄,也是生命周期的载体。
| 状态 | 说明 | Job.isActive | Job.isCompleted | Job.isCancelled |
|---|---|---|---|---|
| New | 刚创建,尚未启动(对标准启动策略可能跳过) | false | false | false |
| Active | 正在执行代码 | true | false | false |
| Completing | 协程体执行完毕,等待子协程完成 | true | false | false |
| Completed | 正常结束或未被取消的完成状态 | false | true | false |
| Cancelling | 正在执行取消逻辑(如释放资源) | false | false | true |
| Cancelled | 最终被取消的状态 | false | true | true |
1.3 作用域与 Job 的关系
每个 CoroutineScope 内部包含一个 Job,其生命周期与作用域绑定。作用域取消时,它的 Job 进入取消状态,进而级联取消所有子协程。子协程通过 coroutineScope 或 supervisorScope 形成树状结构。
关键约定: 永远不要将挂起函数的
this.coroutineScope 暴露给外部;结构化并发要求作用域内启动的协程必须在函数返回前全部完成(除非使用 launch 在外部作用域启动)。// 示例:手动创建作用域并观察生命周期
import kotlinx.coroutines.*
fun main() = runBlocking {
val scope = CoroutineScope(Dispatchers.Default + Job())
val job = scope.launch {
delay(100)
println("子协程执行中")
}
println("Job.active: ${job.isActive}") // true
delay(50)
scope.cancel() // 取消作用域
println("Job.active: ${job.isActive}") // false
println("Job.isCancelled: ${job.isCancelled}") // true
}
常见错误: 直接使用
GlobalScope.launch 启动协程,但未持有 Job 引用,也无法随着组件的销毁被取消,导致协程泄漏。始终优先使用框架提供的结构化作用域。1.4 核心作用域对比
| 作用域类型 | 生命周期 | Job 策略 | 典型用途 |
|---|---|---|---|
GlobalScope |
应用进程 | 无父 Job,独立 | 极少数全局后台任务(如日志上报) |
lifecycleScope |
Activity/Fragment 生命周期 | 自动取消 | UI-绑定任务,如网络请求 |
viewModelScope |
ViewModel 生命周期 | 自动取消 | 数据加载、数据库操作 |
自定义 CoroutineScope |
由开发者控制 | 需手动 cancel() |
工具类、非标准生命周期组件 |
coroutineScope { } |
当前挂起函数作用域 | 等待所有子协程完成后返回 | 结构化并发,并行-汇聚模式 |
1.5 作用域、Job 与协程生命周期关系图
1.6 调试与常见错误
- 调试命令:在 JVM 启动参数中加入
-Dkotlinx.coroutines.debug=on,日志会输出每个协程所在的 Job 名称和栈信息。 - 检查泄漏:使用
Job.children遍历查看未完成子协程;或结合DebugProbes工具 dump 协程状态。 - 常见陷阱:
withContext(Dispatchers.IO)不会改变外部作用域;在finally块中调用挂起函数需使用withContext(NonCancellable)。
// 调试示例:开启协程调试输出
kotlin -Dkotlinx.coroutines.debug=on -jar app.jar
// 代码中打印当前 Job 信息
fun CoroutineScope.dumpChildren() {
coroutineContext[Job]?.children?.forEach { child ->
println("Child job: $child, active=${child.isActive}")
}
}
实战要点: 在大型项目中建议统一管理作用域生命周期,例如通过
Closeable 接口实现自动取消,或利用 AndroidX Lifecycle 的 lifecycleScope 避免手动管理。使用 supervisorScope 处理独立子任务异常,防止一个失败拖垮整个作用域。