首要
先说总结,CoroutineContext的作用是为协程存储各品种信息,实质上是一种调集(能够理解为一种Map);存储的这些类都是CoroutineContext的具体子类,他们都有各自的作用,在运用时直接经过CoroutineContext的get办法取出,非常便利;
CoroutineContext的源码在CoroutineContext.kt文件中,代码很短,可是细节需要特别注意。先看类阐明:
Persistent context for the coroutine. It is an indexed set of Element instances. An indexed set is a mix between a set and a map. Every element in this set has a unique Key.
翻译下:
为协程供给上下文。它是Element实例的索引集。索引集是集和映射之间的混合体。这个调集中的每个元素都有一个仅有的Key。
在此主张咱们先阅览CoroutineContext源码,再看下文章 Kotlin协程源码剖析-7 Context左向链表 ;
先将文章中的示例代码拿出来,便利咱们后续检查
public class My3CoroutineName(
val name: String
) : AbstractCoroutineContextElement(My3CoroutineName) {
public companion object Key : CoroutineContext.Key<My3CoroutineName>
override fun toString(): String = "CoroutineName($name)"
}
public class My4CoroutineName(
val name: String
) : AbstractCoroutineContextElement(My4CoroutineName) {
public companion object Key : CoroutineContext.Key<My4CoroutineName>
override fun toString(): String = "CoroutineName($name)"
}
这段代码中有一个特别需要阐明的点就是My3CoroutineName
承继了类AbstractCoroutineContextElement
,该类定义如下
public abstract class AbstractCoroutineContextElement(public override val key: Key<*>) : Element
构造函数传递的参数类型是key
类型,而咱们传递的是My3CoroutineName
,形似错误但却能正常运转,因为这里用到了Kotlin的语法糖;此处的My3CoroutineName
实践指向的是该类的伴生对象Key
;这个用法在协程中运用广泛。
- CoroutineScope.launch创立newContext时,
combined[ContinuationInterceptor]
@ExperimentalCoroutinesApi
public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
val combined = foldCopies(coroutineContext, context, true)
val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
debug + Dispatchers.Default else debug
}
- AbstractCoroutine初始化时获取父Job时,
parentContext[Job]
init {
if (initParentJob) initParentJob(parentContext[Job])
}
其次
文章Kotlin协程源码剖析-7 Context左向链表 中的给出的示例对于咱们理解左向链表有着非常大的帮助,在此额外弥补个示例,小伙伴能够自己先考虑下,再比对下成果是否与自己想的共同;
fun studyContext4() {
val my3CoroutineName = My3CoroutineName("item3")
val my4CoroutineName = My4CoroutineName("item4")
val my5CoroutineName = My3CoroutineName("item5")
val newElement = (my3CoroutineName + my4CoroutineName) + my5CoroutineName
println("(3+4)+5:$newElement")
}
输出如下
(3+4)+5:[CoroutineName(item4), CoroutineName(item5)]
咱们发现item3
被移除掉了,实质原因在文章开头说过了,这个调集中的每个元素都有一个仅有的Key
,item3
,item5
虽然是2个对象,可是他们具有相同的Key,所以在履行plus操作时,原先的item3
会被移除掉,成果就是item4
,item5
。
最后
运用一个实践工作的例子来结束这篇文章。如果之前没有用过反常处理器的小伙伴,主张阅览Kotlin 协程的反常处理, Kotlin协程核心库剖析-5 Job反常处理器注意点
实践开发工作中,咱们有时需要对协程或许出现的反常增加try catch处理逻辑,可是每次编写这样的样板代码,浪费时间且毫无营养,于是就想着做个包装办法处理这段逻辑;
fun <T> CoroutineScope.launchWithCatch(
context: CoroutineContext = EmptyCoroutineContext,
onComplete: ((Result<T>) -> Unit)? = null,
onCancel: (() -> Unit)? = null,
errorHandler: ((exception: Throwable) -> Unit)? = null,
onFinally:((Result<T>) -> Unit)? = null,
block: suspend CoroutineScope.() -> T
): Job {
val ref = AtomicReference<CoroutineContext>(null)
//由于当时的CoroutineScope纷歧定是根协程;因而必须参加SupervisorJob;不然或许出现CoroutineExceptionHandler无法捕获的bug请注意
return this.launch(context + SupervisorJob() + CoroutineExceptionHandler { coroutineContext, throwable ->
//只有当coroutineContext是根coroutineContext时才表明是一个未处理的子协程反常,
//不然或许是supervisorScope未配置CoroutineExceptionHandler导致的反常传递,此时根协程会进行履行coroutineContext:$coroutineContext ref.get():${ref.get()}", )
throwable.printStackTrace()
if (coroutineContext == ref.get()) {
handleException(throwable, errorHandler, onComplete, onFinally)
}
}) {
ref.set(this.coroutineContext)
try {
val result = block()
onComplete?.invoke(Result.success(result))
onFinally?.invoke(Result.success(result))
} catch (e: Exception) {
if (e is CancellationException) {
onCancel?.invoke()
onFinally?.invoke(Result.failure(e))
} else {
throw e
}
}
}
}
private fun <T> handleException(
e: Throwable,
errorHandler: ((exception: Throwable) -> Unit)?,
onComplete: ((Result<T>) -> Unit)? = null,
onFinally:((Result<T>) -> Unit)? = null
) {
e.printStackTrace()
//反常处理
try {
errorHandler?.invoke(e)
} catch (e: Exception) {
e.printStackTrace()
}
try {
onComplete?.invoke(Result.failure(e))
} catch (e: Exception) {
e.printStackTrace()
}
try {
onFinally?.invoke(Result.failure(e))
} catch (e: Exception) {
e.printStackTrace()
}
}
代码测试正常反常处理,但实践上这段代码存在逻辑隐患,读者朋友能够细心考虑下,问题出在哪里了;
下面给出反常case,在Android activity oncreate办法中履行以下办法:
private fun testLaunchWithCatch() {
lifecycleScope.launchWithCatch {
while (isActive) {
// do loop operate
println(">>>>I'm do loop")
delay(1000)
}
}
}
咱们发现即便activity退出了,协程仍然在工作,>>>>I'm do loop
会一直输出;
问题原因就在于,SupervisorJob()
的参加导致了原先的父子结构发生了变化;他的承继关系是SupervisorJob
–〉JobImpl
–〉JobSupport
–〉Job
;
回忆下文章开头协程获取设置父Job的场景,代码在类AbstractCoroutine
的初始化函数中,
init {
if (initParentJob) initParentJob(parentContext[Job])
}
运用launchWithCatch导致新生成的协程不再是lifecycleScope的子协程,而是SupervisorJob的子协程,因而当activity页面lifecycleScope cancel时,刚刚建议的协程无法正常封闭;
好了,问题已经知道了,供给解决办法就是让SupervisorJob变成lifecycleScope的子协程即可,代码如下
fun <T> CoroutineScope.launchWithCatch(
...
val parentJob = context[Job] ?: coroutineContext[Job]
return this.launch(context + SupervisorJob(parentJob) + CoroutineExceptionHandler {
...