协程拦截器工作原理

249 阅读5分钟

我们现在已经知道了协程可以通过调用其他挂起函数实现真正的挂起,挂起函数中利用传进来的Continuation(协程体本身)调用resumeWith就能实现恢复。现在梳理一下,拦截器是如何工作的。

通过之前文章的讨论,我们对拦截器有了个基本的了解,这里强调一下,我在刚开始学习的时候,思维收到了okhttp 拦截器概念的干扰,我以为也可以进行拦截器链式调用,概念虽然相似但并非完全一回事。

还有就是想到拦截器不要下意识的认为它只能切换线程,不否定它的主要功能就是切线程,但不是只能切线程,比如还能打个log啥的。做个LogContinuationInterceptor


首先,拦截器作为一个CoroutineContext 可以被赋值给completion 并加入到协程体中。

那么说明他一定是一个CoroutineContext.Element,这个没异议的。

第二点,但凡对拦截器这个设计套路有点了解的朋友都会知道,肯定存在一个传入一个A,返回一个另一个A的函数。有点类似代理或者说是hook的意思。

看一下官方定义的拦截器接口。

public interface ContinuationInterceptor : CoroutineContext.Element {
  
    companion object Key : CoroutineContext.Key<ContinuationInterceptor>
    
    //传进来一个返回去一个。传进来的肯定是原来的,返回去的就是拦截器
    public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>

   	//当状态机结束的时候releaseInterceptedContinuation会被调用,参数是interceptContinuation返回的对象。
    public fun releaseInterceptedContinuation(continuation: Continuation<*>) {
        /* do nothing by default */
    }
}

Demo代码

先给出一份实现在回调状态机时把线程切回mian的demo代码,下面将围绕这个程序来分析。


class MyCoroutineDispatch : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {

    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        log("interceptContinuation")
        return MyInterceptorContinuation<T>(continuation.context, continuation)
    }

    override fun releaseInterceptedContinuation(continuation: Continuation<*>) {
        super.releaseInterceptedContinuation(continuation)

        log("releaseInterceptedContinuation " + continuation::class.java.simpleName)
    }


    class MyInterceptorContinuation<T>(
        override val context: CoroutineContext,
        val continuation: Continuation<T>
    ) : Continuation<T> {


        override fun resumeWith(result: Result<T>) {
            //获取Android主线程的Looper,进而切换主线程
            Handler(Looper.getMainLooper()).post {
                log("MyInterceptorContinuation resume")
                continuation.resumeWith(result)
            }

        }

    }
}

class MyContinuation() : Continuation<String> {
    //这里不在使用空上下文
    override val context: CoroutineContext = MyCoroutineDispatch()
    override fun resumeWith(result: Result<String>) {
        log("MyContinuation resumeWith 结果 = ${result.getOrNull()}")
    }

}

suspend fun demo() = suspendCoroutine<String> { c ->

    thread(name = "demo1创建的线程") {
        log("demo 调用resume回调")
        c.resume("hello")
    }

}

