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。

相关推荐
恋猫de小郭3 小时前
Shopify 回应为什么从 RN 回到原生,为什么不用 KMP ?
android·前端·flutter
fundroid5 小时前
Android AI 开发,真正拉开差距的是工程闭环
android·ai·大模型·agent
脚踏实地,坚持不懈!5 小时前
Android 面试题全解(结合 AOSP 源码 · 截至 Android 17 / API 37)
android
换元不配限6 小时前
Android 架构演进实战:MVC → MVP → MVVM → MVI
android·架构·mvc·mvvm·mvp·mvi
mmsx8 小时前
Android Dialog 插槽模式:用一套壳统一全应用的弹窗风格
android·java·dialog·开发语言
码上有光8 小时前
Linux:进程间通信——匿名管道通信、命名管道通信
android·java·linux·linux通信·匿名通信·命名通信
爱笑鱼10 小时前
Android 系统启动机制(九):system_server 已经运行,为什么还不能说 Android 启动完成?
android
mmsx10 小时前
Android 上的 AI 对话链路:流式响应、SSE 解析与三级降级
android·kotlin
阿俊-全栈开发10 小时前
LikeShop单商户高级版定时任务实现:订单超时关闭与自动确认收货
android·likeshop·likeshop开源商城
倔强的石头10611 小时前
【Transformer】Tokenization详解_BPE_WordPiece_SentencePiece
android·深度学习·transformer