最近回归看了一下Retrofit
的源码,主要是因为项目接入了协程,所以想研究一下Retorift
是如何支持协程的。Retrofit
是在Version 2.6.0
开始支持协程的,所以本篇文章有关Retrofit
的源码都是基于2.6.0
的。
温馨提示,如果有
Retrofit
的源码阅读经验,阅读这篇文章将会轻松很多。
Retrofit
相信老鸟都应该很清楚,Retrofit
核心部分是create()
方法返回的动态代理(这里就不详细说明了,之后会有专门的文章分析动态代理)。就是下面这段代码:
|
|
第一眼看,跟我之前印象中的有点区别(也不知道是什么版本),return
的时候居然没有adapt
方法了。开始还以为有什么重大的改变,其实也没什么,只是将之前的adapt
方法封装到invoke
方法中。
相关的method注解解析都放到ServiceMethod
中,有两个关键函数调用,分别是RequestFactory
与HttpServiceMethod
的parseAnnotations()
方法。
|
|
RequestFactory
首先RequestFactory
中的parseAnnotations()
最终通过build()
方法来构建一个RequestFactory
,用来保存解析出来的方法信息。
|
|
可以看到主要分为两步:
- 通过
parseMethodAnnotation
来解析出请求的方式,例如GET
、POST
与PUT
等等;同时也会验证一些注解的合规使用,例如Multipart
与FormUrlEncoded
只能使用一个。 - 通过
parseParameter
来解析出请求的参数信息,例如Path
、Url
与Query
等等;同时也对它们的合规使用做了验证,例如QueryMap
与FieldMap
等注解它们的key
都必须为String
类型。这些注解的解析都是在parseParameterAnnotation()
方法中进行的。
上面的p == lastParameter
需要特别注意下,为何要专门判断该参数是否为最后一个呢?请继续向下看。
协程的判断条件
下面我们来着重看下parseParameter
的源码,因为从这里开始就涉及到协程的判断。
|
|
第一点没什么好说的,里面没什么逻辑,就是一个纯注解解析与Converter
的选取。
第二点是关键点,用来判断该方法的调用是否使用到了协程。同时有个allowContinuation
参数,这个是什么呢?我们向上看,发现它是方法中的一个参数,如果我们继续追溯就会发现它就是我们之前特意需要注意的p == lastParameter
。
所以判断是否是使用了协程有三步:
result
为空,即该参数没有注解allowContinuation
为true
,即是最后一个参数Continuation.class
,说明该参数的类型为Continuation
只有符合上述三点才能证明使用了协程,但脑海里回想一下协程的写法,发现完全对不到这三点…
到这里可能有的读者已经开始蒙圈了,如果你没有深入了解协程的话,这个是正常的状态。
别急,要理解这块,还需要一点协程的原理知识,下面我来简单说一下协程的部分实现原理。
suspend原理
我们先来看下使用协程是怎么写的:
|
|
这是一个标准的协程写法,然后我们再套用上面的条件,发现完全匹配不到。
因为,这是不协程的本来面目。我们思考一个问题,为什么使用协程要添加suspend
关键字呢?这是重点。你可以多想几分钟。
(几分钟之后…)
不吊大家胃口了,我这里就直接说结论。
因为在代码编译的过程中会自动为带有suspend
的函数添加一个Continuation
类型的参数,并将其添加到最后面。所以上面的协程真正的面目是这样的:
|
|
现在我们再来看上面的条件,发现能够全部符合了。
由于篇幅有限,有关协程的原理实现就点到为止,后续我会专门写一个协程系列,希望到时能够让读者们认识到协程的真面目,大家可以期待一下。
现在我们已经知道了Retrofit
如何判断一个方法是否使用了协程。那么我们再进入另一个点:
Retrofit
如何将Call
直接转化为NewResonse
,简单的说就是支持使newsGet
方法返回NewsResponse
。而这一步的转化在HttpServiceMethod
中。
HttpServiceMethod
上面已经分析完RequestFactory
的parseAnnotations()
,现在再来看下HttpServiceMethod
中的parseAnnotations()
。
|
|
代码中已经解析的很清楚了,需要注意3,如果是协程会做两步操作,首先将接口方法的返回类型伪装成Call
类型,然后再将SkipCallbackExecutor
手动添加到annotations
中。字面意思就在后续调用callAdapter.adapt(call)
时,跳过创建Executor
,简单理解就是协程不需要Executor
来切换线程的。为什么这样?这一点先放这里,后续创建Call
的时候再说。
我们直接看协程的7,8部分。7也不详细分析,简单提一下,它就是返回一个Response<T>
的类型,这个Retrofit
最基本的支持了。至于如何在使用协程时将Call<T>
转化成Response<T>
原理与8基本相同,只是比8少一步,将它的body
转化成对应的返回类型model
。所以下面我们直接看8。
将Call转化成对应的Model
|
|
我们的关注点在adapt
,文章开头已经说了,新版的Retrofit
将adapt
隐藏到invoke
中。而invoke
中调用的就是这个adapt
。
首先第一步,适配Call
,如果是RxJava
,这里的callAdapter
就是RxJava2CallAdapter
,同时返回的就是Observable
,这个之前看过源码的都知道。
但现在是协程,那么这个时候的callAdapter
就是Retrofit
默认的DefaultCallAdapterFactory
|
|
代码注释已经将之前3的作用解释完了,我们回到SuspendForBody
的adpt
,再看第二步。熟悉的一幕,又用到了最后的一个参数。这里的isNullable
目前Retrofit
的版本都是false
,可能后续会支持空类型。但现在肯定是不支持的,所以直接进入KotlinExtensions.await()
|
|
看到这段代码,可能有的读者很熟悉,已经明白了它的转化。因为在Retrofit
之前的几个版本,如果使用协程是不支持接口方法直接返回model
的,需要返回Call<T>
类型的数据。所以当时有的开源项目就是通过这个类似的extensions
方法来转化成对应的model
。
遗憾的是,就是使用了Retrofit
的Version 2.6.0
之后的版本,我还是看到有的人使用这一套来自己转化,希望看到这篇文章的读者不要再做重复的事情,将其交给Retrofit
自身来做就可以了。
上面的extensions
作用就一个,通过suspendCancellableCoroutine
来创建一个协程,它是协程中几个重要的创建协程的方法之一,这里就不细说,后续开协程系列在详细说明。
主要是onResponse
回调,协程通过挂起来执行耗时任务,而成功与失败会分别通过resume()
与resumeWithExecption()
来唤起挂起的协程,让它返回之前的挂起点,进行执行。而resumeWithExecption()
内部也是调用了resume()
,所以协程的唤起都是通过resume()
来操作的。调用resume()
之后,我们可以在调用协程的地方返回请求的结果。那么一个完美的协程接口调用就是这样实现的。
嗯,结束了,整理一下也不是很复杂吧。之后使用Retroift
写协程时将通畅多了。
今天就这样吧,协程部分就分析到这里,Retrofit
的整个协程实现部分就分析结束了,我将关键点都特别进行了标注与说明,希望对分析Retrofit
的协程实现有所帮助。
最后,感谢你的阅读,如果你有时间的话,推荐带着这篇文章再去阅读一下源码,你将会有更深刻的印象。
项目
android_startup: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup
的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。
AwesomeGithub: 基于Github
客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin
语言进行开发,项目架构是基于Jetpack&DataBinding
的MVVM
;项目中使用了Arouter
、Retrofit
、Coroutine
、Glide
、Dagger
与Hilt
等流行开源技术。
flutter_github: 基于Flutter
的跨平台版本Github
客户端,与AwesomeGithub
相对应。
android-api-analysis: 结合详细的Demo
来全面解析Android
相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。
daily_algorithm: 每日一算法,由浅入深,欢迎加入一起共勉。
为自己代言
微信搜索公众号:【Android补给站】或者扫描下方二维码进行关注。