Android 常用开源框架源码解析 系列 (三) Retrofit 设计模式+知识点
Retrofit
对网络请求接口的封装,定义网络请求方法的接口,及添加方法的注解和参数。内部通过动态代理拦截需要处理的接口,并把注解和方法参数解析成需要的http api请求,给OkHttp库进行实际的网络请求。
A、Retrofit的设计模式
一、构建者模式
1、将复杂对象的构建与表示相分离
不关心成员对象的创建,直接调用Builder()内部类通过链式调用内部不同方法,满足成员的初始化操作。
Retrofit的构建者模式:
创建Retrofit 对象使用到了build构建者模式,利用构建者模式的Build()内部类进行Retrofit成员变量的初始化,最后通过build()完成创建。
优点:
简便易操作,若不适用构建者模式就需要创建Retrofit构造方法来初始化操作,不同的场景需要构造不同的Retrofit对象。
2、针对不同的网络场景需要配置不同的环境参数,这时候就需要用到了addCallAdapterFactory 网络适配器,解析的效果是不同的。
ps:ServiceMethod 等对象
二、工厂模式
addCallAdapterFactory(xx.create) :添加返回数据所要支持的格式。
Factory abstract内部类,三大函数方法get() 、getRawType()、getParameterUpperBound()
abstract class Factory {
protected static Type getParameterUpperBound(int index, ParameterizedType type) {
return Utils.getParameterUpperBound(index, type);
}
protected static Class<?> getRawType(Type type) {
return Utils.getRawType(type);
}
//让不同类型的(可自定义)工厂继承CallAdapter,通过实现不同的get()方法,返回不同类型的Calladatper使用
public abstract @Nullable CallAdapter<?, ?> get(Type returnType, Annotation[] annotations,
Retrofit retrofit);
在get()方法中有三个实现类,RxJavaCallAdapter、DefaultCallAdapter、ExecutorCallAdapter
A、RxJavaCallAdapter:
CallAdapter<Observable<?>> callAdapter = getCallAdapter(returnType, scheduler);
在RxJavaCallAdapter里面返回的类型,传递的类型是范型的Observable
private CallAdapter<Observable<?>> getCallAdapter(Type returnType, Scheduler scheduler) {
B、DefaultCallAdapter:返回的是CallAdapter<Object, Call<?>>(){}通配符
ps:如何添加一个新的CallAdapter呢?
答:extends继承 这个Factory类,并覆盖get方法:就能添加新的CallAdapter使用
@Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit){}
静态工厂模式:简单工厂模式
实例:Platform类
含有一个静态域:private static final Platform PLATFORM = findPlatform();
//通过findPlatform()返回给客户端调用,findPlatform就是一个静态工厂方法
private static Platform findPlatform(){
try{
//根据Class.forName的返回值来判断所需要不同的平台,默认的是android平台
Class.forName(“android.os.Build”);
}
...
}
静态工厂与动态工厂的区别
三、外观模式
外部与子系统的通信方式是必须通过一个统一的外观对象(门面)进行,为这个子系统的接口提供一个一致的界面
特点:高内聚 低耦合 对外提供一个统一的接口
在客户端 和子系统间 定义一个高级接口,桥接两者
客户端只需要跟中间类交互就可以,中间类把具体操作和子系统进行内部交互,再把结果交还给客户端
在Retrofit中的使用:
Retrofit的初始化操作就使用了外观模式,Retrofit自身就相当于外观类,内部封装所需要的7大成员变量的初始化操作
四、策略模式
根据环境和条件的不同选择不同的方法策略,完成需要的任务。
Context:上下文角色;
维护一个策略角色的引用;定义一个接口ContextInterface()。来让Strategy来访问Context的数据
使用Strategy的接口来调用某个定义的具体的Strategy策略算法
Strategy:策略角色;
不同的具体策略需要实现的方法在该方法中定义
ConcreteStrategy xx :具体的策略
Retrofit 中的使用:
addCallAdapterFactory(xx.create) 设置平台适配模式
interface CallAdapter<T> 接口就是提到的抽象的Strategy
具体的策略实现:CallAdapter的实现类完成
在addCallAdapterFactory中()里会生成一个具体的adapter工厂,实际上就是一种具体的策略
Context:在Retrofit当中配置的类就是对应的Context
与工厂模式的区别:
-
工厂模式强调的是通过RxJavaCallAdapterFactory当中的get()返回(生产)不同的CallAdapter对象;生产不同的对象
-
策略模式强调的是通过不同的CallAdapter对象当中的adapt()方法来产生具体的实现;不同对象的策略、方法的具体实现
五、适配器模式
将一个接口转换成客户端希望的另外一个接口,使与这个接口本不兼容的类可以在一起协同工作
Android当中会将封装的OkHttpCall和主线程executor两个实例对象适配成一个新的实例对象,这样这个call就具备了UI线程切换的功能
@Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) != Call.class) {
return null;
}
final Type responseType = Utils.getCallResponseType(returnType);
return new CallAdapter<Object, Call<?>>() {
@Override
public Type responseType() {
return responseType;
}
@Override
public Call<Object> adapt(Call<Object> call) {
return call;
}
};
}
由于CallAdapter支持多种平台,所以不同的平台需要适配不同的Calladapter
通过适配器模式:
将平台特性和该OkHttpCall适配成可以使用的Call的实例
六、观察者模式-一对多
一个对象和多个对象之间的依赖关系,当一个被观察者对象发生变化的时候会自动通知其他观察他的对象作出相应的改变,这些观察者们之间可以没有相互联系,增加代码的可扩展性。
Retrofit中:
1、创建Retrofit实例
2、定义一个含有注解的接口方法
3、通过调用Retrofit的Create方法完成网络请求接口实例的创建
4、通过动态代理将定义的接口以及接口里的注解、方法和参数转换成Retrofit中OkHttpCall对象call再进行网络请求
Create()方法就是使用了动态代理的所在之处:
public <T> T create(final Class<T> service) {
...
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
//在invoke方法里实现java的反射机制的实现
@Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
...
}
所有的网络请求相关的操作肯定支持异步请求,而异步请求内部实现通过维护一个内部的队列进行
Callcall = interface.getCall();//调用接口中的方法完成OkHttpCall的创建
这个Call接口就是被观察者,里面有相应的实现方法。
call.enqueue(new retrofit2.Callback(){...});
这里的Callback就是观察者,根据call作出反应的变化
B、Retrofit的题
(1)Retrofit中如何完成线程切换(子线程<->主线程)?
解析:
在构建Retrofit对象的时候使用Builder()内部类构建并通过.build()完成Retrofit的创建,在该方法中:(1)最先判断baseUrl是否存在,不存在抛异常;(2)其次会判断是否callFactor对象为空,若为空Retrofit自己则创建一个新的OkHttpClient;(3)第三步会创建一个Executor线程池,这个callbackExecutor(内部通过handle机制完成)就是Retrofit进行主线程到子线程切换的callback;(4)第四步会创建一个AdapterFactory用于产生callAdapter适配器对象,请求封装类Call的适配器,负责将call请求转换成另一个可以被当前平台识别的对象。该对象会被添加到platform(在.Builder()内部创建的该对象)的默认适配器defaultCallAdapterFactory工厂;(5)第五步添加一个ConverterFactory数据转换器工厂,将http请求返回的数据解析成java所能用的对象;最后将前面5个对象返回给Retrofit对象构造函数中完成Retrofit对象的创建。
其中第4步中的platform用于适配平台,本身是一个静态platform对象,通过findPlatForm()初始化实例。默认情况下调用defaultCallbackExecutor()函数中返回了一个Android.MainThreadExecutor(),来进行主线程切换操作。在该方法中有一个主线程的handler(传入Looper.getMainLooper()一个主线程Looper)
在call.enqueue()中Callback回调函数会调用ExecutorCallbackCall.this.callbackExecutor.execute(…)。
callbackExecutor在默认情况下就是使用的MainThreadExecutor,所以最后会回调到主线程中。
答:
static class mainThreadExecutor implements Executor{
//构建持有主线程Looper的Handler
private final Handler handler = new Handler(Looper.getMainLooper());
MainThreadExecutor(){}
public void execute(Runnable r){
this.handler.post(r);
}
}
Retrofit 运用android基本的异步机制;通过构建主线程的Looper,通过Looper.getMainLooper()创建handler,由于handler构造函数中传入的是主线程的Looper,所以能实现通过handler.post(runnable)将消息被主线程Looper获取,将整个的运行在主线程中
(2)Retrofit和RxJava结合 完成基本网络请求
a、不使用RxJava的Retrofit网络请求-生成call对象
1、创建一个用于描述网络请求的接口(含有注解及方法参数)
ps:每一个接口当中的方法的参数也必须用注解进行标注 *用于@GET方法的@Query(),用于查询参数,否则出错*
2、利用构建者模式 创建Retrofit对象
3、调用Retrofit对象的Create()方法创建自定义网络请求接口实例
4、通过接口对象调用接口方法创建Call实例,用于真实的请求实例
5、通过call对象建立同步/异步请求
b、使用RxJava的网络请求-生成observable对象
1、在Retrofit初始化的时候添加RxJavaCallAdapterFactory适配器工厂,Retrofit和RxJava进行关联-区别与普通Retrofit对象的构建
2、定义新的接口,方法返回值是Observable 被观察者 -与普通Retrofit对象流程的差异化之一
3、通过新的接口方法 获取Observable 对象实例
4、调用被观察者.subscribeOn()函数:来指定io线程处理数据
5、调用.observeOn(AndroidSchedulers.mainThread()) :在主线程中显示数据
ps:RxJava线程操作符切换线程,因为不能在主线中耗时操作,不在工作线程显示Ui,所以工作结束后要通知主线程更新Ui
6、在.subscribe(onCompleted() / onError() )中处理返回结果
(3)Hook与动态代理
Hook:
通过某种手段对一件事物进行改头换面,从而劫持Hook的目标来达到控制目标的行为的目的。
在不改变原代码的情况下替换原有的对象、方法、函数从而改变原有行为的目的
DroidPlugin 历史:
Hook很多android系统底层代码,不安全!现在已被废弃
Hook的两种实现机制:
<1>、反射
把需要替换的目标类替换成需求类,注意:该类需要有和目标类有一样的特征。
hook理念: 实现目标类含有的接口,或是继承目标类的实现类,拦截其内部方法并修改
1、通过反射机制获取目标类的私有成员变量
Field carEngineFiled = 目标类PrivatePlane.class.getDeclaredField(“目标类的引用");
2、通过setAccessible 设为true 表示可以访问私有方法和它的成员变量
carEngineFiled.setAccessible(true);
3、改变原有成员变量 privatePlane—》ReflectPrivatePlane
PrivatePlaneEngine privatePlaneEngine = (PrivatePlaneEngine)carEngineFiled.get(privatePlaneEngine);
carEngineFiled.set(privatePlane,new ReflectPrivatePlane新修改类(privatePlaneEngine) );
<2>、动态代理——JDK形式
1、 创建ProxyHandler 代理类并实现 InvocationHandler 接口
Public class PrivatePlaneProxyHandler implements InvocationHandler {
private Object object;
public PrivatePlaneProxyHandler(Object object){
this.object = object;
}
//在invoke()方法中添加需要的逻辑,修改返回值
@Override
public Object invoke(Object proxy,Method method,Object[] args){
if(“xxx” .equals(method.getName())){
return …;
}
return method.invoke(object, args):
}
}
2、代理测试类
Filed carEngineFiled = PrivatePlane.class.getDeclared(“privatePlaneEngine");
carEngineFiled.setAccessible(true);
PrivatePlane carEngine = (PrivatePlane) carEngineField.get(privatePlane);
//调用Proxy,newProxyInstance,传入目标实现了字节码,interface接口字节码,及代理类Handler对象
PrivatePlaneEngineInterface
carEngineProxy = (PrivatePlaneEngineInterface) Proxy,newProxyInstance(
privatePlaneEngine.class.getClassLoader(),
new Class[] {PrivatePlaneEngineInterface.class},
new PrivatePlaneProxyHandler(carEngine) );
carEngineField .set(privatePlane , carEngineProxy);
(4)MVP MVC模式
MVC - Model View Controller
数据模型显示和业务逻辑相分离的操作,在改变Ui界面的时候不需要重新的修改业务逻辑
M:处理数据和业务逻辑
V:更新UI显示
C:控制V层与M层通信,分离更新UI和更新业务逻辑的操作
Android中的MVC:
V:XML文件描述
C:Activity or Fragment 的操作(不进行耗时操作),将数据层的改变交给Ui层
M:JavaBean 实体类
缺陷:
1、所有逻辑都在A or F层进行,造成A/F 层代码的臃肿, 对代码扩展维护不利
2、XML功能太弱,逻辑的简单不能构成复杂的内容代码
3、允许M数据层和V层进行通信和数据交互
MVP
V:更新UI显示
P:逻辑处理,提供V层和M层之间交互的操作
M:提供数据
Android中的MVP:
V:实现V层的接口,绘制Ui界面,与用户的交互操作,Activity or Fragment 极简操作,进行负责与P层关联(持有P层引用)
P:实现P层的接口,V层和M层交互的核心纽带,ps:将A/F层复杂逻辑交予P层处理,通过接口回调的形式返回给V层 ;持有V、M层引用
M:实现M层的接口,数据的下载,存储及与服务器接口数据交互操作
优势:
将UI层和业务逻辑层的交互通过接口进行
缺陷:
1、P层继承了Activity or Fragment的随着业务代码的增加逻辑越来越复杂,代码越来越臃肿的问题
2、V层与P层的耦合问题,相互持有引用(问题在MVP解析文章中进行详细描述)
3、接口的颗粒度,接口的过多过少都会影响
(5)sp跨进层问题及apply/commit
sharedPreferences跨进城读写 —does not work reliably
Context.MODE_MULTI_PROCESS
数据安全问题
Sp底层使用的是XML文件的形式,Android的读取有自己的缓存策略。在使用的时候会在内存中缓存一份sp的缓存。这样就会导致在多进程操作下,每一个进程都会保存一份sp的缓存文件,造成读写的不一致情况的产生。
相对安全的跨进程数据操作:ContentProvider
apply /commit :提交数据
区别:
- apply 是一个void 没有返回值的异步方法;先将修改的数据提交到内存(commitToMemery),(run)异步将真实数据提交到硬盘中(enqueueDiskWrite(mcr,runnable))
ps:提交失败不会收到通知;
在子线程中提交数据到HD中
- commit是一个boolean 类型返回值的同步方法;同步将数据提交保存到内存和硬盘中,多个并发提交commit时候会阻塞,会等待真正执行的commit执行完后才会执行相应的其他commit操作——enqueueDiskWrite(mcr,null)在当前主线程操作
ps:提交失败会有boolean值反馈,不需要返回值反馈的时候推荐使用apply
在当前主线程中同步的阻塞式提交数据
在执行了apply(),再执行commit()方法 ,commit()会被阻塞