Android 面试系列:Retrofit 的 suspend 是如何实现的?

1 阅读2分钟

今天这个是送分题,但是仍然有很多人不清楚,今天就聊聊这个

比如:

interface UserApi {

    @GET("user")
    suspend fun getUser(): User

}

调用:

val user = api.getUser()

这里 Retrofit 并不是同步等待网络结果。

它只是把原来的异步流程转换成了 Kotlin 协程可以理解的挂起恢复流程。所以你不要再withContext 一下。


Retrofit 是怎么知道这是 suspend 方法?

Kotlin 的 suspend 函数在编译后,会增加一个隐藏参数:

Object getUser(
    Continuation<? super User> continuation
)

也就是说:

suspend fun getUser(): User

实际上类似:

getUser(Continuation)

Retrofit 在解析接口方法时,会检查方法参数。

如果发现最后一个参数是:

Continuation

就认为:

这是 Kotlin suspend 方法。

Retrofit 内部如何处理 suspend 方法?

核心类:

HttpServiceMethod

Retrofit 根据不同返回类型创建不同的处理方式。

对于:

suspend fun getUser(): User

会创建:

SuspendForBody

它负责把:

Call<User>

转换成:

User

SuspendForBody 内部做了什么?

核心逻辑类似:

suspend fun <T> Call<T>.await(): T {

    return suspendCancellableCoroutine { continuation ->

        enqueue(object : Callback<T> {

            override fun onResponse(
                call: Call<T>,
                response: Response<T>
            ) {

                continuation.resume(
                    response.body()!!
                )

            }


            override fun onFailure(
                call: Call<T>,
                error: Throwable
            ) {

                continuation.resumeWithException(error)

            }

        })
    }
}

它主要完成三件事:

  1. 发起异步请求
enqueue()
  1. 保存当前协程执行状态
Continuation
  1. 请求完成后恢复协程

成功:

continuation.resume()

失败:

continuation.resumeWithException()

这里通过 suspendCancellableCoroutine 把原来的callback 转成 suspend


这里的 await 是 Kotlin 协程提供的吗?

不是。

这里的:

await()

只是 Retrofit 定义的扩展函数。

它只是一个名字。

真正关键的是:

suspendCancellableCoroutine

以及:

Continuation.resume()

那 Kotlin 协程里的 await 是什么?

Kotlin 协程也有一个 await:

val result = async {
    request()
}.await()

这个属于:

Deferred.await()

作用:

等待另一个协程任务完成。

而 Retrofit 的:

Call.await()

作用:

把网络请求结果转换成协程结果。

两个不是同一个东西。


Retrofit suspend 底层是不是阻塞线程?

不是。

它不会:

线程等待网络

而是:

调用接口

↓

协程挂起

↓

线程释放

↓

OkHttp 执行网络请求

↓

结果回来

↓

Continuation.resume()

↓

协程继续执行

等待的是:

异步结果


Retrofit suspend 会切换 Dispatchers.IO 吗?

Retrofit 不负责线程调度。

网络线程:

OkHttp Dispatcher

协程恢复:

Coroutine Dispatcher

两个职责不同。

那异常为什么可以直接 try catch?

因为 Retrofit 把 Callback 的失败转换成协程异常。

以前:

onFailure()

异常在回调里。

现在:

continuation.resumeWithException(error)

协程状态机收到异常后:

等价于:

throw error

所以:

try {
    api.getUser()
} catch(e:Exception){

}

可以捕获。


总结 Retrofit suspend 原理

Retrofit 的 suspend 支持,本质是通过动态代理识别 suspend 方法,利用 Continuation 将 OkHttp 的异步结果转换成协程挂起恢复流程,让 Callback 风格的 API 变成 suspend 风格的 API。