前言
在日常生活中我们经常和代理打交道,举个栗子,当我们出去旅行需要预定酒店时,我们一般选择携程或飞猪这些酒店代理商来完成酒店的预定,这里携程们(代理)代理了和实际服务提供者(酒店)之间的酒店预订流程。代理解决了什么问题?
- 降低通讯成本,用户不需要和每一个实际服务商通讯,只需要和代理商就可以完成通讯
- 抹平差异,让用户只需要关注到订酒店这件事情,和订酒店无关的其他事情都被代理商做掉了
那么什么是「代理模式」?
代理控制了对象的真实访问。代理模式是指,在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。主要解决在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。 这里直接访问对象时带来的问题,可以类比上面生活中的例子,车站买票太远(对象单次访问成本高)选择用代买点买票。直接去酒店订房间太麻烦(实现功能的对象太多无法直接选择出最佳的对象)而选择在酒店平台预定酒店。
Binder中的代理模式
所有安卓开发应该都已经有了一个印象那就是binder是android中实现进程间通信的方案,也都听说过所谓共享内存、Socket、管道、消息队列等等一堆进程间通信(IPC)的方案,也听说过诸如dubbo,spring cloud之类的远程过程调用(RPC)。但是可能只是听过,对安卓应用工程师来说这些技术名词总给人云里雾里的感觉,binder作为进程间通信的方案又有远程调用方案的特征,如果说想要理解远程调用的方案,这里一定绕不开代理模式。不管是移动端的跨进程通信还是服务端的远程通信都是围绕代理模式来做设计的,在通讯的两端分别用代理隐藏实际的通信细节,让调用方像调用自己进程内对象方法一样实现对跨进程对象的调用。你比较熟悉组件化开发的话,也可以先从组件化的视角来类比binder通信,如果让你来实现一个进程间通信的架构方案,有哪些东西是必不可少的呢?
先类比组件化
- 面向接口的设计,组件间通信,通过接口暴露组件能力
- 通过一个manager获取接口对应的服务实例,组件间相互调用,意味着组件间能够通过某种方式获取定义的组件接口所对应的实例对象,实现调用其他组件的方法就像调用自己内部方法一样
- 服务的注册和查询,想要获取响应的接口实现,意味着有一个类似路由和路由表的东西,通过一个路由服务查询到具体的接口实现对象
- 其他高性能、安全性、稳定性的设计暂且不谈
再看Binder IPC,
- 开发者使用AIDL实现进程间通信接口的定义
interface IMyAidlInterface {
Response aidlGet(in Request request);
}
- 调用者client通过context bindService获取远程服务的代理对象,通过
queryLocalInterface和Stub.DESCRIPTOR查询到远程服务在client的代理对象
public static com.android.aidldemo.IMyAidlInterface asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.android.aidldemo.IMyAidlInterface))) {
return ((com.android.aidldemo.IMyAidlInterface) iin);
}
return new com.android.aidldemo.IMyAidlInterface.Stub.Proxy(obj);
}
- 对client来说,Binder代理对象
Stub.proxy隐藏了和远程服务对象真实通信的细节,client不需要关心这个代理对象是不是真实的服务实现方,就像调用本地方法一样调用原生服务对象。而Stub.proxy实现了调用参数的序列化和响应结果的序列化,帮助client拿到了远程调用的结果。
可以对照下图理解代理过程:
同样的角色分布我们可以再看看系统的ActivityManagerService。IActivityManager是一个服务接口,代表了服务能力。ActivityManagerNative代表系统本地服务,ActivityManagerService是它的具体实现。而ActivityMangerProxy代表在app中的BInder代理对象,实现client到service调用的代理转发。
Retrofit的代理
相比较Binder的静态代理,retrofit使用了动态代理模式。所谓动态代理模式,是指并没有手动创建一个代理类,而是使用动态字节码的方式创建代理类(用class生成class),然后使用反射的方式创建代理类的对象,再使用反射方式调用被代理的方法。可以先看一个在java中最简单的动态代理写法:
public class TestProxy {
public static void main(String args[]) {
Subject subject = new RealSubject();
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
//在调用具体函数方法前,执行功能处理
result = method.invoke(subject, args);
//在调用具体函数方法后,执行功能处理
return result;
}
};
Subject object = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
subject.getClass().getInterfaces(), handler);
object.doSomething();
}
}
我们使用Proxy.newProxyInstance和InvocationHandler动态构造代理对象,通过获取invoke method注解对请求的描述信息,生成ServiceMethod对象,并根据该对象执行相应的网络请求。
public <T> T create(final Class<T> service) {
validateServiceInterface(service);
return (T)
Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] {service},
new InvocationHandler() {
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
@Override
public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
args = args != null ? args : emptyArgs;
return platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args);
}
});
}
这里借用知乎大佬的一张图,同时对比下静态代理和动态代理的差异。
主要在于代理类如何生成,如果是编码过程中生成的代理类就是静态代理,所以静态代理的一个缺点就是会生成很多代理类。
如果在运行时或者编译时动态生成的代理类,一般就是动态代理。可以通过接口的Class对象,创建一个代理Class,通过代理Class创建代理对象。也就是所谓的用Class造Class。
以上。