-
熟悉代码设计流程,使用过程碰到问题可以更快速解决。说实话仅这一点无法激起我读源码的兴趣,毕竟以正确的姿态使用一个优秀的框架不应该出现这种问题。
-
一个优秀的框架必须要保证易用性、扩展性,所以作者定会引入大量的思考进行设计,如若我们能吸收一二,那何尝不是与作者进行了一次心灵交互呢!
今天我将带着我的理解,尝试从设计者的角度分析Retrofit原理,相信你认真读完再加以思考,当再被面试官问Retrofit时你的答复或许会让他眼前一亮
提示:Retrofit基于2.9.0。文中贴的源码可能会有部分缺失,这是我刻意为之,目的在于筛选掉无用信息增强可读性
==============================================================
-
1. 什么是REST ful API?
-
2. 为什么将请求设置为(接口+注解)形式?
-
2.1. 迪米特法则和门面模式
-
2.2. 为什么通过门面模式设计ApiService?
-
3. 动态代理其实不是工具
-
3.1. Retrofit构建
-
3.2. 何为动态代理?
-
3.3. 动态代理获取ApiService
-
4. ReturnT、ResponseT做一次适配的意义何在?
-
4.1 创建HttpServiceMethod
-
4.2 如何管理callAdapter、responseConverter?
-
4.3 发起请求
================================================================================
一句话概括REST ful API:在我们使用HTTP协议做数据传输时应当遵守HTTP的规矩,包括请求方法、资源类型、Uri格式等等…
不久前在群里看到某小伙伴提出一个问题:“应后端要求需要在GET请求加入Body但Retrofit 中GET请求添加Body会报错,如何解决?” 一时间讨论的好不热闹,有让把Body塞到Header里的,有让自定义拦截器、也有人直接怂恿改源码…但问题的本质不是后端先违反规则在先吗?两个人打架总不能把挨打的抓起来吧。
俗话说无规矩不成方圆,面对以上这种情况应当让错误方去修改,因为所有人都知道GET没有Body,否则一旦其他人接手你的代码很容易被搞懵。
Retrofit对REST ful API的兼容做的很优秀,不符合规范直接给你报错,强行规范你的代码。所以你们公司正在使用REST ful API而Retrofit将是你的不二选择
===================================================================================
该小节为前置知识
==========================================================================
迪米特法则:也称之为最小知道原则,即模块之间尽量减少不必要的依赖,即降低模块间的耦合性。
门面模式:基于迪米特法则拓展出来的一种设计模式,旨在将复杂的模块/系统访问入口控制的更加单一。举个例子:现要做一个获取图片功能,优先从本地缓存获取,没有缓存从网络获取随后再加入到本地缓存,假如不做任何处理,那每获取一张图片都要写一遍缓存逻辑,写的越多出错的可能就越高,其实调用者只是想获取一张图片而已,具体如何获取他不需要关心。此时可以通过门面模式将缓存功能做一个封装,只暴露出一个获取图片入口,这样调用者使用起来更加方便而且安全性更高。其实函数式编程也是门面模式的产物
======================================================================================
用Retrofit做一次请求大致流程如下:
interface ApiService {
/**
* 获取首页数据
*/
@GET(“/article/list/{page}/json”)
suspend fun getHomeList(@Path(“page”) pageNo: Int)
: ApiResponse
}
//构建Retrofit
val retrofit = Retrofit.Builder().build()
//创建ApiService实例
val apiService =retrofit.create(ApiService::class.java)
//发起请求(这里用的是suspend会自动发起请求,Java中可通过返回的call请求)
apiService.getHomeList(1)
然后通过Retrofit创建ApiService类型实例调用对应方法即可发起请求。乍一看感觉很普通,但实际上Retrofit通过这种模式(门面模式)帮我们过滤掉了很多无用信息
tips:我们都知道Retrofit只不过是对OkHttp做了封装。
如果直接使用OkHttp,当在构造Request时要做很多繁琐的工作,最要命的是Request可能在多处被构造(ViewModel、Repository…),写的越分散出错时排查的难度就越高。而Retrofit通过注解的形式将Request需要的必要信息全依附在方法上(还是个抽象方法,尽量撇除一切多余信息),作为使用者只需要调用对应方法即可实现请求。至于如何解析、构造、发起请求 Retrofit内部会做处理,调用者不想也不需要知道,
所以Retrofit通过门面模式帮调用者屏蔽了一些无用信息,只暴露出唯一入口,让调用者更专注于业务开发。像我们常用的Room、GreenDao也使用了这种模式
==========================================================================
看过很多Retrofit相关的文章,都喜欢上来就抛动态代理,关于为什么用只字不提,搞的Retrofit动态代理像是一个工具(框架)一样,殊不知它只是代理模式思想层面的一个产物而已。本小结会透过Retrofit看动态代理本质,帮你解除对它的误解
==========================================================================
Retrofit构建如下所示:
Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(ApiConstants.BASE_URL)
.build()
很典型的构建者模式,可以配置OkHttp、Gson、RxJava等等,最后通过build()做构建操作,跟一下build()代码:
#Retrofit.class
public Retrofit build() {
//1.CallAdapter工厂集合
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
//2.Converter工厂集合
List<Converter.Factory> converterFactories =
new ArrayList<>(
1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());
converterFactories.add(new BuiltInConverters());
converterFactories.addAll(this.converterFactories);
converterFactories.addAll(platform.defaultConverterFactories());
return new Retrofit(
callFactory,
baseUrl,
unmodifiableList(converterFactories),
unmodifiableList(callAdapterFactories),
callbackExecutor,
validateEagerly);
}
将一些必要信息注入到Retrofit并创建返回。注释1、2处两个集合非常重要,这里先埋个伏笔后面我们再回来看
=======================================================================
什么是代理模式?
代理模式概念非常简单,比如A想做一件事可以让B帮他做,这样做的好处是什么?下面通过一个例子简要说明。需求:每一次本地数据库CRUD都要做一次上报
最简单粗暴的方式就是每次CRUD时都单独做一次记录,代码如下
//业务层方法test1
fun test1{
//数据库插入操作
dao.insert()
//上报
post()
}
//业务层方法test2
fun test2(){
//数据库更新操作
dao.update()
//上报
post()
}
以上这种方式存在一个问题:
上报操作本身与具体业务无关,一旦需要对上报进行修改,那就可能影响到业务,进而可能造成不可预期的问题产生
面对以上问题可以通过代理模式完美规避,改造后的代码如下:
class DaoProxy(){
//数据库插入操作
fun insert(){
dao.insert()
//上报
post()
}
//数据库更新操作
fun update(){
dao.update()
//上报
post()
}
}
//业务层方法test1
fun test1{
//数据库插入操作
daoProxy.insert()
}
//业务层方法test2
fun test2(){
//数据库更新操作
daoProxy.update()
}
新增一个代理类DaoProxy,将dao以及上报操作在代理类中执行,业务层直接操作代理对象,这样就将上报从业务层抽离出来,从而避免业务层改动带来的问题。实际使用代理模式时应遵守基于接口而非实现编程思想,但文章侧重于传授思想,规范上可能欠缺
此时还有一个问题,每次CRUD都会手动做一次上报操作,这显然是模版代码,如何解决?下面来看动态代理:
什么是动态代理?
java中的动态代理就是在运行时通过反射为目标对象做一些附加操作,代码如下:
class DaoProxy() {
//创建代理类
fun createProxy(): Any {
//创建dao
val proxyAny = Dao()
val interfaces = proxyAny.javaClass.interfaces
val handler = ProxyHandler(proxyAny)
return Proxy.newProxyInstance(proxyAny::class.java.classLoader, interfaces, handler)
}
//代理委托类
class ProxyHandler(private val proxyObject:Any): InvocationHandler {
//代理方法,p1为目标类方法、p2为目标类参数。调用proxyObject任一方法时都会执行invoke
override fun invoke(p0: Any, p1: Method, p2: Array): Any {
//执行Dao各个方法(CRUD)
val result = p1.invoke(proxyObject,p2)
//上报
post()
return result
}
}
}
//此处规范上应该使用基于接口而非实现编程。如果要替换Dao通过接口编程可提高扩展性
val dao:Dao = DaoProxy().createProxy() as Dao
dao.insert()
dao.update()
其中Proxy是JDK中用于创建动态代理的类,InvocationHandler是一个委托类, 内部的invoke(代理方法)方法会随着目标类(Dao)任一方法的调用而调用,所以在其内部实现上报操作即可消除大量模版代码。
动态代理与静态代理核心思想一致,区别是动态代理可以在运行时通过反射动态创建一个切面(InvocationHandler#invoke),用来消除模板代码。喜欢思考的同学其实已经发现,代理模式符合面向切面编程(AOP)思想,而代理类就是切面
================================================================================
2.2小节有提到可以通过retrofit.create()创建ApiService,跟一下retrofit的create()
#Retrofit.class
public T create(final Class service) {
//第一处
validateServiceInterface(service);
return (T) Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] {service},
new InvocationHandler() {
//第二处
@Override
public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
…
return platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args);
}
});
}
create()大致可以分为两部分:
-
第一部分为validateServiceInterface()内容,用来验证ApiService合法性,比较简单就不多描述,感兴趣的同学可自行查看。
-
第二部分就是invoke(),通过3.2小节可知这是一个代理方法,可通过调用ApiService中的任一方法执行,其中参数method和args代表ApiService对应的方法和参数。返回值中有一个isDefaultMethod,这里如果是Java8的默认方法直接执行,毕竟我们只需要代理ApiService中方法即可。经过反复筛选最后重任落在了loadServiceMethod,这也是Retrofit中最核心的一个方法,下面我们来跟一下
#Retrofit.class
ServiceMethod<?> loadServiceMethod(Method method) {
ServiceMethod<?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = ServiceMethod.parseAnnotations(this, method);
serviceMethodCache.put(method, result);
}
}
return result;
}
大致就是对ServiceMethod做一个很常见的缓存操作,这样做的目的是为了提升运行效率,毕竟创建一个ServiceMethod会用到大量反射。创建ServiceMethod对象是通过其静态方法parseAnnotations实现的,再跟一下这个方法:
#ServiceMethod.class
static ServiceMethod parseAnnotations(Retrofit retrofit, Method method) {
//第一步
RequestFactory requestFactory =
RequestFactory.parseAnnotations(retrofit, method);
Type returnType = method.getGenericReturnType();
…
//第二步
return HttpServiceMethod.parseAnnotations(retrofit,
method, requestFactory);
}
第一步:
通过RequestFactory的parseAnnotations()解析method(ApiService的method)中的注解信息,具体代码很简单就不再贴了。不过需要注意这一步只是解析注解并保存在RequestFactory工厂中,会在请求时再通过RequestFactory将请求信息做拼装。
第二步:
调用HttpServiceMethod的parseAnnotations创建ServiceMethod,这个方法很长并且信息量很大,下一小节我再详细描述,此处你只需知道它做了什么即可。其实到这方法调用链已经很绕了,我先帮大家捋一下 HttpServiceMethod其实是ServiceMethod的子类,Retrofit动态代理里面的loadServiceMethod就是HttpServiceMethod类型对象,最后来看一下它的invoke()方法。
#HttpServiceMethod.class
@Override
final @Nullable ReturnT invoke(Object[] args) {
Call call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}
创建了一个OkHttpCall实例,它内部其实就是对OkHttp的一系列操作,这里先按住不表后面我会再提到。把关注点切到返回值,返回的Call对象没做任何操作,而是传入到adapter()方法一并返回来,字面意思应该是一个适配操作,那究竟如何适配?这里再埋一个伏笔与3.1结尾相呼应,下一小节我们再一一揭开。
动态代理讲完了,那么它解决了什么问题?
假如不使用代理模式,那关于ApiService中方法注解解析的操作势必会浸入到业务当中,一旦对其修改就有可能影响到业务,其实也就是也违背了我们前面所说的门面模式和迪米特法则,通过代理模式做一个切面操作(AOP)可以完美规避了这一问题。可见这里的门面模式和代理模式是相辅相成的
Retrofit事先都不知道ApiService方法数量,就算知道也避免不了逐一解析而产生大量的模版代码,此时可通过引入动态代理在运行时动态解析 从而解决这一问题。
4. ReturnT、ResponseT做一次适配的意义何在?
============================================================================================
ResponseT、ReturnT是 Retrofit 对响应数据类型和返回值类型的简称
===================================================================================
上一小节我们跟到了adapter(),这是一个抽象方法,其实现类是通过HttpServiceMethod的parseAnnotations创建的,继续跟下去:
#HttpServiceMethod.class
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
boolean continuationWantsResponse = false;
boolean continuationBodyNullable = false;
Annotation[] annotations = method.getAnnotations();
Type adapterType;
//1.获取adapterType,默认为method返回值类型
if (isKotlinSuspendFunction) {
Type[] parameterTypes = method.getGenericParameterTypes();
Type responseType =
Utils.getParameterLowerBound(
0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
// Unwrap the actual body type from Response.
responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
continuationWantsResponse = true;
} else {
}
adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
adapterType = method.getGenericReturnType();
}
//2.创建CallAdapter
CallAdapter<ResponseT, ReturnT> callAdapter =
createCallAdapter(retrofit, method, adapterType, annotations);
Type responseType = callAdapter.responseType();
//3.创建responseConverter
Converter<ResponseBody, ResponseT> responseConverter =
createResponseConverter(retrofit, method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
//4.创建HttpServiceMethod类型具体实例
if (!isKotlinSuspendFunction) {
return new HttpServiceMethod.CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
}
//兼容kotlin suspend方法
else if (continuationWantsResponse) {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (HttpServiceMethod<ResponseT, ReturnT>)
new HttpServiceMethod.SuspendForResponse<>(