一、什么是上下文
Kotlin协程中,上下文就是这样一个不可变的上下文元素集合,它携带了协程运行所需的各种配置和服务。
1、1 技术定义
// kotlin.coroutines.CoroutineContext.kt
public interface CoroutineContext {
/**
* 通过 Key 获取上下文中的元素
* 类似于:context[Job.Key] 获取 Job 对象
*/
public operator fun <E : Element> get(key: Key<E>): E?
/**
* 合并两个上下文
* 类似于:context1 + context2
*/
public operator fun plus(context: CoroutineContext): CoroutineContext
/**
* 遍历所有元素
* 用于内部实现,一般不直接使用
*/
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
/**
* 移除某个元素
* 类似于:context.minusKey(Job.Key) 移除 Job
*/
public fun minusKey(key: Key<*>): CoroutineContext
}
核心特点:
- 不可变性:一旦创建就不能修改,只能通过
+操作符创建新的上下文 - 元素集合:包含多个不同类型的元素
- 类型安全:通过 Key 确保类型安全
1、2 Element的定义
每个上下文元素必须实现 CoroutineContext.Element 接口:
public interface Element : CoroutineContext {
/**
* 用于在上下文中查找此元素的唯一 Key
* 每个 Element 类型都有一个对应的 Key 单例对象
*/
public val key: Key<*>
}
public interface Key<E : Element>
1、3 Key的作用
Key 就像是元素的"身份证号",用于在上下文中唯一标识一个元素类型。
1、4 实际示例:Job 的实现
让我们看看 Job 是如何实现 Element 的:
// Job.kt (简化版)
public interface Job : CoroutineContext.Element {
// Job 的 Key 是一个单例对象
public companion object Key : CoroutineContext.Key<Job>
// 实现 Element 接口,返回 Key
override val key: CoroutineContext.Key<*>
get() = Key
// Job 的其他方法...
public val isActive: Boolean
public val isCompleted: Boolean
public val isCancelled: Boolean
public fun cancel(): Boolean
// ...
}
关键理解:
- 每种 Element 类型都有一个唯一的 Key:
Job.Key、CoroutineName.Key等 - 一个 CoroutineContext 中每种 Key 最多只有一个对应的 Element:你不能在同一个上下文中有两个 Job
- 使用
context[Key]可以获取对应的 Element:context[Job.Key]返回 Job 对象
1、5 使用示例
// 创建一个包含多个元素的上下文
val context = Dispatchers.Main + Job() + CoroutineName("MyCoroutine")
// 通过 Key 获取元素
val job = context[Job.Key] // 获取 Job 对象
val name = context[CoroutineName.Key] // 获取 CoroutineName 对象
val dispatcher = context[ContinuationInterceptor.Key] // 获取 Dispatcher
println(job) // 输出:JobImpl{Active}@12345678
println(name) // 输出:CoroutineName(MyCoroutine)
println(dispatcher) // 输出:HandlerDispatcher@Main
二、常见的上下文元素详解
2、1 Job - 协程的生命周期控制器
作用: 控制协程的生命周期,包括取消、等待完成等。
Key: Job.Key
示例:
// 创建一个 Job
val job = Job()
// 创建一个包含 Job 的上下文
val context = Dispatchers.Main + job
// 使用 Job 控制协程
val scope = CoroutineScope(context)
scope.launch {
println("协程开始执行")
delay(1000)
println("协程执行完毕")
}
// 取消协程
job.cancel() // 这会取消所有使用这个 Job 的协程
Job 的状态:
isActive:协程是否活跃isCompleted:协程是否已完成isCancelled:协程是否被取消
2、2 CoroutineDispatcher - 协程的执行器
作用: 决定协程在哪个线程或线程池中执行。
Key: ContinuationInterceptor.Key(注意:Dispatcher 实际上是 ContinuationInterceptor 的子类)
常见的 Dispatcher:
| Dispatcher | 说明 | 使用场景 |
|---|---|---|
Dispatchers.Main | 主线程 | UI 操作、Android 界面更新 |
Dispatchers.IO | IO 线程池 | 网络请求、文件读写、数据库操作 |
Dispatchers.Default | CPU 密集型线程池 | 计算密集型任务 |
Dispatchers.Unconfined | 不限制线程 | 特殊场景,一般不使用 |
示例:
// 在主线程执行
launch(Dispatchers.Main) {
// 更新 UI
textView.text = "Hello"
}
// 在 IO 线程执行
launch(Dispatchers.IO) {
// 网络请求
val response = httpClient.get("https://api.example.com")
}
// 在默认线程池执行
launch(Dispatchers.Default) {
// 计算密集型任务
val result = heavyComputation()
}
2、3 CoroutineName - 协程的名称
作用: 为协程命名,方便调试和日志追踪。
Key: CoroutineName.Key
示例:
// 创建一个带名称的协程
launch(CoroutineName("DataLoader")) {
println("协程名称: ${coroutineContext[CoroutineName]}")
// 输出:协程名称: CoroutineName(DataLoader)
}
// 在调试时,协程名称会显示在日志中
// 例如:[DataLoader @coroutine#1] Loading data...
2、4 CoroutineExceptionHandler - 异常处理器
作用: 处理协程中未捕获的异常。
Key: CoroutineExceptionHandler.Key
示例:
// 创建异常处理器
val handler = CoroutineExceptionHandler { context, exception ->
println("协程异常: $exception")
println("协程上下文: $context")
}
// 使用异常处理器
val scope = CoroutineScope(Dispatchers.Main + handler)
scope.launch {
throw RuntimeException("出错了!")
}
// 输出:
// 协程异常: java.lang.RuntimeException: 出错了!
// 协程上下文: [CoroutineExceptionHandler@12345678, JobImpl{Cancelled}@87654321]
三、上下文的 + 操作符详解
3、1 基本用法
使用 + 操作符可以合并两个上下文:
// 示例 1:逐步构建上下文
val context1 = Dispatchers.Main
val context2 = context1 + Job()
val context3 = context2 + CoroutineName("MyCoroutine")
// context3 包含:Dispatchers.Main, Job, CoroutineName
// 示例 2:一次性构建
val context = Dispatchers.Main + Job() + CoroutineName("MyCoroutine")
3、2 覆盖规则
如果两个上下文包含相同 Key 的元素,右边的元素会覆盖左边的:
// 覆盖示例
val context1 = Dispatchers.Main + Job() + CoroutineName("Parent")
val context2 = context1 + Dispatchers.IO
// context2 中的 Dispatcher 变成了 Dispatchers.IO(覆盖了 Dispatchers.Main)
// 但 Job 和 CoroutineName 保持不变
println(context2[ContinuationInterceptor]) // 输出:Dispatchers.IO
println(context2[Job]) // 输出:原来的 Job
println(context2[CoroutineName]) // 输出:CoroutineName(Parent)
3、3 为什么右边覆盖左边?
这是为了提供灵活性。想象一下:
// 基础上下文
val baseContext = Dispatchers.Main + Job() + CoroutineName("Base")
// 在特定场景下,我们想改变 Dispatcher,但保留其他配置
val specialContext = baseContext + Dispatchers.IO
// specialContext 现在包含:
// - Dispatchers.IO(覆盖了 Main)
// - Job(继承自 baseContext)
// - CoroutineName("Base")(继承自 baseContext)
3、4 实际应用示例
// 创建一个基础作用域
val baseScope = CoroutineScope(
Dispatchers.Main +
Job() +
CoroutineName("BaseScope") +
CoroutineExceptionHandler { _, e ->
println("全局异常处理: $e")
}
)
// 在基础作用域中启动协程
baseScope.launch {
// 这个协程继承了所有基础配置
println("协程名称: ${coroutineContext[CoroutineName]}")
// 输出:协程名称: CoroutineName(BaseScope)
// 但我们可以覆盖某些配置
launch(Dispatchers.IO + CoroutineName("Child")) {
// 这个子协程覆盖了 Dispatcher 和 CoroutineName
println("子协程名称: ${coroutineContext[CoroutineName]}")
println("子协程 Dispatcher: ${coroutineContext[ContinuationInterceptor]}")
// 输出:
// 子协程名称: CoroutineName(Child)
// 子协程 Dispatcher: Dispatchers.IO
}
}
四、子协程的上下文继承机制
4、1 继承规则
当使用 launch 或 async 启动子协程时:
- 默认继承父协程的完整上下文
- 可以覆盖特定元素
- 未覆盖的元素保持不变
4、2 详细示例
// 创建父协程作用域
val scope = CoroutineScope(
Dispatchers.Main +
Job() +
CoroutineName("Parent") +
CoroutineExceptionHandler { _, e -> println("父异常: $e") }
)
scope.launch {
// 这个协程继承了父作用域的所有配置
println("=== 父协程 ===")
println("名称: ${coroutineContext[CoroutineName]}")
println("Dispatcher: ${coroutineContext[ContinuationInterceptor]}")
println("Job: ${coroutineContext[Job]}")
println("异常处理器: ${coroutineContext[CoroutineExceptionHandler]}")
// 启动子协程,覆盖部分配置
launch(
context = Dispatchers.IO + CoroutineName("Child"),
block = {
println("\n=== 子协程 ===")
println("名称: ${coroutineContext[CoroutineName]}")
println("Dispatcher: ${coroutineContext[ContinuationInterceptor]}")
println("Job: ${coroutineContext[Job]}")
println("异常处理器: ${coroutineContext[CoroutineExceptionHandler]}")
}
)
}
输出:
=== 父协程 ===
名称: CoroutineName(Parent)
Dispatcher: Dispatchers.Main
Job: JobImpl{Active}@12345678
异常处理器: CoroutineExceptionHandler@87654321
=== 子协程 ===
名称: CoroutineName(Child) ← 被覆盖
Dispatcher: Dispatchers.IO ← 被覆盖
Job: JobImpl{Active}@12345678 ← 继承自父协程
异常处理器: CoroutineExceptionHandler@87654321 ← 继承自父协程
4、3 继承链图
父作用域上下文
┌─────────────────────────────────────┐
│ Dispatchers.Main │
│ Job(parent) │
│ CoroutineName("Parent") │
│ CoroutineExceptionHandler │
└─────────────────────────────────────┘
↓ 继承
子协程上下文(未覆盖)
┌─────────────────────────────────────┐
│ Dispatchers.Main ← 继承 │
│ Job(child) ← 新创建,parent=parent │
│ CoroutineName("Parent") ← 继承 │
│ CoroutineExceptionHandler ← 继承 │
└─────────────────────────────────────┘
子协程上下文(覆盖部分)
┌─────────────────────────────────────┐
│ Dispatchers.IO ← 覆盖 │
│ Job(child) ← 新创建,parent=parent │
│ CoroutineName("Child") ← 覆盖 │
│ CoroutineExceptionHandler ← 继承 │
└─────────────────────────────────────┘
4、4 Job 的特殊继承规则
重要: 子协程的 Job 总是新的,但它的 parent 是父协程的 Job。
val parentJob = Job()
val scope = CoroutineScope(Dispatchers.Main + parentJob)
scope.launch {
val childJob = coroutineContext[Job]!!
println("父 Job: $parentJob")
println("子 Job: $childJob")
println("子 Job 的父 Job: ${childJob.parent}")
// 输出:
// 父 Job: JobImpl{Active}@12345678
// 子 Job: JobImpl{Active}@87654321
// 子 Job 的父 Job: JobImpl{Active}@12345678
}
这意味着:
- 取消父 Job 会取消所有子 Job
- 子 Job 失败会影响父 Job(除非使用
SupervisorJob)
五、上下文的其他操作
5、1 minusKey - 移除元素
val context = Dispatchers.Main + Job() + CoroutineName("MyCoroutine")
// 移除 Job
val contextWithoutJob = context.minusKey(Job.Key)
println(context[Job]) // 输出:JobImpl{Active}@12345678
println(contextWithoutJob[Job]) // 输出:null
5、2 fold - 遍历元素
val context = Dispatchers.Main + Job() + CoroutineName("MyCoroutine")
// 遍历所有元素
context.fold("元素列表: ") { acc, element ->
"$acc, ${element.key}"
}
// 输出:元素列表: , ContinuationInterceptor.Key, Job.Key, CoroutineName.Key
六、实际应用场景
6、1 创建自定义协程作用域
// 创建一个专门用于网络请求的作用域
class NetworkScope {
private val job = SupervisorJob()
private val handler = CoroutineExceptionHandler { _, e ->
println("网络请求失败: $e")
}
val scope = CoroutineScope(
Dispatchers.IO +
job +
CoroutineName("NetworkScope") +
handler
)
fun cleanup() {
job.cancel()
}
}
// 使用
val networkScope = NetworkScope()
networkScope.scope.launch {
// 网络请求
val data = fetchData()
println(data)
}
// 清理
networkScope.cleanup()
七、总结
上下文的核心概念:
- 不可变的元素集合:类似于一个不可变的 Map,Key 是元素类型,Value 是元素实例
- Element 和 Key:每个 Element 类型都有一个唯一的 Key,用于标识和查找
- + 操作符:用于合并上下文,右边的元素会覆盖左边的相同 Key 的元素
- 上下文继承:子协程默认继承父协程的上下文,但可以覆盖特定元素
- 常见元素:
Job:控制生命周期CoroutineDispatcher:决定执行线程CoroutineName:协程名称CoroutineExceptionHandler:异常处理