阅读源码基本套路:WHW
What:能做哪些事?提供了什么功能?
How:采用什么方式实现的?由哪些模块组成?
Why:为什么有这样的需求?模块这样封装的意图是什么?还有没有更好的方式?
前言
在业务常用框架中,Retrofit 算是代码量较少,难度较低的开源项目。虽然代码不多,但依然用到了大量的设计模式,具有非常好的扩展性。
解析 Retrofit 源码的优秀文章不少,本文不再赘述,抓住易曲解的概念、整体架构和难理解的细节来做分析。
明确概念
一个简单的网络请求调用流程如下:
我们从上面的应用场景可以看出,Retrofit的作用是按照接口去定制Call网络工作对象,也就是说:Retrofit并不直接做网络请求,只是生成一个能做网络请求的对象。
Retrofit在网络请求中的作用大概可以这样理解:
我们看到,从一开始,Retrofit要提供的就是个Call工作对象。
换句话说,对于给Retrofit提供的那个接口
这个接口并不是传统意义上的网络请求接口,这个接口不是用来获取数据的接口,而是用来生产对象的接口,这个接口相当于一个工厂,接口中每个函数的返回值不是网络数据,而是一个能进行网络请求的工作对象,我们要先调用函数获得工作对象,再用这个工作对象去请求网络数据。
所以Retrofit的实用价值意义在于,他能根据你的接口定义,灵活地生成对应的网络工作对象,然后你再择机去调用这个对象访问网络。
代码结构
说白了,Retrofit就做了三件事:
- 根据注解解析 ApiService 中每个方法的参数,url,请求类型等信息,组装为 ServiceMethod 类。
- 根据 ServiceMethod 信息 new 请求对象OkHttpCall(内部持有真正的请求对象 okhttp3.Call 的引用,相当于包了一层)。
- 用适合的适配器 callbackExecutor 转换网络请求对象 Call 为我们声明的接口返回类型(如 Call
到 Observable ),用适合的转换器 Converter 转换默认的Response为声明的接口返回值(如 Call 到 Call ),返回请求对象。
以上加粗的关键类,我们可以详细看下类结构是怎么样的。
可以看到,Retrofit 自身的结构很简单,代码量也不是很大。红色框部分是http包,代表的是所有的Annotation层。 通过这些注解类,把方法里声明的注解一一映射到构造 OkHttp 请求对象所需要的Request参数。
几个主要类的UML简图:
1. Retrofit 和 ServiceMethod
Retrofit 和 ServiceMethod 使用了 Builder模式(省略了 Director 和Abstract Product 的 Builder模式)来构建自己,Retrofit 的作用很简单,传入需要的参数,构建一个 Retrofit 对象,然后通过动态代理的方式,得到我们自定义的方法接口的实例,参数中除了baseUrl 之外,其他都是可选的,如果没设置会使用默认值。
ServiceMethod 作用就是解析Annotation,同时提供方法生成网络请求需要的Request和解析请求结果Response。
2. CallAdapter 和 CallAdapter.Factory
CallAdapt 的作用是把 Call 转变成你想要返回的对象,起作用的是 adapt 方法,CallAdapter.Factory 的作用是获取 CallAdapter 。ExecutorCallAdapterFactory 的 CallAdapter会将回调方法放到主线程中执行,能够接受的返回值类型为Call
3. Converter和Converter.Factory
Converter 的作用是将网络请求结果 ResponseBody 转换为我们希望的返回值类型。Converter.Factory 的作用是获取 Converter,这里很明显采用了静态工厂模式。
4. OkHttpCall
OkHttpCall 继承自 interface Call,主要的作用是调起执行网络请求以及返回当前请求状态状态,但是真正的网络请求其实在okhttp3.Call接口,接口定义如下:
这个接口的实现类是 okhttp3.RealCall,可以发现,Retrofit 的Call 接口和 okhttp3 的 Call 接口定义几乎是完全一样的,这样做的好处显而易见:利于扩展,解耦。
5. RxJavaCallAdapterFactory
CallAdapterFactory的作用及工作机理前面已经介绍过了,RxJavaCallAdapterFactory的作用也是一样的,只不过RxJavaCallAdapterFactory中内部又定义了三种CallAdapter:ResponseCallAdapter、ResultCallAdapter和SimpleCallAdapter,根据返回值类型决定到底使用哪个,代码如下:
细节点
以上流程中,有很多细节可以详细梳理下。
1. 如何将 ApiService 接口转换为网络请求?
|
|
看到以上代码,我们不禁提出疑问,这里的mApiService是什么类型?为什么可以直接调用接口方法?create做了什么?生成接口实现类吗?
我们 debug 到 retrofit.create() 中一看究竟:
返回值就是我们自定义的接口实例对象T。由于T的所有方法都是抽象方法,当调用T的方法时,会被 InvocationHandler 拦截,真正的调用会转到 InvocationHandler 的 invoke() 方法中,其中 method 参数就是我们自己的抽象方法。
invoke() 方法中,基于我们的接口方法构造了一个 ServiceMethod,构建过程中对方法中的各种注解做了解析。创建了一个 OkHttpCall 对象,这个对象将会在被adapt之后返回给客户端,类型取决于客户端的方法返回类型和设置的 CallAdapter。这里的代码其实不是很好,OkHttpCall 和 ServiceMethod 有互相引用的感觉,其实本意只是将 OkHttpCall 转换成客户端需要的返回值,那么 CallAdapter 对象是否有必要放在 ServiceMethod,我觉得可以再仔细斟酌一下。
注:此处有个预加载开关 validateEagerly ,开启后将会在调用create时就先去解析ApiService中每个方法,并且add serviceMethodCache缓存里。等到调用方法时,无需再解析,就可以直接在缓存里取解析对象了。
2. 谁去进行网络请求?我们的回调是怎么回到主线程的呢?
拿到返回的Call对象,我们可以执行网络请求了。
调用call.enqueue,内部实际会调用ExecutorCallAdapter的enqueue方法。
这里的 delegate 对应的就是 okhttp 的 call ,我们注意到 response 的回调由callbackExecutor.execute() 来执行。一步步追踪 callbackExecutor 来源,Retrofit 的 build() 方法里:
平台默认的回调调度器
我们发现,Android 默认的调度器是主线程的 Handler ,execute()方法也只是 mainHandler.post() 。所以 enqueue() 中 onResponse 方法调用 defaultCallbackExecutor.execute 方法,实际上就是使用主线程 Handler.post(runnable) 从而实现线程切换操作。
3. 添加多个转换器和适配器时,内部优先逻辑是什么?
|
|
addConverterFactory 扩展的是对返回的数据类型的自动转换,addCallAdapterFactory 扩展的是对网络工作对象 callWorker 的自动转换。就算我们不添加 CallAdapterFactory 也是能实现适配转换的,原因在于 Retrofit 的 build 函数里,会添加默认的 CallAdapterFactory。
addConverterFactory 和 addCallAdapterFactory 都是把工厂对象添加到各自数组里保存
当需要转换或适配时,就循环数组,调用每个工厂对象的方法去尝试转换,转换成功,就表明合适。其实这也是责任链的另一种表现形式。
思考
总体来说,Retrofit 在类的单一职责方面分隔的很好,OkHttpCall 类只负责网络交互,凡是和函数定义相关的,都交给ServiceMethod 类去处理,而 ServiceMethod 类对使用者不公开,因为 Retrofit 是个 门面模式也就是外观模式,所有需要扩展的都在Retrofit的建造者中实现,用户只需要简单调用门面里的方法,就能满足需求,不需要关心内部的实现。
我们尝试来分析下,在一个网络请求中,哪些是易变的,哪些是不变的,为什么 Retrofit 会这么去设计?
由于 Retrofit 提供网络访问的工作对象,又是服务于具体业务,所以可以分网络访问和具体业务两部分来分析。
网络访问的不变性
对于网络访问来说,不变的是一定有一个实现网络访问的对象,Retrofit 选用了自家的 OkHttpClient,为了把 Retrofit 和OkHttp 解耦合,Retrofit根据依赖倒置原则定义了自己的接口 Call 即 retrofit2.Call,并定义了操作网络请求的具体类 OkHttpCall,和okHttp3.Call仅为引用关系。网络访问的易变性
对于网络访问来说,易变的是网络访问的url、请求方式(get/post等)、Http请求的Header设置与安全设置等,以及返回的数据类型。针对易变的url和请求方式,Retrofit使用了方法注解的方式,可读性良好,但这需要实现对接口函数中注解的解析,这样就有了ServiceMethod。
针对Http请求的各种设置,其实Retrofit没做什么,因为Retrofit使用的OkHttp有拦截器机制,可以应付这种变化。
针对返回的数据类型,由于目标数据类型与业务有关,是不确定的,Retrofit无法提供一个万能的转换类,所以Retrofit提供了扩展接口,允许开发者自己定义 ConverterFactory 和 Converter,去实现潜在的数据类型转换。
具体业务的不变性
对于具体业务来说,不变的是一定要有一个Call网络工作对象,所以Retrofit可以有一个生产对象的机制(像工厂一样)具体业务的易变性
对于具体业务来说,易变的就是这个Call网络工作对象的类型,不仅有CallBacl回调、可能还有Flowable工作流、或者其他潜在的对象类型。针对这种Call对象的易变性,Retrofit也是无法提供一个万能的实现类,所以也是提供了扩展解耦,允许开发者自己定义CallAdapterFactory和CallAdapter,去实现潜在的Call类型转换。
因为这种Call对象的生产需要有大量的配套代码,为了简化代码,Retrofit使用动态代理来生产这个对象。
最后,因为需要处理的方法和对象太多太复杂,需要使用建造者模式来把建造过程和使用过程分离开。
可以说 Retrofit 设计得非常精妙。最后贴张架构图,跑路!
参考:
Retrofit 分析-漂亮的解耦套路
框架源码 — 可能会有趣一点地简析学习 Retrofit
Android:手把手带你 深入读懂 Retrofit 2.0 源码
拆轮子系列 - 如何由浅入深探索 Retrofit 源码?