一 . 简介
代理模式是Android 开发中常用的一种设计模式,能为其他对象提供一种代理以控制对这个对象的访问,能在客户端和目标对象之间起到中介的作用。
对于一些初学者来说,上述定义可能很难理解,或者理解了定义却不知道怎么运用,试用哪些场景.
本文从基础定义介绍代理模式,同时从常用的框架中举例,介绍代理模式的使用.
希望能帮助还没有理解的猿(媛)们理解.欢迎大家指正.
二. 详解
分类: 代理模式分静态代理和动态代理
先举个 宝强与老宋 假栗子:
对象
宝强
是明星,而各位大佬很有钱,想找宝强拍戏,但是你无法直接找到他,所以就需要经纪人老宋
帮忙
对象老宋
就负责了宝强
的工作安排和各种事务处理工作,同时他还可以具有特殊的行为,比如对马小姐
的访问.
这个老宋
就相当于代理模式中的代理,你可以通过对象老宋
从而调用对象宝强
.
如图:
- 真实对象
(宝强)RealSubject
与代理(老宋)Proxy
需要实现同一个接口 (你)Client
访问(宝强)RealSubject
时,可以通过访问(老宋)Proxy
进行对(宝强)RealSubject
的控制(老宋)Proxy
可以作为一个轻量级的对象代理(宝强)RealSubject
的一部分,也可以修改原来属性方法,比如添加CallMaXiaoJie()
(老宋)Proxy
作为(你)Client
和RealSubject
的中间层,可以降低模块间和系统的耦合性
下面介绍具体的代码实现和用法分析
1.静态代理
接口Subject,包含宝强演戏和聚会等抽象行为
public interface Subject {
void act();
void party();
}
Baoqiang:代表宝强 ,实现了Subject接口,具有演戏和聚会的真正行为.
public class Baoqiang implements Subject {
private static final String TAG = "Baoqiang:";
@Override
public void act() {
//宝强片场演戏
System.out.println(TAG+"去片场演戏");
}
@Override
public void party() {
//宝强参加聚会
System.out.println(TAG+"去参加聚会");
}
}
LaoSong:代表老宋,同样实现了Subject的接口,拥有了
Baoqiang
相同的行为方法,控制Client
对Baoqiang
的访问.
public class LaoSong implements Subject {
private static final String TAG = "LaoSong:";
private Baoqiang mBaoqiang;
public LaoSong(Baoqiang baoqiang) {
mBaoqiang = baoqiang;
}
/**
* 安排宝强演戏
*/
@Override
public void act() {
callMaXiaoJie(TAG);
mBaoqiang.act();
callMaXiaoJie(TAG);
}
/**
* 安排宝强聚会
*/
@Override
public void party() {
callMaXiaoJie(TAG);
mBaoqiang.party();
callMaXiaoJie(TAG);
}
/**
* 约马小姐
*/
private void callMaXiaoJie(String string) {
System.out.println(string+"约马小姐");
}
}
Client(你)通过访问代理LaoSong从而通知Baoqiang去act(),party();
public class Client {
public static void main(String[] args) {
LaoSong proxy = new LaoSong(new Baoqiang());
proxy.act();
System.out.println("");
proxy.party();
}
}
打印结果:
结果表明: .Client 你“通过访问代理LaoSong 完成了对真实对象Baoqiang的访问,安排了Baoqiang去act()和party(),同时 代理LaoSong 也可以拥有自己的行为,比如: 约马小姐
注:上面就是静态代理的简单使用,可能大佬会有如下问题?
- 问: 既然已经有真实对象,为何不直接修改方法捏,为何还要传入到代理中去使用捏.不是反而变慢了么?
答: 修改原有的方法来适应。这样违反了“对扩展开放,对修改关闭”的原则.
不利于维护和扩展
(万能答案)问:为何要实现同一接口,随便写一个类依赖真实对象不是也可以么.
答: 简单而且具有规范性,
易于维护和扩展
(万能答案),当然也有坏处,如果接口方法很多,代理类就需要实现很多不必要实现方法。增加了代码维护的复杂度。问: 如果真实对象不能直接拿到,而且也无法实现共同的接口,怎么办?
- 答: 一个对象无法被直接引用,也无法实现共同的接口的时候,我们可以使用JAVA动态代理,同时也不用像静态代理类一样,实现很多不必要的方法.
2.动态代理
当对象无法被直接引用,也没有共同接口的时候,我们可以使用JAVA 动态代理 控制对原有对象的访问
JAVA 动态代理 在Android使用比较常见
比如:Retrofit框架
还有鸿洋大佬的一篇 [教你打造Android的IOC框架],里面就用到了动态代理
注: 后面会用这两篇示例讲解动态代理
1.基础介绍
假设:现在我们无法拿到真实对象的引用(即无法直接联系宝强),真实对象也没有实现对外的共同接口,按照静态代理我们就无法生成一个代理类老宋.所以我们也无法通过老宋取得”宝强的联系,所以大佬们就要通过实现动态代理实现和宝强的联系
在Java动态代理机制中,有一个重要的 Proxy类和InvocationHandler接口,这一个类和接口是实现我们动态代理所必须用到的。首先我们先来看看这两个类进行的描述:
java.lang.reflect.Proxy
Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.
Proxy 提供 一组静态方法 为 一组接口 动态的生成对象和代理类,它也是所有生成的代理类
的父类
这里也就关注Proxy的newProxyInstance这个静态方法
/**
* @param loader 指定类加载器
* @param interfaces 指定类的实现接口
* @param invocationHandler 动态代理类的处理器
* @return 代理类Proxy
*/
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler invocationHandler);
方法需要三个参数:
参数 ClassLoader loader 和 参数 Class<?>[] interfaces 用来生成动态代理proxy.
参数invocationHandler 用来执行的代理类的方法逻辑.
java.lang.reflect.InvocationHandler
InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.调用处理器接口,自定义invoke方法,用于实现对于真正委托类的代理访问,当动态代理类方法执行时,就会执行InvocationHandler的invoke方法.
/**
* @param proxy:指代理类实例
* @param method:指代的是我们所要调用真实对象的某个方法的Method对象
* @param args:指代的是调用真实对象某个方法时接受的参数
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
好了,基本介绍完毕
没有懂得大兄弟不用担心,
下面从常用的框架源码中分析,动态代理proxy具体使用,一起来感受感受它的强大.
示例 一
2014年9月22日,hyman在他的CSDN里发表了文章 教你打造Android的IOC框架[ViewInject](传送门)
这是关于对Android控件的依赖注入的文章,里面就用到了动态代理,以及包含动态代理的介绍,大兄弟们可以直接点进去阅读.
我这里根据自己的理解也介绍一下:
首先看代码:
@ContentView(value = R.layout.activity_main)
public class MainActivity extends BaseActivity
{
@ViewInject(R.id.id_btn)
private Button mBtn1;
@ViewInject(R.id.id_btn02)
private Button mBtn2;
@OnClick({ R.id.id_btn, R.id.id_btn02 })
public void clickBtnInvoked(View view)
{
switch (view.getId())
{
case R.id.id_btn:
Toast.makeText(this, "Inject Btn01 !", Toast.LENGTH_SHORT).show();
break;
case R.id.id_btn02:
Toast.makeText(this, "Inject Btn02 !", Toast.LENGTH_SHORT).show();
break;
}
}
是不是觉得很熟悉,类似一种ButterKnife的简化版,不过原理不一样的,这里我们主要介绍动态代理,我们主要看下面这段;
@OnClick({ R.id.id_btn, R.id.id_btn02 })
public void clickBtnInvoked(View view){}
这是实现了view的点击事件的处理,可具体是怎么实现的捏,先让我们回顾下平常点击事件;
mBtn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "点击了view" );
}
});
我们都知道,当我们点击view的时候,事件会由Activity传到我们所对应的view,而onClick是在onTouchEvent内部通过performClick触发的,如下图:
所以我们只要li.mOnClickListener不为null,li.mOnClickListener.onclick(view)就会执行,而mOnClickListener就是OnClickListener 的接口,具体如下:
public OnClickListener mOnClickListener;
public interface OnClickListener {
void onClick(View v);
}
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
所以为什么大佬们每次处理点击事件的时候,都会先调用view.setOnClickListener(listener)方法,其实就是为了传递一个OnClickListener对象,然后回调onclick(view)方法,我们就可以在onclick方法中处理自己的逻辑.
其实ViewInject的本质是一样的,只不过通过IOC的思想,使用自定义注解的方式,把view.setOnClickListener(listener)的控制,反转给了其他的类(ViewInjectUtils)处理,下面我们来看具体实现:
private static void injectEvent(final Activity activity) {
Class<? extends Activity> clazz = activity.getClass();
//获取当前Activity所有的方法
Method[] _methods = clazz.getDeclaredMethods();
for (final Method _method : _methods) {
//找到有Onclick注解的方法
Onclick _onclick =_method.getAnnotation(Onclick.class);
if (_onclick != null) {
//获取当前注解所传入的控件Id数组
int[] viewId = _onclick.value();
_method.setAccessible(true);
/获取OnClickListener的代理
Object listener = Proxy.newProxyInstance(
View.OnClickListener.class.getClassLoader(),
new Class[]{View.OnClickListener.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return _method.invoke(activity,args);
}
});
//遍历Id,转化成view
for (int id : viewId) {
View v = activity.findViewById(id);
try {
Method setListener= v.getClass().
getMethod("setOnClickListener",
View.OnClickListener.class);
//实现View.setOnClickListener(listener)方法;
setListener.invoke(v, listener);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
- 1-13行:主要通过反射拿到Activity中的方法,遍历找到@Onclick注解,获取到注解中的控件Id
- 27-28行:根据activity.findviewById(Id)指定对应的view控件
- 30-36行: 然后反射拿到setOnClickListener方法,通过view. setOnClickListener(listener)设置监听
注: 真实对象listener(宝强)是个系统的OnClickListener 接口,我们在ViewInjectUtil类中无法直接拿到它,所有需要通过java的动态代理生产一个OnClicklistener的代理类.
/获取OnClickListener的代理
Object listener = Proxy.newProxyInstance(
View.OnClickListener.class.getClassLoader(),
new Class[]{View.OnClickListener.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy,
Method method, Object[] args) throws Throwable {
return _method.invoke(activity,args); }
});
先看它前两个参数,由于要代理OnClickListener类,所以这里传入OnClickListener的类加载器和OnClickListener的接口数组,生成OnClickListener的代理类
动态代理类和静态代理类不一样, 它非预存在于任何一个.class 文件中也不需要事先定义好接口,它由 JVM 在运行时动态生成的.
我们再看第三个参数:传入的是个InvocationHandler接口对象,用于委托代理类访问,什么意思捏?稍后解释.
我们先来解决最后一个问题,我们现在已经拿到了OnClickListener类,也设置了view.setOnClickListener(listener)监听,这时候我们点击view的时候,代理类的onclick里方法就会被回调,我们也能在里面处理自己的逻辑,如下:
@Override
public void onClick(View v) {
Log.e(TAG, "点击了view" );
}
但是问题是我们现在并不需要onClick方法,而是希望能把回调放到下面的clickBtnInvoked(view)方法中执行,该怎么办捏?
@OnClick({ R.id.id_btn, R.id.id_btn02 })
public void clickBtnInvoked(View view){
switch (view.getId()) {
case R.id.id_btn:
Toast.makeText(this, "Inject Btn01 !",
Toast.LENGTH_SHORT).show();
break; }
}
这个时候,第三个参数InvocationHandler就出场了,InvocationHandler接口能委托代理类访问,当代理类OnClickListener.onClick(view)方法执行的时候,InvocationHandler中的invoke()方法也就被调用,然后我们只要在invoke方法中再调用clickBtnInvoked(view方法,就完成了,如下:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//_method就是clickBtnInvoked()方法
return _method.invoke(activity,args);
}
当然,我们还可以在InvocationHandler的invoke的方法中调用其他我们想要执行的逻辑(比如:访问马小姐),只要代理类的任何方法调用的时候,都会被委托到invoke方法中,我们只要做好逻辑判断即可.
好的,示例一的动态代理介绍完毕,下面我们看示例二.
示例 二
示例二是我们熟悉的Retrofit框架,这里先贴几句大家都熟悉的代码:
首先是APIService接口,里面通过注解的方式,用来定义了网络请求的方法
public interface APIService {
@GET("forecast7d?city=CH250901&key=hepj2khdr187nvlm")
Call<WeatherBean> getWeath();
}
然后是 Retrofit的基本配置
retrofit = new Retrofit.Builder().
baseUrl(HttpConfig.BASE_URL).
client(new OkHttpClient()). addConverterFactory(GsonConverterFactory.create())
.build();
APIService _apiService = retrofit.create(APIService.class);
我们看最后行代码retrofit.create(APIService.class)传递的是一个接口类,然后却直接返回了一个APIService 对象,这又事怎么实现的捏?我们看源码:
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);
}
});
}
此时看到 Proxy.newProxyInstance()方法是不是特别熟悉,没错,这里用的也是java动态代理机制生成了APIService 的代理类
Call<WeatherBean> weatherBeanCall = _apiService.getWeath();
而当代理类调用APIService 里面的请求时,同时InvocationHandler的invoke()方法也被调用,然后根据不同的情况,retrofit2.0在invoke里面就有了不同的具体实现.
至于具体实现是什么,这里就不做解释,
本文主要讲述的是代理模式,到这里也基本完成了,欢迎你的阅读