Retrofit 系列一:Retrofit 核心思想、源码初探
本文概述:
- 文章以Retrofit 框架为主题,探究了其核心思想AOP ,介绍了OkHttp 设计缺陷、Retrofit 设计思路、Retrofit 整体使用流程;从源码角度深入分析了Retrofit 执行流程,ServiceMethod 存在价值等;
知识补充:
什么是切面编程:AOP
-
AOP 的生活例子:
- 就向切好西瓜后,用勺子从切面处挖西瓜吃 ,切面为我们提供了统一的操作入口;
-
AOP 的应用场景:
-
处理内存泄漏:
- leakCanary
- BlockCannary
- Matrix
-
处理生命周期:
-
OkHttp 设计思想:
- 拦截器在实现拦截请求时,也是AOP 思想
-
Retrofit 核心思想:AOP
-
Retrofit 能干什么?
-
Retrofit 不具有网络请求功能
-
不是网络请求模块,只是通过封装OkHttp 使得用户更简单使用
-
Retrofit 的中文意思就是改造(型)
-
所有的网络请求都会给OkHttp
- 内部存在通道(Retrofit ---> OkHttp)
- 这个通道就是AOP 思想;
-
-
正文部分:
OkHttp 设计的缺陷
-
朴素版OkHttp 网络请求流程
- 实例化OkHttpClient 、Request;
- 将Request 实例作为参数实例化Call
- 调用Call 实例对象进行同/异步 请求;
-
OkHttp 存在什么问题?
-
设计初衷:仅完成网络请求(根据高内聚、低耦合、单一职责)
-
但其不算是好用:利用Retrofit 进行改型
-
用户网络请求的接口配置繁琐,尤其是配置复杂请求body 、请求头、参数;
- 存在许多样板代码 并且 这些复杂的请求参数不能简单复用
-
数据解析困难:需要用户手动拿到响应体(responsebody )进行解析且不能复用
- GSON 解析
-
线程切换困难:无法适配自动进行线程切换
- android 4.0 后就不支持在主线程中发起网络请求
-
回调陷阱:存在嵌套的网络请求
-
例如登录拿到Token,再请求网络拿到数据,再进行新的网络请求;
-
OkHttp 在上述场景的处理逻辑
//OkHttp 回调陷阱 发起第一次请求; 拿到第一次请求结果(response){ 发起第二次网络请求; 拿到第二次网络请求结果(response){ 发起第三次网络请求; 拿到第三次网络请求结果(response){ } } }
-
-
-
Retrofit 的设计思路
-
设计目的:通过封装OkHttp 使其更加好用
-
需要解决什么问题:
-
请求前:
-
统一的网络请求头配置
- Retrofit 就处理好了
-
一致适配请求Request
- 根本都不用去实例化Request、Call
-
-
结果返回:
-
数据适配
- Retrofit 完成了GSON 解析等
-
线程切换
- Retrofit 完成了从网络请求的异步线程切换回主线程;
-
-
-
Retrofit 源码概览
- 16 个类
- 8 种设计模式
Retrofit 的整体使用
-
简单使用过程:
- 导入依赖:
- 实例化Retrofit:需要配置
- 基于访问接口创建一个接口类型对象
- 基于接口类型对象实例化OkHttp Call
- 将请求Call 添加到OkHttp 请求队列
Retrofit 的使用疑问
-
为什么搞出来的那个接口可以直接拿来用?
-
接口是不能实例化对象的,需要有个接口实现类,用这个类去实例化对象
-
定义的接口:
public interface ICoinRankService{ @GET("coin/rank/{page}/json") Observable<CoinRankBean> getCoinRankList(@Path("page") int page); } -
好像直接就使用了这个接口:这个参数到底传给了谁?
ICoinRankService iCoinRankService = retrofit.create( ICoinRankService.class); Observale<CoinRankBean> coinRankList = ICoinRankService .getCoinRankList(1);
-
-
参数到底传给了谁?
-
具体的URL 到底是怎么形成的?
- 我只传了一个域名进去,URL 是怎么出来的?
-
为什么所有的请求都是同一个方式
Retrofit 源码阅读
应该怎么看源码:
- 先看框架,对大体流程、整体思路有个了解;
- 再进入具体的细节;
Retrofit.build() 是什么玩的:设计模式
-
示意图:
-
采用建造者设计模式与外观模式,对Retrofit 的类属性进行赋值;
-
什么时候使用建造者设计模式?
-
参数过多(大于5个 )且参数可选
- 例如dialog 等;
-
建造者与工厂是两回事
-
-
这里面是没有工厂模式的
- 设计模式是类之间的组织方式而非对象之间
-
为什么这里是外观(门面 )设计模式
- Retrofit 是网络请求与用户打交道的唯一途径,就像一个门面,我们通过这个门面就可以拿到里面的所有东西
- 注意:统一接口呈现内容不一定是外观模式,可能是委托、代理、装饰器等模式;
-
Retrofit.create() 干了什么:AOP 思想
-
业务逻辑:当实例化Retrofit 后,我们就可以拿这个对象去进行网络请求了
//对接口进行“实例化” ISharedListService sharedListService = retrofit.create(ISharedListService.class);-
什么样的东西才能为接口赋值?
- 接口的实现类或者接口的子类
-
-
返回了Proxy.newProxyInstance(
-
这是动态代理,是Retrofit 的精华之所在
- 程序运行到这个地方时,产生了一个类(实现了这个接口) ---> 所以为什么能对接口对象赋值
-
为什么说是代理?
-
这个类会去实现接口所指代的所有任务(接口中的函数)
- 例如接口中有两个方法 ---> 动态生成的这个类会去实现这两个方法;
-
当使用接口对象去调用接口的方法 ---> 调用到动态生成类的对象方法 ---> 将网络请求交给Handler 中的invoke 去实现而这个invoke 函数就是Retrofit.create 中的invoke 函数
-
意思就是,所有接口网络请求都会进入统一的Retrofit.create 中的invoke 中去;
- 这就是AOP !!!
- 这就是典型的切面编程;
-
-
AOP 思想有什么好处?
-
回归Retrofit 的本质:封装OkHttp
- 我们可以将所有的网络请求封装为OkHttp
-
这样封装解决了什么问题:OkHttp 设计缺陷得到完善
-
既然都会经过invoke,请求适配、数据解析、线程切换等都可以在统一的invoke处进行;
- 在切面处完成同一操作
-
但有个限制:网络请求需要在同一域名(baseUrl )下
- 域名相同 ---> 请求头相同;
- 我们只需要修改后面的参数(进入不同的二级页面 )
-
-
AOP 的实现原理:动态代理
- 程序运行到此处会自动生成(动态)既定网络接口的实现类,并实现其中所有方法(代理);
- 这个类的invoke 方法就是切面,所有的网络请求都走到这个里面了
invoke 是如何完成Retrofit ---> OkHttp:ServiceMethod
-
Retrofit 能不能封装成单例?
-
单例意味着什么:生命周期长
-
单例会一直存在,这可能导致内存泄漏
- 因为在Retrofit.build() 中会保存许多参数,这些都是会占据内存的;
- 如果要封装成单例,那么在源码中的build() 方法内就做了
-
-
业务场景:Retrofit 使用很频繁能不能将其搞成单例?
-
这个时候,我们可能会想将其封装成单例;这个时候就不用Retrofit 了,那么我们就使用Socket长连接(Netty )
- 比如说股票APP 需要一直推送消息;
-
-
ServiceMethod 的存在价值
-
网络接口上存在许多注解
-
代码展示:
public interface ICoinRankService{ @GET("coin/rank/{page}/json") Observable<CoinRankBean> getCoinRankList(@Path("page") int page); } -
这些注解是干什么的,怎么用的?
-
ServiceMethod 可以通过反射拿到这些注解,作为invoke 参数
@Override public Object invoke(Object proxy, Method method, Object... args)
-
-
-
ServiceMethod 干了什么?
- 源码示意:
ServiceMethod serviceMethod = loadServiceMethod(method);-
使用建造者设计模式配置了一系列参数,拿到了一系列注解
-
回到Retrofit.create().invoke()
//这个Method 是invoke() 的形参,这个是从网络接口的方法中来的 ServiceMethod serviceMethod = loadServiceMethod(method); -
现在的情况是:
-
已经拿到了Retrofit 实例
-
已经拿到了网络接口对象
- 网络接口在定义时,指定了参数、注解;
-
我想要的是得到一个Call ---> 将网络接口转变为请求Call
- 其实网络接口的注解就是请求Call 的参数
- 那么,ServiceMethod 要做的就是去适配Call
-
-
ServiceMethod 中有什么:ServiceMethod.Build()
-
适配器:去适配Call
callAdapter = createCallAdapter(); -
响应类型:接收Response
responseType = callAdapter.responseType(); -
响应转换器(Response 转成 JavaBean )
responseConverter = createResponseConverter();
-
读取注解:for 循环
for (Annotation annotation : methodAnnotations) { parseMethodAnnotation(annotation); } -
全部弄好了,保存在ServiceMethod 中去
-
-
每一个网络接口对应着各自的ServiceMethod
- 用于保存其请求的细节:解析所有的请求、注解、返回值
-
解析了有什么用?
- 解析好了就开始用:实例化OkHttpCall
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); -
返回
return serviceMethod.callAdapter.adapt(okHttpCall);
-
Retrofit 与 ServiceMethod
-
Retrofit 负责的是整个网络参数的保存,而ServiceMethod 负责的是具体的某一个网络接口对应的请求
- 后者去前者中找自己需要的;
- 前者就是菜市场,后者就是买菜的;
-
-
注解可不是APT 技术
- APT 是基于注解生成代码,类似于DataBinding等
-
serviceMethod.callAdapter
-
最终执行ExecutorCallAdapterFactory.adapt
- 返回ExecutorCallbackCall ,这个就是我们构建的Call
-
是什么:ServiceMethod 的类属性
final class ServiceMethod<T> { final CallAdapter<?> callAdapter; -
怎么初始化:createCallAdapter
private CallAdapter<?> createCallAdapter() { return retrofit.callAdapter(returnType, annotations);
-
进一步分析:retrofit.callAdapter
public CallAdapter<?> callAdapter(Type returnType, Annotation[] annotations) { return nextCallAdapter(null, returnType, annotations); } -
再进去:Retrofit.nextCallAdapter
CallAdapter<?> adapter = adapterFactories.get(i).get(returnType, annotations, this);- 从adapterFactories 中来的
-
Factories 在哪里赋值的:Retrofit.build()
List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories); adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor)); -
platform.defaultCallAdapterFactory 是什么东西
//此处为调度终点 CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) { if (callbackExecutor != null) { return new ExecutorCallAdapterFactory(callbackExecutor); } return DefaultCallAdapterFactory.INSTANCE; }-
所以Retrofit.build() 中的Adapter 就是ExecutorCallAdapterFactory.get() 拿到的Adapter :就是一个内部类
@Override public CallAdapter<Call<?>> get() { //就是这个 return new CallAdapter<Call<?>>() { @Override public Type responseType() { return responseType; } //最终执行这个adapt @Override public <R> Call<R> adapt(Call<R> call) { return new ExecutorCallbackCall<>(callbackExecutor, call); } }; }
-
-
-
整体来说:
-
所有网络请求都会走到invoke;
-
invoke 走到ServiceMethod
-
ServiceMethod 最终会返回一个Call
return new ExecutorCallbackCall<>(callbackExecutor, call);
-
-
ExecutorCallbackCall 与 Call 的区别
- 前者是后者的实现类,所以前者对象可以赋值给Call
怎么将Call 放入OkHttp 的请求队列:enqueue 方法
-
注意执行的是那个方法:ExecutorCallbackCall.enqueue
-
整体来说:
-
所有网络请求都会走到invoke;
-
invoke 走到ServiceMethod
-
ServiceMethod 最终会返回一个Call
return new ExecutorCallbackCall<>(callbackExecutor, call);
-
-
-
执行的逻辑:
@Override public void enqueue(final Callback<T> callback) { // delegate.enqueue(new Callback<T>() { }); }-
delegate 是什么:ExecutorCallbackCall 的类属性
static final class ExecutorCallbackCall<T> implements Call<T> { final Call<T> delegate; -
里面的泛型T 指的是什么:OkHttpCall
//Retrofit.create().invoke() ServiceMethod serviceMethod = loadServiceMethod(method); OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall);
-
也就是说此时的enqueue 操作是交给OkHttpCall 去执行的
- Retrofit 是对OkHttp 封装的有力证明
- OkHttpCall 实现了Call (Retrofit 的接口)
-
-
enqueue 的执行逻辑
-
创建真正的Call :createRawCall
okhttp3.Call call; call = rawCall = createRawCall(); -
createRawCall 的执行逻辑
//将之前保存在ServiceMethod 中解析好的参数拿出来,构建出Request Request request = serviceMethod.toRequest(args); //构建OkHttp 的Call okhttp3.Call call = serviceMethod.callFactory.newCall(request);
-
得到的Call 入队列:
okhttp3.Call call = serviceMethod.callFactory.newCall(request);
-
-
Retrofit 的调度流程总结
-
创建Retrofit
-
使用动态代理为网络接口创建代理类
-
访问代理类的ISharedListService 函数,将其转接到Retrofit.create().invoke() 中;
-
在invoke 中创建ServiceMethod (通过反射拿到网络接口的注解,将其作为参数) 同时 构造ExecutorCallbackCall ,并将其赋值给sharedListCall
- 注意ExecutorCallbackCall 并不是真正的网络请求,内部是调用createRawCall 将其封装成OkHttpCall ,然后用OkHttpCall 去完成真正的网络请求入队列操作;
-
下集预告:
- Retrofit 源码分析、动态代理原理、Retrofit 设计模式详解