以 Retrofit suspend 方法举例解析 Kotlin 协程原理

先看举例的代码:
一个 Retrofit API 接口 VideoApi,其方法是 suspend 方法。

interface VideoApi {
    @GET("https://live.{env}.shopee.{cid}/api/v1/homepage/replay")
    suspend fun getVideoList(
        @Path("env") env: String = EnvConstants.NETWORK_ENV,
        @Path("cid") cid: String = EnvConstants.NETWORK_CID,
        @Query("offset") offset: Int = 0,
        @Query("limit") limit: Int = 10
    ): VideoListResponse
}

一个继承了 Paging3 的 RemoteMediator 的类 VideoRemoteMediator 。其 load() 方法是一个 suspend 方法,这个方法体里调用的 videoApi.getVideoList() 也是 suspend 方法。

class VideoRemoteMediator(
    private val videoDatabase: VideoDatabase,
    private val videoApi: VideoApi,
    private val remoteKeyName: String
) : RemoteMediator<Int, VideoInfo>() {
    ...

    override suspend fun load(
        loadType: LoadType,
        state: PagingState<Int, VideoInfo>
    ): MediatorResult {
        ...

        val result = videoApi.getVideoList(offset = loadKey, limit = limit)

        ...
        val nextKey = if (result.videoListData.hasMore) (loadKey + videoList.size) else null
        ...

        return MediatorResult.Success(endOfPaginationReached = videoList.isEmpty())
    }
}

1、load() 方法在主线程的协程里执行,执行到 videoApi.getVideoList()。
在这里插入图片描述
2、videoApi.getVideoList() 方法由 Retrofit 写的动态代理实现。
在这里插入图片描述
3、接着执行 SuspendForBody 的父类 HttpServiceMethod 的 invoke() 方法。
在这里插入图片描述
4、执行 SuspendForBody 的 adapt 方法,Object[] args 参数为 videoApi.getVideoList() 的所有入参,包括最后一个 Continuation 参数。
在这里插入图片描述
5、 进入 KotlinExtension.await() 方法,这是一个用 kotlin 编写的 Call 的扩展 suspend 方法。

suspend fun <T : Any> Call<T>.await(): T {
  return suspendCancellableCoroutine { continuation ->
    continuation.invokeOnCancellation {
      cancel()
    }
    enqueue(object : Callback<T> {
      override fun onResponse(call: Call<T>, response: Response<T>) {
        if (response.isSuccessful) {
          val body = response.body()
          if (body == null) {
            val invocation = call.request().tag(Invocation::class.java)!!
            val method = invocation.method()
            val e = KotlinNullPointerException("Response from " +
                method.declaringClass.name +
                '.' +
                method.name +
                " was null but response body type was declared as non-null")
            continuation.resumeWithException(e)
          } else {
            continuation.resume(body)
          }
        } else {
          continuation.resumeWithException(HttpException(response))
        }
      }

      override fun onFailure(call: Call<T>, t: Throwable) {
        continuation.resumeWithException(t)
      }
    })
  }
}

编译后,其第一个参数是 Call,第二个参数是 Continuation。
这个方法使用了 内联 suspend 方法 suspendCancellableCoroutine() 来实现。

public suspend inline fun <T> suspendCancellableCoroutine(
    crossinline block: (CancellableContinuation<T>) -> Unit
): T =
    suspendCoroutineUninterceptedOrReturn { uCont ->
        val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_CANCELLABLE)
        /*
         * For non-atomic cancellation we setup parent-child relationship immediately
         * in case when `block` blocks the current thread (e.g. Rx2 with trampoline scheduler), but
         * properly supports cancellation.
         */
        cancellable.initCancellability()
        block(cancellable)
        cancellable.getResult()
    }

而 suspendCancellableCoroutine() 又使用了 内联 suspend 方法 suspendCoroutineUninterceptedOrReturn() 来实现。

public suspend inline fun <T> suspendCoroutineUninterceptedOrReturn(crossinline block: (Continuation<T>) -> Any?): T {
    contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
    throw NotImplementedError("Implementation of suspendCoroutineUninterceptedOrReturn is intrinsic")
}

