作用域与 Job 树
CoroutineScope 不仅仅是一个上下文容器,它是结构化并发 (Structured Concurrency) 的锚点。
Job 树 (The Job Tree)
协程通过 Job 自动建立父子关系。
mermaid
graph TD
Scope[Scope (Parent Job)] --> JobA(Job A)
Scope --> JobB(Job B)
JobA --> JobA1(Job A-1)
JobA --> JobA2(Job A-2)三大定律:
- 取消传播:Scope 取消 -> Job A/B 取消 -> Job A-1/A-2 取消。
- 异常传播:Job A-1 抛出异常 -> Job A 取消 -> Scope 取消 -> Job B 取消。
- 生命周期:Scope 只有在 A、B、A-1、A-2 全部执行完毕后,自己才能完成。
致命错误:断链 (Breaking the Structure)
极其危险
在 launch 时手动传入一个全新的 Job 实例,会切断它与父级作用域的联系。
kotlin
val scope = CoroutineScope(Job())
scope.launch(Job()) { // ❌ 传入了新 Job,导致断链!
// 1. 父 scope 取消时,这个协程不会取消 (内存泄漏)。
// 2. 这个协程抛出异常时,父 scope 无法感知。
delay(1000000)
}标准作用域
Android Jetpack 提供了与生命周期绑定的作用域:
| 作用域 | 绑定对象 | 默认调度器 | 取消时机 |
|---|---|---|---|
lifecycleScope | Activity / Fragment | Dispatchers.Main.immediate | onDestroy |
viewModelScope | ViewModel | Dispatchers.Main.immediate | onCleared |
GlobalScope | 进程 | Dispatchers.Default | 永不 (除非进程杀掉) |
GlobalScope
严禁在业务逻辑中使用 GlobalScope。它破坏了结构化并发,导致任务无法被取消,是内存泄漏的温床。
自定义作用域模式
如果你需要在非生命周期类(如 Repository 或 Service)中启动协程,请遵循以下标准模式:
kotlin
class DataRepository : AutoCloseable {
// 1. 使用 SupervisorJob 防止单个子任务失败导致整个 Scope 崩溃
// 2. 默认使用 IO 调度器
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
fun loadData() {
scope.launch { /* ... */ }
}
// 3. 必须提供关闭/清理方法
override fun close() {
scope.cancel()
}
}