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。

相关推荐
FlightYe37 分钟前
音视频修炼之容器与协议(四):RTSP详解
android·linux·ffmpeg·音视频·dash
疯华整猫2 小时前
删掉 LeakCanary 那天,我用 Trae 自己撸了一套:最难的不是分析堆,是决定什么时候 dump
android·人工智能·性能优化
mmsx10 小时前
耳切法切带洞多边形:论文能跑通,真实数据为什么崩
android·kotlin
洞见前行10 小时前
你的 Activity 关了,为什么还活着?Android 内存泄漏从发现、定位到线上治理
android·性能优化·leakcanary
Kapaseker12 小时前
Kotlin 又要加语法糖了:这次轮到 if-else
android·kotlin
恋猫de小郭13 小时前
Flutter 3.50 & Dart 3.14 发布,快来看看有什么更新吧
android·前端·flutter
Starry-sky(jing)1 天前
Vidda ADB 开启:R Pro 65 找不到开发者选项的处理与连接验收
android·adb·远程控制·电视·vidda
雨白1 天前
从原理到实践:使用 Kotlin KSP 仿写 ButterKnifeLite
android·kotlin