今天这个是送分题,但是仍然有很多人不清楚,今天就聊聊这个
比如:
1interface UserApi { 2 3 @GET("user") 4 suspend fun getUser(): User 5 6} 7
调用:
1val user = api.getUser() 2
这里 Retrofit 并不是同步等待网络结果。
它只是把原来的异步流程转换成了 Kotlin 协程可以理解的挂起恢复流程。所以你不要再withContext 一下。
Retrofit 是怎么知道这是 suspend 方法?
Kotlin 的 suspend 函数在编译后,会增加一个隐藏参数:
1Object getUser( 2 Continuation<? super User> continuation 3) 4
也就是说:
1suspend fun getUser(): User 2
实际上类似:
1getUser(Continuation) 2
Retrofit 在解析接口方法时,会检查方法参数。
如果发现最后一个参数是:
1Continuation 2
就认为:
这是 Kotlin suspend 方法。
Retrofit 内部如何处理 suspend 方法?
核心类:
1HttpServiceMethod 2
Retrofit 根据不同返回类型创建不同的处理方式。
对于:
1suspend fun getUser(): User 2
会创建:
1SuspendForBody 2
它负责把:
1Call<User> 2
转换成:
1User 2
SuspendForBody 内部做了什么?
核心逻辑类似:
1suspend fun <T> Call<T>.await(): T { 2 3 return suspendCancellableCoroutine { continuation -> 4 5 enqueue(object : Callback<T> { 6 7 override fun onResponse( 8 call: Call<T>, 9 response: Response<T> 10 ) { 11 12 continuation.resume( 13 response.body()!! 14 ) 15 16 } 17 18 19 override fun onFailure( 20 call: Call<T>, 21 error: Throwable 22 ) { 23 24 continuation.resumeWithException(error) 25 26 } 27 28 }) 29 } 30} 31
它主要完成三件事:
- 发起异步请求
1enqueue() 2
- 保存当前协程执行状态
1Continuation 2
- 请求完成后恢复协程
成功:
1continuation.resume() 2
失败:
1continuation.resumeWithException() 2
这里通过 suspendCancellableCoroutine 把原来的callback 转成 suspend
这里的 await 是 Kotlin 协程提供的吗?
不是。
这里的:
1await() 2
只是 Retrofit 定义的扩展函数。
它只是一个名字。
真正关键的是:
1suspendCancellableCoroutine 2
以及:
1Continuation.resume() 2
那 Kotlin 协程里的 await 是什么?
Kotlin 协程也有一个 await:
1val result = async { 2 request() 3}.await() 4
这个属于:
1Deferred.await() 2
作用:
等待另一个协程任务完成。
而 Retrofit 的:
1Call.await() 2
作用:
把网络请求结果转换成协程结果。
两个不是同一个东西。
Retrofit suspend 底层是不是阻塞线程?
不是。
它不会:
1线程等待网络 2
而是:
1调用接口 2 3↓ 4 5协程挂起 6 7↓ 8 9线程释放 10 11↓ 12 13OkHttp 执行网络请求 14 15↓ 16 17结果回来 18 19↓ 20 21Continuation.resume() 22 23↓ 24 25协程继续执行 26
等待的是:
异步结果
Retrofit suspend 会切换 Dispatchers.IO 吗?
Retrofit 不负责线程调度。
网络线程:
1OkHttp Dispatcher 2
协程恢复:
1Coroutine Dispatcher 2
两个职责不同。
那异常为什么可以直接 try catch?
因为 Retrofit 把 Callback 的失败转换成协程异常。
以前:
1onFailure() 2
异常在回调里。
现在:
1continuation.resumeWithException(error) 2
协程状态机收到异常后:
等价于:
1throw error 2
所以:
1try { 2 api.getUser() 3} catch(e:Exception){ 4 5} 6
可以捕获。
总结 Retrofit suspend 原理
Retrofit 的 suspend 支持,本质是通过动态代理识别 suspend 方法,利用 Continuation 将 OkHttp 的异步结果转换成协程挂起恢复流程,让 Callback 风格的 API 变成 suspend 风格的 API。