Android开发中的代理模式
来源:互联网 发布:linux grep 完全匹配 编辑:程序博客网 时间:2024/05/29 00:32
上一篇文章Android开发中单例模式写法与可能遇到的坑讲到了单例模式的一般写法,本篇继续总结一下Android(Java)中比较常用的代理模式。和单例模式一样,代理模式也是经常使用到的,使用过Spring的小伙伴,应该比较清楚,其AOP代理就默认使用JDK自带动态代理来生成代理类的。在Android中很多基于Binder机制的系统服务如ActivityManagerService、自己定义的AIDL都使用了远程代理来进行跨进程通信,如果使用过网络请求框架Retrofit(可以参考我之前写的Retrofit2源码解析),也可以轻易发现,也使用了动态代理。下文会再详细分析。现在先看一下代理模式的概念
代理模式
《Head First Design Patterns》对其定义:代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。
这个定义很容易引导我们思考,为什么要为一对象提供“替身”,或者去控制对对象的访问呢?直接访问对象不也是可以吗?
现实是有些时候没有那么简单,例如如果客户端需要访问的对象在服务端,客户端直接访问需要处理网络等一系列复杂的问题,如果使用代理模式那么客户端只需要和代理打交道,客户端不需要知道代理怎么和服务端交互。当然这只是一个例子,在生活中也有例如火车票飞机票代售点这类代理思想的例子。我们就从最简单的代理模式说起吧!
静态代理
顾名思义,代理类是在编译时就生成的,也就是说代理类在编译完成后已经存在.class文件。一般来说它的UML(直接使用Idea生成的,不是很美观)是这样的:
RealSubject:原对象也可以称之为委托对象的类。
SubjectProxy:代理对象类
Subject:委托和代理对象类共同实现的接口,request()是其拥有的方法。
一般来说,代理模式都会包括上述的三个部分。只不过,会出现各种变体罢了,所以从最简单的入手,在慢慢深入是一个很好的方式。
上述UML对应的Java代码如下:
/** * 委托对象和代理对象实现的接口 * Created by chuck on 17/1/19. */public interface Subject { void request();}/** * 委托类,该类真正实现了request() * Created by chuck on 17/1/19. */public class RealSubject implements Subject { @Override public void request() { System.out.println("RealSubject::request"); }}/** * 代理类,通过其访问委托的实现 * Created by chuck on 17/1/19. */public class SubjectProxy implements Subject { Subject subject; public SubjectProxy() { this.subject = new RealSubject(); } @Override public void request() { System.out.println("SubjectProxy:preRequest"); if (subject != null) { subject.request(); } System.out.println("SubjectProxy:finishRequest"); }}public class ProxyTest { public static void main(String[] args) { //静态代理 SubjectProxy proxy = new SubjectProxy(); //通过代理访问 proxy.request(); } }
麻雀虽小,五脏俱全,通过这个简单的例子,可以快速的掌握代理模式中最核心的委托对象,代理对象和代表其行为的接口。有了这些基础在去看其他的变种就容易的多。
* 动态代理*
有静态代理,当然也有动态代理。所谓动态代理就是在运行时才会生成代理类,和静态代理不同在编译阶段不会生成实在.class文件,而是在运行时动态生成类字节码。然后再通过ClassLoader加载到JVM中,简而言之,代理类是在运行时根据需要代理的接口等一系列参数动态生成的。其UML图如下:
这个UML还不是完整的,只包含了核心的部分,但是没有包括运行时生成的代理类。这个稍后再分析。我们上图代表的是我们编码时涉及到的各部分。
Subject,RealSubject上文中已经说过了,这里着重介绍下边两个类:
java.lang.reflect.Proxy:这个是生成代理类的核心类,位于java.lang.reflect包下,我们大致可以确定该类和反射有关。事实也确实如此,动态代理就是依靠反射生成代理类的,并且生成的代理类都是继承自Proxy,即$ProxyN extends Proxy
。关于生成的代理类所在包和命名也是有讲究的:
1.如果所代理的接口访问权限是public,那么代理类会被定义在root包路径下,如果是package(java中接口不能被定义为private和protect)那么代理类会被定义在接口所在的包。这样做可以有效的防止生成的代理类出现包访问权限的问题。
2.生成的代理类名称都是自动生成的类似”$ProxyN”格式的,N会随着代理类的数量增加,可以保证代理类的命名是唯一的。下边是我Demo中生成的代理类
3.生成的代理类都被final修饰符修饰,保证代理类不允许被继承。
上文说了代理类会继承自Proxy,同时还会实现其代理的接口,这样做的目的是可以将代理类安全的类型转换成所代理的接口类型。代理类完整的继承关系:
结合上文中的UML,构成了动态代理的基本结构。
java.lang.reflect.InvocationHandler接口:该接口有一个invoke()方法,我们在使用动态代理时一般都需要自定义一个类然后实现该接口,自定义的类就是我们的调用处理器,我们可以在这里实现我们需要的任务,当运行时代理类调用任何方法都会回调invoke()方法。
动态代理简单的实现:
/** * 委托对象和代理对象实现的接口 * Created by chuck on 17/1/19. */public interface Subject { void request();}/** * 委托类,该类真正实现了request() * Created by chuck on 17/1/19. */public class RealSubject implements Subject { @Override public void request() { System.out.println("RealSubject::request"); }}/** * 实现InvocationHandler * Created by chuck on 17/1/19. */public class ProxyHandler implements InvocationHandler { private Subject subject; public ProxyHandler() { subject = new RealSubject(); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("ProxyHandler:preInvoke"); Object result = method.invoke(subject, args); System.out.println("ProxyHandler:finishInvoke"); return result; }}public class ProxyTest { public static void main(String[] args) { ProxyHandler handler = new ProxyHandler(); Subject proxy = (Subject) Proxy.newProxyInstance(RealSubject.class.getClassLoader() , RealSubject.class.getInterfaces(), handler); proxy.request(); }}
一般来说,使用Java动态代理的步骤:
1.定义委托类和公共接口
2.自定义一个现实了InvocationHandler接口的调用处理类(代码中的ProxyHandler),然后在invoke方法中实现自己想要的任务,例如监控方法的调用。
3.通过Proxy.newProxyInstance方法生成代理类,代理对象。
第3步中的Proxy.newProxyInstance方法的实现比较复杂,打算单独一篇文章分析,这里简单介绍一下。主要是看一下其参数:
Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
ClassLoader loaer:类加载器,主要是将生成的代理类的字节码装载到JVM,动态生成的代理类同样需要ClassLoader加载,并为其指明对象。所以每次在生成代理类和代理类对象时,都需要指定一个类加载对象。
Class
public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); if (validateEagerly) { eagerlyValidateMethods(service); } return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, 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); } if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } ServiceMethod serviceMethod = loadServiceMethod(method); OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall); } }); }
关于Retrofit的分析可以看看我之前的Retrofit2源码解析 。
远程代理
远程代理可以为一个位于不同的地址空间的对象(例如服务端server)提供一个本地代理对象(存根或者桩)。这个不同的地址空间在java中可以是不同的进程甚至是不同的机器。远程代理可以将网络或者跨进程通信的细节隐藏起来,使得客户端不必考虑网络或跨进程的存在。客户完全可以认为被代理的对象是本地的而不是远程的,而代理对象承担了大部份的通信工作。
上图只是概念图,实际要复杂的多,图中proxy即为server在客户端本地的代理对象。这样client可以直接和proxy交互,然后proxy处理和server的通信,他们之间的通信可以很复杂也可以简单。在java中可以有类似RMI一样的机制,在Android中也有其特殊的Binder机制。并且Android中大量使用其作为IPC通信方式。接下来我就结合AIDL来认识一下吧。
Binder作为Android系统IPC重要的通信方式,融合了代理模式和RMI机制的思想。在Android中的很多系统服务中如ActivityManagerService,PackageManagerService等都有很多应用,当然还有我们平时进行跨进程通信的AIDL也是以Binder为基础的。如果对AIDL还不熟悉可以先看一下从AIDL看Android跨进程通信 。AIDL在我们定义好接口后,会自动给我们生成一个对应的java文件,代码如下(直接用的从AIDL看Android跨进程通信 的例子。):
/* * This file is auto-generated. DO NOT MODIFY. * Original file: /Users/chenkai/Android/codeExcise/aidl/AidlDemo/app/src/main/aidl/com/chuck/aidldemo/IMyService.aidl */package com.chuck.aidldemo;// Declare any non-default types here with import statementspublic interface IMyService extends android.os.IInterface { /** * Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.chuck.aidldemo.IMyService { private static final java.lang.String DESCRIPTOR = "com.chuck.aidldemo.IMyService"; /** * Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.chuck.aidldemo.IMyService interface, * generating a proxy if needed. */ public static com.chuck.aidldemo.IMyService asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.chuck.aidldemo.IMyService))) { return ((com.chuck.aidldemo.IMyService) iin); } return new com.chuck.aidldemo.IMyService.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_getValue: { data.enforceInterface(DESCRIPTOR); java.lang.String _result = this.getValue(); reply.writeNoException(); reply.writeString(_result); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.chuck.aidldemo.IMyService { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ @Override public java.lang.String getValue() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.lang.String _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getValue, _data, _reply, 0); _reply.readException(); _result = _reply.readString(); } finally { _reply.recycle(); _data.recycle(); } return _result; } } static final int TRANSACTION_getValue = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); } /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ public java.lang.String getValue() throws android.os.RemoteException;}
从代码中可以看出远程代理的影子,如果感觉代码结构不清晰,可以看下图(也是):
IMyService是共同的接口,Proxy是代理,Client通过其发起远程请求。Stub是Server的存根。其简化的框架可以是这样的:
这里不是讲Binder的知识,Binder机制很复杂,需要专门的学习,只是说明一下远程代理在Android中的应用,体会一下远程代理在现实中的应用。掌握了这些基本结构,在分析ActivityManagerService等一系列Android系统服务时对其结构会有很大的帮助。
总结:代理还有其他多种变种,例如虚拟代理,Copy-on-Write代理,保护代理,防火墙(Firewall)代理,智能引用(Smart Reference)代理等。这里不做详细介绍了,总之,代理模式不管是在java,Android甚至是现实生活中都普通存在的模式,有必要掌握其思想。
本文练习的项目地址https://github.com/hgchenkai/DesignPattern 有时间都亲手敲一下。
- Android开发中的代理模式
- Android中的代理模式
- android开发之代理模式
- iOS开发中的设计模式----代理模式
- 代理模式在企业级开发中的应用
- 浅谈Java开发中的代理模式
- Android源码中的代理模式解析
- Android中的系统服务(代理模式)
- 代理模式在Android中的应用
- 【开发模式】代理模式
- [设计模式]代理模式在android中的使用
- android应用开发设计模式之代理模式
- android应用开发设计模式之代理模式
- Android开发中常用的设计模式 --- 动态代理模式
- Android的开发之&java23中设计模式------>代理模式
- Android中的 mvp 开发模式
- Android开发中的MVVM模式
- Android开发中的MVP模式
- 一:简单的C++程序--初学第一天
- 博客写些什么
- 子线程新建Handler为什么会报错?——浅谈Handler,Looper,Message之间的关系
- 2017计划。
- cocostudio读取UI后,触摸或者按钮点击穿透问题。
- Android开发中的代理模式
- Scrapy中文乱码解决
- 如何创建索引、什么时候该创建、什么时候不应该创建
- JavaScript返回上一页方法归纳
- Linux Miscellaneous Device
- android selector 备忘
- 借助listView实现parallaxListView
- Android利用canvas画各种图形
- 转!!URL和URI区别