suspend fun demo2() = suspendCoroutine<String> { 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
*/

拦截器的使用方式为:

suspend{
	...
}.startCoroutine(object : Continuation<Int>{
  //把定义好的协程上下文赋值给作为完成回调的Continuation(completion)实例,这样就可以将它绑定到协程上了。
    val context: CoroutineContext = MyCoroutineDispatch()
})

把定义好的协程上下文赋值给作为完成回调的Continuation(completion)实例,这样就可以将它绑定到协程上了。

可见协程上下文传递的起点是completion,那么为什么是这里?只能从源码里找答案了。

internal abstract class ContinuationImpl(
    completion: Continuation<Any?>?,
    private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {
    //使用传入completion的上下文作为ContinuationImpl的上下文。
	//例子中的MyContinuation是completion,而MyContinuation的上下文(可能包含)MyCoroutineDispatch就是我们创建的拦截器
    constructor(completion: Continuation<Any?>?) : this(completion, completion?.context)
	
    public override val context: CoroutineContext
        get() = _context!!

    @Transient
    private var intercepted: Continuation<Any?>? = null
	
    //调用这个函数的目的是拿到一个continuation,调用处并不知道是否设置了拦截器,如果设置了拦截器那么拿到的就是代理的continuation
    public fun intercepted(): Continuation<Any?> =
    	//保存了获取到的拦截器   
	    intercepted
    		//如果context没设置拦截器,那么就把自己返回。
    		//如果有那么调用interceptContinuation去创建一个Continuation
    		//比如Lite框架设计中 Dispatcher对象就是通过ContinuationInterceptor传递给了InterceptContinuation
            ?: (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
    }
}

源码总结:

通过反编译不难得知:suspendLambda.startCoroutine(myContinuation) 创建协程的过程中,通过create函数创建了协程体,也就是:

ContinuationImpl(suspendLambda会被转化为SuspendLambdaSuspendLambda又继承ContinuationImpl)

image-20220727112014994.png

这会调用单参数构造方法:

 constructor(completion: Continuation<Any?>?) : this(completion, completion?.context)

再看一下协程上下文的获取方法:

 public override val context: CoroutineContext
        get() = _context!!

也就是说创建的协程体的context ,就是complete 的 context

所以当我们使用拦截器的时候,首先绑到了completion上,创建协程的时候,又把completion的上下文列表赋值给了协程体

函数 intercepted

 //调用这个函数的目的是拿到一个continuation,调用处并不知道是否设置了拦截器,如果设置了拦截器那么拿到的就是代理的continuation
    public fun intercepted(): Continuation<Any?> =
    	//保存了获取到的拦截器   
	    intercepted
    		//如果context没设置拦截器,那么就把自己返回。
    		//如果有那么调用interceptContinuation去创建一个Continuation
    		//比如Lite框架设计中 Dispatcher对象就是通过ContinuationInterceptor传递给了InterceptContinuation
            ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
                .also { intercepted = it }

intercepted什么时候第一次调用

public fun <T> (suspend () -> T).startCoroutine(
    completion: Continuation<T>
) {
    createCoroutineUnintercepted(completion).intercepted().resume(Unit)
}

启动协程的时候回获取一次拦截器,然后用拦截器返回代理Continuationresume


class MyCoroutineDispatch : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
		//intercepted()第一次调用的会调用到这里
    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        log("interceptContinuation")
        //返回一个代理Continuation对象
        return MyInterceptorContinuation<T>(continuation.context, continuation)
    }

    override fun releaseInterceptedContinuation(continuation: Continuation<*>) {
        super.releaseInterceptedContinuation(continuation)

        log("releaseInterceptedContinuation " + continuation::class.java.simpleName)
    }


    class MyInterceptorContinuation<T>(
        override val context: CoroutineContext,
        val continuation: Continuation<T>
    ) : Continuation<T> {


        override fun resumeWith(result: Result<T>) {
            //获取Android主线程的Looper,进而切换主线程
            Handler(Looper.getMainLooper()).post {
                log("MyInterceptorContinuation resume")
                //回调原始的Continuation对象
                continuation.resumeWith(result)
            }

        }

    }
}

是不是非常清晰了呢。

我们再看看我的挂起函数demo又是怎么切换回ui线程

suspend fun demo() = suspendCoroutine<String> { c ->

    thread(name = "demo1创建的线程") {
        log("demo 调用resume回调")
        c.resume("hello")
    }
}

注意看,c.resume 这个 c 是哪个c呢?首先他肯定是SafeContinuation,但是熟悉SafeContinuation的人都知道,这就是一个马甲,关键看他的delegate是哪个。如果是状态机的c,那么好像就回不到主线程了吧?所以点进去瞅一眼

public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T =
    suspendCoroutineUninterceptedOrReturn { c: Continuation<T> ->
        val safe = SafeContinuation(c.intercepted())//返回拦截器的代理的Continuation对象
        block(safe)
        safe.getOrThrow()
    }

到此,拦截器整个的工作原理,就分析完了。是不是非常简单呢

20200329194547643.jpeg