当前位置: 代码迷 >> 综合 >> 移动架构学习笔记十二:代理模式(Proxy )续
  详细解决方案

移动架构学习笔记十二:代理模式(Proxy )续

热度:45   发布时间:2024-02-12 13:58:18.0
  • 前言

前段时间学习了代理模式,并写了一篇笔记:移动架构学习笔记九:代理模式(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的实现方法。

完整代码

 

  相关解决方案