Coroutine优势:性能快,语法简单,业务清晰
Thread性能差,Callback业务嵌套过多时容易产生回调地狱,RxJava不熟悉的人不会合理运用链式函数编程。自定义协程拦截器:
版权声明:本文为CSDN博主「不会写代码的丝丽」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qfanmingyiq/article/details/105181027
完整代码:
class MyCoroutineDispatch : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
override fun interceptContinuation(continuation: Continuation): Continuation {
log("interceptContinuation")
return MyInterceptorContinuation(continuation.context, continuation)
}
override fun releaseInterceptedContinuation(continuation: Continuation<*>) {
super.releaseInterceptedContinuation(continuation)
log("releaseInterceptedContinuation " + continuation::class.java.simpleName)
}
class MyInterceptorContinuation(
override val context: CoroutineContext,
val continuation: Continuation
) : Continuation {
override fun resumeWith(result: Result) {
//获取Android主线程的Looper,进而切换主线程
Handler(Looper.getMainLooper()).post {
log("MyInterceptorContinuation resume")
continuation.resumeWith(result)
}
}
}
}
class MyContinuation() : Continuation {
//这里不在使用空上下文
override val context: CoroutineContext = MyCoroutineDispatch()
override fun resumeWith(result: Result) {
log("MyContinuation resumeWith 结果 = ${result.getOrNull()}")
}
}
suspend fun demo() = suspendCoroutine { c ->
thread(name = "demo1创建的线程") {
log("demo 调用resume回调")
c.resume("hello")
}
}
suspend fun demo2() = suspendCoroutine { c ->
thread(name = "demo2创建的线程") {
log("demo2 调用resume回调")
c.resume("world")
}
}
fun testInterceptor() {
// 假设下面的lambda需要在UI线程运行
val suspendLambda = suspend {
log("demo 运行前")
val resultOne = demo()
log("demo 运行后")
val resultTwo = demo2()
log("demo2 运行后")
//拼接结果
resultOne + resultTwo
}
val myContinuation = MyContinuation()
thread(name = "一个新的线程") {
suspendLambda.startCoroutine(myContinuation)
}
}
fun log(msg: String) {
Log.e("TAG","[${Thread.currentThread().name}] ${msg}")
}
上文代码输出结果:
[一个新的线程] interceptContinuation
[main] MyInterceptorContinuation resume
[main] demo 运行前
[demo1创建的线程] demo 调用resume回调
[main] MyInterceptorContinuation resume
[main] demo 运行后
[demo2创建的线程] demo2 调用resume回调
[main] MyInterceptorContinuation resume
[main] demo2 运行后
[main] releaseInterceptedContinuation MyInterceptorContinuation
[main] MyContinuation resumeWith 结果 = helloworld
自定义协程拦截器:
class MyCoroutineDispatch :
AbstractCoroutineContextElement(ContinuationInterceptor),
ContinuationInterceptor {
}
我们继承AbstractCoroutineContextElement类,并实现了ContinuationInterceptor接口,我们分别看看各自的用处。
AbstractCoroutineContextElement 的声明:
public abstract class AbstractCoroutineContextElement(public override val key: Key<*>) : Element
可以看到了实现了Element接口其实就是一个协程上下文 :
public interface Element : CoroutineContext {
public val key: Key<*>
public override operator fun get(key: Key): E? =
@Suppress("UNCHECKED_CAST")
if (this.key == key) this as E else null
public override fun fold(initial: R, operation: (R, Element) -> R): R =
operation(initial, this)
public override fun minusKey(key: Key<*>): CoroutineContext =
if (this.key == key) EmptyCoroutineContext else this
}
Element可以放入某个协程上下文中的链表存储的对象。而Element本身也是一个上下文对象。在上下文中可以用get函数或者[]操作符获取对应的存储对象。
所以这个MyCoroutineDispatch可以当做上下文使用,并且也可以放入其他上下文存储,自身的key是ContinuationInterceptor。所以他可以放入MyContinuation中做上下文对象。
再看看ContinuationInterceptor:
public interface ContinuationInterceptor : CoroutineContext.Element {
companion object Key : CoroutineContext.Key
public fun interceptContinuation(continuation: Continuation): Continuation
public fun releaseInterceptedContinuation(continuation: Continuation<*>) {
/* do nothing by default */
}
}
ContinuationInterceptor是一个拦截规范,interceptContinuation传入一个原始continuation对象,然后返回一个代理的Continuation,然后在代理Continuation中进行现场切换。如果不返回代理continuation,直接返回原始continuation 即可。当状态机结束的时候releaseInterceptedContinuation会被调用,参数是interceptContinuation返回的对象。
获取拦截器流程:
分析以下代码:
fun testInterceptor() {
// 假设下面的lambda需要在UI线程运行
val suspendLambda = suspend {
log("demo 运行前")
val resultOne = demo()
log("demo 运行后")
val resultTwo = demo2()
log("demo2 运行后")
//拼接结果
resultOne + resultTwo
}
val myContinuation = MyContinuation()
thread(name = "一个新的线程") {
suspendLambda.startCoroutine(myContinuation)
}
}
当遇到 suspend {}调用 startCoroutine的代码,编译器会把suspend{}编译成SuspendLambda,作为协程体。
SuspendLambda继承了ContinuationImpl,重点看看ContinuationImpl代码,注意获取context的方法:
@SinceKotlin("1.3")
// Suspension lambdas inherit from this class
internal abstract class SuspendLambda(
public override val arity: Int,
completion: Continuation?
) : ContinuationImpl(completion), FunctionBase, SuspendFunction {
constructor(arity: Int) : this(arity, null)
public override fun toString(): String =
if (completion == null)
Reflection.renderLambdaToString(this) // this is lambda
else
super.toString() // this is continuation
}
@SinceKotlin("1.3")
// State machines for named suspend functions extend from this class
internal abstract class ContinuationImpl(
completion: Continuation?,
private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {
//使用传入completion的上下文作为ContinuationImpl的上下文。
//MyContinuation是completion,而MyContinuation的上下文MyCoroutineDispatch
//MyCoroutineDispatch就是我们创建的拦截器
constructor(completion: Continuation?) : this(completion, completion?.context)
public override val context: CoroutineContext
get() = _context!!
@Transient
private var intercepted: Continuation? = null
public fun intercepted(): Continuation =
intercepted
?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
.also { intercepted = it }
protected override fun releaseIntercepted() {
val intercepted = intercepted
if (intercepted != null && intercepted !== this) {
context[ContinuationInterceptor]!!.releaseInterceptedContinuation(intercepted)
}
this.intercepted = CompletedContinuation // just in case
}
}
我们再来看看个函数intercepted
//ContinuationImpl.kt
@Transient
private var intercepted: Continuation? = null
public fun intercepted(): Continuation =
//如果拦截器为空那么会做如下三步
//1.上下文中获取可以为ContinuationInterceptor的拦截器
//2.调用拦截器interceptContinuation函数获取一个代理Continuation对象。所以拦截器的interceptContinuation只会调用一次
//3.保存拦截器返回的代理Continuation对象后面方便再次获取就不需要再次调用interceptContinuation
intercepted
?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
.also {
//保存获取的拦截器
intercepted = it
}
我们最后看看什么时候第一次调用intercepted的代码。
public fun (suspend () -> T).startCoroutine(
completion: Continuation
) {
createCoroutineUnintercepted(completion).intercepted().resume(Unit)
}
启动协程的时候回获取一次拦截器,然后用拦截器返回代理Continuation 去执行resume方法
再来看看我们的写的拦截器:
class MyCoroutineDispatch : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
//intercepted()第一次调用的会调用到这里
override fun interceptContinuation(continuation: Continuation): Continuation {
log("interceptContinuation")
//返回一个代理Continuation对象
return MyInterceptorContinuation(continuation.context, continuation)
}
override fun releaseInterceptedContinuation(continuation: Continuation<*>) {
super.releaseInterceptedContinuation(continuation)
log("releaseInterceptedContinuation " + continuation::class.java.simpleName)
}
class MyInterceptorContinuation(
override val context: CoroutineContext,
val continuation: Continuation
) : Continuation {
override fun resumeWith(result: Result) {
//获取Android主线程的Looper,进而切换主线程
Handler(Looper.getMainLooper()).post {
log("MyInterceptorContinuation resume")
//回调原始的Continuation对象
continuation.resumeWith(result)
}
}
}
}
当调用启动协程的时候会调用拦截器的代理Continuation对象的resumeWith,然后在Ui线程回调原始Continuation对象。
我们再看看我的挂起函数demo又是怎么切换回ui线程
suspend fun demo() = suspendCoroutine { c ->
thread(name = "demo1创建的线程") {
log("demo 调用resume回调")
c.resume("hello")
}
}
在正常不启用拦截器的情况会回调suspendLambda在demo1创建的线程线程回调。但是我们发现启用拦截器后被在ui线程回调。而真正做切换的逻辑在suspendCoroutine这个lambda表达式上。
public suspend inline fun suspendCoroutine(crossinline block: (Continuation) -> Unit): T =
suspendCoroutineUninterceptedOrReturn { c: Continuation ->
val safe = SafeContinuation(c.intercepted())//返回拦截器的代理的Continuation对象
block(safe)
safe.getOrThrow()
}
这里我们便知道答案。demo()函数拿到的Continuation会经过一层拦截器代理对象,一切便自然解释的通了。
总结:拦截器返回一个代理Continuation对象给挂起函数,当挂起函数恢复的时候,恢复代理Continuation的resume函数,最后代理Continuation对象切换指定的线程在回调原始的Continuation对象。