suspendCoroutineUninterceptedOrReturn() 方法在编译期实现(参考:https://blog.csdn.net/hegan2010/article/details/117789735),相当于执行:

    return block(continuation)

其中 continuation 为 suspend 方法的隐藏的最后一个 Continuation 参数。

最后,suspend fun <T : Any> Call<T>.await(): T 方法等效于如下:

fun <T : Any> await(call: Call<T>, continuation: Continuation<T>): T {
    val cancellable =
        CancellableContinuationImpl(continuation.intercepted(), resumeMode = MODE_CANCELLABLE)
    cancellable.initCancellability()

    cancellable.invokeOnCancellation {
        call.cancel()
    }
    call.enqueue(object : Callback<T> {
        override fun onResponse(call: Call<T>, response: Response<T>) {
            if (response.isSuccessful) {
                val body = response.body()
                if (body == null) {
                    val invocation = call.request().tag(Invocation::class.java)!!
                    val method = invocation.method()
                    val e = KotlinNullPointerException(
                        "Response from " +
                                method.declaringClass.name +
                                '.' +
                                method.name +
                                " was null but response body type was declared as non-null"
                    )
                    cancellable.resumeWithException(e)
                } else {
                    cancellable.resume(body)
                }
            } else {
                cancellable.resumeWithException(HttpException(response))
            }
        }

        override fun onFailure(call: Call<T>, t: Throwable) {
            cancellable.resumeWithException(t)
        }
    })

    return cancellable.getResult()
}

这里会执行 call.enqueue() 方法将网络请求入队。
在这里插入图片描述
然后返回 COROUTINE_SUSPENDED 将 videoApi.getVideoList() 方法挂起。
在这里插入图片描述
转载请说明出处:https://blog.csdn.net/hegan2010/article/details/117902011

6、当 Retrofit 在网络请求线程中执行完成后,通过 continuation.resume() 方法,将 videoApi.getVideoList() 方法从主线程恢复执行,并传递执行结果。
注:此 continuation 为 KotlinExtension.await() 方法里创建的 CancellableContinuationImpl 。
在这里插入图片描述
7、CancellableContinuationImpl.resume() 方法会进入 DispatchedTask.dispatch() 扩展方法(CancellableContinuationImpl 继承了 DispatchedTask)。使用 CancellableContinuationImpl 代理的 delegate(Continuation) 的 dispatcher(CoroutineDispatcher) 和 context(CoroutineContext) 将其派发到 dispatcher 的线程池中进行恢复执行。
在这里插入图片描述
8、此 dispatcher 为 Dispatcher.Main.immediate(HandlerContext),其将 CancellableContinuationImpl 通过 post 稍后执行。
注:CancellableContinuationImpl 继承了 DispatchedTask,而 DispatchedTask 实现了 Runnable 接口。
在这里插入图片描述
9、回到主线程,执行 CancellableContinuationImpl 父类 DispatchedTask 实现的 run() 方法。
在这里插入图片描述
取出 CancellableContinuationImpl 的 delegate(DispatchedContinuation) 的 continuation(ContinuationImpl)。
这个 continuation 是在 VideoRemoteMediator.load() 方法在调用 VideoApi.getVideoList() 前创建的匿名内部类实例(编译器插入代码实现的,参考:https://blog.csdn.net/hegan2010/article/details/117796291)。
VideoRemoteMediator.load() 方法在编译期间插入代码后类似如下(LocalContinuationImpl 表示 continuation 所属的类。这里只考虑 load() 方法体里只调用了 videoApi.getVideoList() 这一个 suspend 方法):

    override fun load(
        loadType: LoadType,
        state: PagingState<Int, VideoInfo>,
        continuation : Continuation<MediatorResult>
    ): Object {
        class LocalContinuationImpl(completion: Continuation<Any?>?) :
            ContinuationImpl(completion) {
            // 保存 suspend 方法执行结果
            var result: Any? = null

            // 状态机状态标记
            var label = 0

            // 用于保存局部临时变量
            var L$0 : Any? = null
            ...
            var L$x : Any? = null

            protected override fun invokeSuspend(lastResult: Any): Any? {
                result = lastResult
                label = label or Int.MIN_VALUE
                return load(null, null, this) // 恢复执行时重新调用 load() 方法,除了最后一个 continuation 参数,其他的参数都是 null 值。
            }
        }

        var localContinuation: LocalContinuationImpl
        block_label@{
            if (continuation is LocalContinuationImpl) { // 说明这是恢复执行 load() 方法
                localContinuation = continuation
                if (localContinuation.label and Int.MIN_VALUE != 0) {
                    localContinuation.label -= Int.MIN_VALUE
                    break@block_label
                }
            }
            localContinuation = LocalContinuationImpl(continuation) // 说明这是第一次执行 load() 方法
        }

        val lastResult = localContinuation.result
        val state = COROUTINE_SUSPENDED
        val newResult Any?

        when (localContinuation.label) {
            0 -> {
                ... // 执行 videoApi.getVideoList() 方法之前的代码
                localContinuation.label = 1 // 设置下一次恢复执行的状态
                newResult = videoApi.getVideoList(offset = loadKey, limit = limit,continuation = localContinuation)
                if (newResult == state /*COROUTINE_SUSPENDED*/) {
                    return state // videoApi.getVideoList() 返回 COROUTINE_SUSPENDED,说明需要挂起 load() 方法的执行
                }
                // videoApi.getVideoList() 立即返回结果值,说明不需要挂起 load() 方法的执行,退出 when 后继续执行 videoApi.getVideoList() 方法之后的代码,返回最终结果。
            }
            1 -> {
                ... // 恢复 localContinuation 保存的临时变量 L$0, ..., L$x
                newResult = lastResult;
            }
            else -> throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine")
        }

        ... // 执行 videoApi.getVideoList() 方法之后的代码
        MediatorResult finalResult = ...
        return finalResult
    }

10、然后执行上述 continuation(ContinuationImpl) 的 resume() 方法。
在这里插入图片描述
进而执行父类 BaseContinuationImpl 实现的 resumeWith() 方法。
看注释:此循环在 current.resumeWith(param) 中展开递归,以使恢复时的堆栈跟踪更清晰、更短。
这里会调用 invokeSuspend() 方法。
在这里插入图片描述
invokeSuspend() 方法会恢复执行 VideoRemoteMediator.load() 方法。

        class LocalContinuationImpl(completion: Continuation<Any?>?) :
            ContinuationImpl(completion) {
            protected override fun invokeSuspend(lastResult: Any): Any? {
                ...
                return load(null, null, this)
            }
        }

在这里插入图片描述
继续执行后续代码。。。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值