前言
前段时间学习了代理模式,并写了一篇笔记:移动架构学习笔记九:代理模式(Proxy )。当中有一些遗留问题,后来反复琢磨之后,觉得再写一篇笔记解决当时的问题。
遗留问题
笔记的最后一个问题:有没有办法将代理运用到所有事物呢?当时的代码在我后来反复的琢磨后,发现并不能用于代理所有的事物,因为在LogHandler类中,明确持有了Car类,导致它其实只能代理Car类。要想用于代理多有事物,得放宽当中引用的事物。后来修改了代码如下:
public class Main {public static void main(String[] args) {Car car = new Car();Movable movable = (Movable) Proxy.newProxyInstance(car.getClass().getClassLoader(), new Class[]{Movable.class}, new TimeHandler(car));movable.move();}static class LogHandler implements InvocationHandler {Object object;public LogHandler(Object object) {this.object = object;}private void before() {System.out.println("method start..");}private void end() {System.out.println("method end!");}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {before();Object o = method.invoke(object, args);end();return o;}}static class TimeHandler implements InvocationHandler{Object object;public TimeHandler(Object object) {this.object = object;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {long start = System.currentTimeMillis();Object o = method.invoke(object, args);long end = System.currentTimeMillis();System.out.println("事件耗费时间:" + (end - start));return o;}}
}
其中, LogHandler和TimeHanler这两个代理类中持有的引用,改为了Object。这样才能将两个代理类用来代理所有的事物。
动态代理实际运用
下面我们举一个动态代理的实际运用案例。在Android开发中,经常要设置控件的各种事件处理器,比如点击事件:OnClickListener,长按事件:OnLongClickListener。原生的Android开发中,要设置控件的事件处理器,会用到如下代码:
findViewById(R.id.click).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(MainActivity.this, "点击事件触发", Toast.LENGTH_SHORT).show();}});
代码中大量这种内部类,让人看起来头疼,而且需要写大量无用的代码。使用过XUtils、ButterKnife等IOC注入框架的朋友,就可以用如下代码来实现事件的注入:
@OnClick({R.id.click})public void onClick(View v) {Toast.makeText(this, "点击事件触发", Toast.LENGTH_SHORT).show();}
这段代码就简洁明了多了,今天我们就用动态代理来实现以下事件注入。首先聊一下思路:
首先注解要传递的内容有:
1、哪些控件需要绑定这个事件;
2、控件绑定的具体事件,比如是OnClickListener还是OnLongClickListener;
3、绑定指定事件监听器的方法:比如setOnClickListener或者OnLongClickListener;
4、具体实现方法绑定的是哪个回调方法。
其次利用反射来实现:
1、拿到需要绑定事件的控件,比如Button;
2、利用动态代理生成事件监听器:比如是OnClickListener还是OnLongClickListener;
3、绑定事件监听器
考虑到框架的扩展性,我们首先设计一个用于指定事件监听器各种信息的注解:
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {/*** 设置监听的方法** @return*/String listenerSetter();/*** 事件类型** @return*/Class<?> listenerType();/*** 回调方法* 事件被触发后,执行回调方法名称** @return*/String callBackMethod();
}
这个注解是用在各种事件注解之上的,比如下面两个事件的注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerSetter = "setOnClickListener",listenerType = View.OnClickListener.class,callBackMethod = "onClick"
)
public @interface OnClick {int[] value() default -1;
}@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerSetter = "setOnLongClickListener",listenerType = View.OnLongClickListener.class,callBackMethod = "onLongClick"
)
public @interface OnLongClick {int[] value() default -1;
}
OnClick这个注解用于绑定点击事件,在这个注解上面用EventBase这个注解指定了以下信息:
1、控件绑定的具体事件,OnClickListener.class;
2、绑定指定事件监听器的方法:setOnClickListener;
3、具体实现方法绑定的是哪个回调方法:onClick。
另外OnClick还有自己的方法value(),用于传递需要绑定点击事件的控件ID。下面我们就来看利用反射绑定控件监听器的代码:
private static void injectEvent(Context context) {Class<?> clazz = context.getClass();//获取Activity里面 所有方法Method[] methods = clazz.getDeclaredMethods();//遍历Activity所有方法for (Method method : methods) {//获取方法上所有的注解Annotation[] annotations = method.getAnnotations();for (Annotation annotation : annotations) {//获取注解 annotationType OnClick OnLongClickClass<?> annotationType = annotation.annotationType();EventBase eventBase = annotationType.getAnnotation(EventBase.class);if (eventBase == null) {continue;}//开始获取事件三要素 通过反射注入进去String listenerSetter = eventBase.listenerSetter();Class<?> listenerType = eventBase.listenerType();String callBackMethod = eventBase.callBackMethod();Map<String, Method> methodMap = new HashMap<>();methodMap.put(callBackMethod, method);try {Method valueMethod = annotationType.getMethod("value");int[] viewIds = (int[]) valueMethod.invoke(annotation);for (int viewId : viewIds) {Method findViewById = clazz.getMethod("findViewById", int.class);View view = (View) findViewById.invoke(context, viewId);if (view == null) {continue;}Method listenerSetterMethod = view.getClass().getMethod(listenerSetter, listenerType);//通过动态代理生成listenerType(例如:OnClickListener)类ListenerInvocationHandler listenerInvocationHandler = new ListenerInvocationHandler(context, methodMap);Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(),new Class[]{listenerType},listenerInvocationHandler);//类比 于 textView.setOnClickListener(new View.OnClickListener)......listenerSetterMethod.invoke(view, proxy);}} catch (NoSuchMethodException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}}}
其中ListenerInvocationHandler的代码如下:
public class ListenerInvocationHandler implements InvocationHandler {private Context mContext;private Map<String, Method> mMethodMap;public ListenerInvocationHandler(Context context, Map<String, Method> methodMap) {mContext = context;mMethodMap = methodMap;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String methodName = method.getName();Method methodProxy = mMethodMap.get(methodName);if (methodProxy != null) {return methodProxy.invoke(mContext, args);} else {return method.invoke(proxy, args);}}
}
需要在Acticity的OnCreate方法中调用以上方法才能实现控件事件的注入。通过以上代码就实现了利用动态代理绑定控件事件监听器需求,这也是XUtils的实现方法。
完整代码