Retrofit 适配器与转换器
v1.0 1st Edition
本文主要说明Retrofit中关于retrofit2.Converter
和retrofit2.CallAdapter
的相关内容,不可避免的会牵扯到一部分okhttp3
和RxJava
的内容。
在使用Retrofit
的时候首先会对其使用动态代理的方式做http
请求的方式非常喜欢,这里个人觉得一是因为用接口做模板非常简洁清晰,其次也正是因为这种原因隐藏了http请求的繁琐步骤,同时也减少了不必要的出错。
其次对于默认的数据转换感觉非常神奇,仅仅是声明一个泛型就可以把http返回转换成对象。并且其拥有非常好的扩展性,特别是对RxJava的支持在第一次用的时候令人印象深刻。
那么接下来开始读代码,如果不关心okhttp的实现那么看Retrofit
的源代码,会觉得非常的简洁并且文档注释写的非常详细。
TL;DR
一、总体流程
1 | Retrofit.Builder builder = new Retrofit.Builder(); |
Retrofit.java
为入口类同时也是核心类,一般使用的时候这个类的对象是由Retrofit.Builder
创建的,并且调用Retrofit#create(CLass<t>):T</t>
来创建代理对象。
GitHub上rxjava adapter的源代码为1.2,而Gradle依赖库中最新的为2.1.0。这里源码使用2.1.0避免示例代码的正确说明。
上图为一个非常简单的调用流程,简单来说使用Retrofit#create(class)
创建一个代理对象。实际的http请求在调用此代理对象的方法时开始,而这里由calladapter去处理,在这里可以选择执行的方式。
每个代理对象的方法执行的操作与一个指定的ServiceMethod
对象一一对应,并且不能动态改变,因为在第一次创建之后其就被缓存下来。在ServiceMethod
的初始化中确定三个关键成员CallAdapter
、Converter
、ParameterHandler
,他们分别是调用适配器、数据转换器、参数转换器,这三个对象如何工作在下文进行详细说明。
之后由ServiceMethod
中获得的CallAdapter#adapt(OkHttpCall)
返回对应的代理方法的返回值Type
。
由于当前版本HttpClient
使用OKHttpClient
,所以核心操作其实上都在retrofit2.OkHttpCall
这个类之中,同时其由于持有ServiceMethod
实例。因此可以间接的将返回数据经由ServiceMethod
传递给Converter
进行处理。
接下来针对每个部分做详细说明。
二、Retrofit
虽然这是入口并且提供了很多功能,但是逻辑上并没有过多的说明,更多的是体现在编码上。
1 | public <T> T create(final Class<T> service) { |
java.lang.reflect.Proxy#newProxyInstance
接口代理,简单可以理解为在内存中动态的生成一个此接口的子类并实例化它。对于做Java Web的朋友来说这个肯定非常熟悉了,更强的还有ASM
这种工具。之后是java.lang.reflect.InvocationHandler
在代理对象的方法被调用时InvocationHandler#invoke(...):Object
将被回调。正因为如此写一个接口作为清单如此简洁的http请求调用才得以实现。
接下来值得关注的是ServiceMethod
,这里可以看到起调用自身的CallAdapter#adapt(OkHttpCall)
直接返回了最外层声明的返回值。
1 | //Retrofit#loadServiceMethod |
这里是创建并缓存ServiceMethod
的代码,可以看出这个对象和调用的方法是一一对应的,并且创建之后会被缓存下来。
1 | //Retrifit.Builder()#build() |
对于Android
平台来说默认的执行器Executor
为Handler(Looper.getMainLooper())
,使用Handler#post(Runnable)
执行线程。这里添加了一个默认的回调适配器DefaultCallAdapterFactory
,其中的关键点是返回类型为Call
的方法,而其返回的Adapter
中更是直接返回Call
对象。而这个Call
对象就是在Retrofit#create(class)
方法中初始化的OkHttpCall
。这就是一些默认值,而其他内容则是在代理对象的方法调用时动态确定的。
另外值得注意的是以下几个方法
1 | public CallAdapter<?> nextCallAdapter(CallAdapter.Factory skip, Type type, |
获取CallAdapter
的最终方法,其在ServiceMethod
被创建时调用并把返回值储存起来。这里值得注意的是其通过泛型返回值Type
和方法注解Annotation[]
在所有缓存的Factory
中做测试,并将第一个符合条件的返回值返回并结束,同时我们也可以传入一个Factory
做排他选项。因此这里为多个回调适配器的使用成为可能,即当http调用情况比较复杂时可以提供多种候选项。同样的Retrofit#nextRequestBodyConverter
和Retrofit#stringConverter
两个方法非常相似,在此就不过多说明了。
三、ServiceMethod
ServiceMethod
就如其名字所说,它主要处理和代理对象方法的相关操作。其必须的两个参数为Retrofit
和Method
,这可以看出其主要与Retrofit
对象做交互。
1 | //ServiceMethod.Builder#build() |
这里的#createCallAdapter():CallAdapter
和#createResponseConverter():Converter
都是直接调用Retrofit
对象中的方法。之后的parseMethodAnnotation
主要是处理方法注解中的@GET
、@POST
等关键字。之后的parseParameter
方法是将对应的参数类型转换器和ParameterHandler
对象进行关联,用于处理参数的类型转换。并且默认的转换器为BuiltInConverters.ToStringConverter.INSTANCE
,其默认直接将参数转换为字符串。
另外值得注意的是方法#toResponse(ResponseBody):T
是直接调用内部的responseConverter
将ResponseBody
转换为方法返回的泛型。#toRequest(Object...):Request
方法则是将参数转换为okhttp3的最终请求对象。
四、OkHttpCall
OKHttpCall <t>implements Call</t>
其实是对okhttp3.Call
的包装,也许是对将来支持更多的底层请求做准备吧(我记得之前的版本确实是支持很多底层实现的)。接下来我们关注一下几个问题:如何发送请求,如何获得返回值,如何解析返回值,返回值是如何转换到方法返回的泛型中。
就如同okhttp3
的Call
一样,OkHttpCall
提供了同样的几个接口。execute():Response
同步请求接口,enqueue(Callback<t>)</t>
异步回调接口。
1 | public Response<T> execute(){ |
默认的CallAdapter
由于返回retrofit.Call
,所以此时我们是直接调用到这里。而通过一些使用我想有些朋友已经感觉到代理对象调用时的返回值是由CallAdapter
提供的,因此有些实现可以完全隐藏Call
,比如直接返回泛型对象或者返回RxJava中的Observable
。
由于http请求底层是由okhttp3
提供,它返回是okhttp3.Response
,而经过封装之后的retrofit.Call
中返回的却是retrofit.Response
。其中不仅包括原始的http请求信息,还包括一个我们经过转换之后的泛型T
。这里请参加以下代码。
1 | //retrofit.OkHttpCall |
网络请求的返回是okhttp3.Response
,通过以上方法将其转换为retrofit.Response
对象。在默认的情况下,要么直接返回返回Response
,要么通过retrofit.Call
回调返回Response
。这里值得注意的是T body = serviceMethod.toResponse(catchingBody)
,在上文ServiceMethod
中已经很清楚,这里是直接调用缓存的Converter
将返回值转换为泛型T
并且保存到返回的Response
中。所以这里可以很明确的看出我们的转换器只能获得实际对象为ExceptionCatchingRequestBody
的返回数据,并将其转换为我们需要的泛型而最终返回的实际上是Response
。但是整个外部流程是由CallAdapter
控制的,而这个Response
最终也是要经过CallAdapter
返回。所以实际上我们要做的可以远比从API上看到的多。
从API上来看默认返回retrofit.Call
,我们拿到的无论是异步还是同步最终返回的是Response
,那么我们是否可以通过重写CallAdapter
直接返回T
呢?答案自然是肯定的,我在上一篇《Android Retrofit》
中简单的说明了一下,即可以通过CallAdapter<t>#adapt(Call<r>):T</r></t>
方法直接返回T
,这里也是一个R
转T
的过程,因此我想rxjava的话会更加自然。
五、数据转换器
1 | public interface Converter<F, T> { |
以上为转换器接口基本代码,为了篇幅和排版删除了一些类结构。首先对于Converter<f, t="">#convert(F):T</f,>
来说,由于结构限制实际我们能做的也仅仅是将泛型F
转换为T
。但是正因为是两个泛型,实际上它是一个通用接口其作用远远的超过之前的Converter
这种转换返回值到我们自定义类型的方式。
框架内部实际上为我们规定的使用方法参见它内部的工厂类Converter.Factory
,其提供三个方法。分别是ResponseBody
转换为任意类型?
,任意类型?
转换为RequestBody
,任意类型?
转换为String
。那么在框架内这三个方法的实际调用地方在哪里呢?
- responseBodyConverter:
ServiceMethod#toResponse(ResponseBody):T
,是在OkHttpCall
中获得okhttp3.Response
之后将其转换为我们定义的泛型T
,并重新封装到retrofit.Response
中过程被调用的。 - requestBodyConverter:
ServiceMethod#toRequest(Object ...):okhttp3.Request
,是在OkHttpCall
中发起http请求前根据我们的代理对象生成请求对象时调用。另外Retrofit
封装了httpResponse
对象,但是没有封装httpRequest
对象。 - stringConverter:实际上是在
retrofit.ParameterHandler
中被多个实例调用。从内部实现来说有Header
、Path
、Query
、QueryMap
、HeaderMap
、Field
、FieldMap
、PartMap
。这里可以看出这些转换器都是将参数转换为String
,而向另外的一些实现,Part
、Body
是使用的Converter
转换器。而这些转换器都是在ServiceMethod#parseMethodAnnotation(Annotation)
实例化的,毕竟可能性和情况比较多,这里感觉非常繁杂。
六、调用适配器
1 | public interface CallAdapter<T> { |
看结构和Converter
非常相似,实例也是由工厂对象生成。首先看工厂类方法Factory#get(...):CallAdapter
生成一个实例,同样也是根据返回对象的类型和方法方法注解判断是否生成实例,如果返回null则查找下一个注册的Factory
。
另外这里提供了两个工具方法Factory#getParameterUpperBound(...):Type
,作用是返回参数中的泛型上界。泛型上界简单说就是中后面的V
。方法Factory#getRawType(...):Class
则是返回原始的类型,比如List
则返回List.class
。
官方的默认实现为retrofit.DefaultCallAdapterFactory
,其中CallAdapter#adapt(Call<r>):Call</r>
方法更是直接返回了Call
对象。
1 | public <R> R adapt(Call<R> call) { |
如上述代码也可以直接返回泛型对象,这里的方法是使用Call
进行同步Http请求。其返回的对象为经过我们Converter
处理过的retrofit.Response
对象,然后使用retrofit.Response#body():R
方法返回我们真正想要的对象。然后就是请尽量不要这样写。
RxJava CallAdapter
1 | Retrofit.Builder builder = new Retrofit.Builder(); |
以上为RxJava调用的基本方式,其返回的基本数据形式为Observable
。注意这里和Call
是一个意思并没有开始HTTP请求,按照RxJava的规则开始订阅才执行。
之前已经提到无论外面是如何实现,其内部依旧是OkHttpCall
执行请求并最终返回retrofit.Response
。那么在添加一个RxJavaCallAdapterFactory
之后返回值为什么会变成Observable
呢?这也是我第一次用RxJava的回调适配器时比较感兴趣的。
首先看适配器的关键方法CallAdapter<t>#adapt(Call<r>):T</r></t>
,其实质是一个从R
转换到T
的过程,当然了这里T
可以和R
相同。那么来看看官方是如何实现的。
1 | public final class RxJavaCallAdapterFactory extends CallAdapter.Factory{ |
首先从代码来看其支持Observable
、Single
和Completable
三种返回类型,而且泛型还可以做些文章(这里稍后说明)。那么直接用代码说明,这里的CallAdapter
有五个实现。
1 | static class CompletableCallAdapter implements CallAdapter<Completable>{ |
首先CompletableCallAdapter
的内部仅仅是将Observable
替换为Completable
,而SingleAdapter
更是直接使用Observable
的回调包装一下将结果转换为Single
而已,因此这两个就不多说了。
1 | private CallAdapter<Observable<?>> getCallAdapter(Type t, Scheduler s) { |
这是当返回值为Observable
时的三种CallAdapter
实现。可以看出,这里设计上是支持两种嵌套泛型和一种通常泛型的。分别是Observable<response<t>></response<t>
、Observable<result<t>></result<t>
和Observable
,当然这里的泛型T
还可以再次嵌套泛型,这是由Converter
决定的。
retrofit2.adapter.rxjava.Result
实际上是对retrofit.Response
的再次封装,在ResultCallAdapter
中直接使用rxjava的map
运算符将Response
转换为Result
(这里map理解为一对一映射就好了)。而ResultCallAdapter
中使用了rxjava的lift操作,执行操作为OperatorMapResponseToBodyOrError <t>implements Operator<t, response<t="">></t,></t>
,简单理解为将Response
解包为T
的操作。但是会不会觉得如此明确的操作和转换,各种map和lift简直是多此一举。就目前的实现来说rxjava在控制http请求的时候都是调用的OkHttpCall
做同步请求,再使用rxjava自己的线程控制器进行处理。而RxJava更多时候是保证流程和逻辑上的清晰与合理,面对比较单纯的情况确实会感觉反而更复杂了。
到了这里,可以解决上一篇文章提出的疑问使用Observable
形式如何获得Http返回信息?这里的回答是将泛型再次包装一层如Observable<response<t>></response<t>
和Observable<result<t>></result<t>
,这里就可以在RxJava的观察者回调中获得Response
和Result
进而获得http请求的返回信息。
关于RxJava
关于RxJava使用上其实非常简单,但是逻辑上要说清楚是比较困难。当面对比较复杂的逻辑时也是考验我们对RxJava的理解的时候。随后的文章将从逻辑上和实现上对RxJava进行详细说明。