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

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

比如:

kotlin 复制代码
interface UserApi {

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

}

调用:

kotlin 复制代码
val user = api.getUser()

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

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


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

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

java 复制代码
Object getUser(
    Continuation<? super User> continuation
)

也就是说:

kotlin 复制代码
suspend fun getUser(): User

实际上类似:

java 复制代码
getUser(Continuation)

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

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

java 复制代码
Continuation

就认为:

这是 Kotlin suspend 方法。

Retrofit 内部如何处理 suspend 方法?

核心类:

复制代码
HttpServiceMethod

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

对于:

kotlin 复制代码
suspend fun getUser(): User

会创建:

复制代码
SuspendForBody

它负责把:

sql 复制代码
Call<User>

转换成:

sql 复制代码
User

SuspendForBody 内部做了什么?

核心逻辑类似:

kotlin 复制代码
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. 发起异步请求
kotlin 复制代码
enqueue()
  1. 保存当前协程执行状态
kotlin 复制代码
Continuation
  1. 请求完成后恢复协程

成功:

kotlin 复制代码
continuation.resume()

失败:

kotlin 复制代码
continuation.resumeWithException()

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


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

不是。

这里的:

kotlin 复制代码
await()

只是 Retrofit 定义的扩展函数。

它只是一个名字。

真正关键的是:

kotlin 复制代码
suspendCancellableCoroutine

以及:

kotlin 复制代码
Continuation.resume()

那 Kotlin 协程里的 await 是什么?

Kotlin 协程也有一个 await:

kotlin 复制代码
val result = async {
    request()
}.await()

这个属于:

kotlin 复制代码
Deferred.await()

作用:

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

而 Retrofit 的:

kotlin 复制代码
Call.await()

作用:

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

两个不是同一个东西。


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

不是。

它不会:

text 复制代码
线程等待网络

而是:

lua 复制代码
调用接口

↓

协程挂起

↓

线程释放

↓

OkHttp 执行网络请求

↓

结果回来

↓

Continuation.resume()

↓

协程继续执行

等待的是:

异步结果


Retrofit suspend 会切换 Dispatchers.IO 吗?

Retrofit 不负责线程调度。

网络线程:

复制代码
OkHttp Dispatcher

协程恢复:

python 复制代码
Coroutine Dispatcher

两个职责不同。

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

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

以前:

kotlin 复制代码
onFailure()

异常在回调里。

现在:

kotlin 复制代码
continuation.resumeWithException(error)

协程状态机收到异常后:

等价于:

kotlin 复制代码
throw error

所以:

kotlin 复制代码
try {
    api.getUser()
} catch(e:Exception){

}

可以捕获。


总结 Retrofit suspend 原理

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

相关推荐
solo_9913 小时前
用 LSPosed 模拟录屏音频编码器卡顿:从思路到落地
android
zzq779713 小时前
别把大模型 API Key 写进 APK:移动 AI 应用接口防盗刷实践
android·人工智能·安全·app加固·御盾安全·安卓加固
xiaopang15 小时前
RK3568 AOSP 编译与快速部署实战
android
程序员正茂16 小时前
Android工程中使用ncnn进行yolo识别
android·yolo·ncnn
码农coding16 小时前
android12 SystemUI之通知
android
summerkissyou198716 小时前
android - 性能 - Perfetto - cpu 分析教程及例子
android·性能
AFinalStone18 小时前
Android 7系统休眠唤醒(七)内核层—wakelock与autosleep机制
android·休眠唤醒
starvapour18 小时前
在关机键损坏的情况下让手机关机
android·adb·手机