文章目录
前言
一、创建 事件监听器 对应的 动态代理
二、动态代理 数据准备
三、动态代理 调用处理程序
四、动态代理 实例对象创建
前言
Android 依赖注入的核心就是通过反射获取 类 / 方法 / 字段 上的注解 , 以及注解属性 ; 在 Activity 基类中 , 获取该注解 以及 注解属性 , 进行相关操作 ;
在博客 【IOC 控制反转】Android 事件依赖注入 ( 事件三要素 | 修饰注解的注解 | 事件依赖注入步骤 ) 中 , 定义了 2 22 个注解 ,
第一个是方法上的注解 , 用于修饰方法 ;
第二个是修饰注解的注解 , 该注解用于配置注入的方法 ( 事件监听方法 | 监听器类型 | 监听器回调方法 ) ;
事件依赖注入比较复杂 , 涉及到动态代理 , 本博客分析 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入代码示例 ) 事件依赖注入的详细步骤 ;
本博客的核心是 : 使用动态代理 , 创建 View.OnClickListener 或 View.OnLongClickListener 或 View.onTouchListener 等接口的动态代理类 ;
拦截相应的 onClick , onLongClick , onTouch 方法 , 执行自己的方法 , 其它方法正常执行 ;
一、创建 事件监听器 对应的 动态代理
为组件设置的监听器可能是 View.OnClickListener 或 View.OnLongClickListener 或 View.onTouchListener 等监听器 , 因此使用 静态代理 , 需要为每个监听器都要设置一个单独的类 , 比较繁琐 ;
这里使用动态代理实现上述功能 ;
动态代理是作用于接口上的 , 根据接口动态创建该接口子类的代理对象 ;
原来是设置了一个匿名内部类 , 这个匿名内部类就是代理模式中的 被代理对象 ;
textView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { } });
现在使用 动态代理 , 创建一个 代理对象 , 代理 上述 匿名内部类 被代理对象 , 要在调用 onClick 方法时 , 注入自己的业务逻辑 ;
该动态代理中的元素梳理 :
目标对象 ( 主题对象 ) : View.OnClickListener 接口 ;
被代理对象 : View.OnClickListener 接口匿名内部类 ;
new View.OnClickListener() { @Override public void onClick(View v) { } }
代理对象 : 使用 Proxy.newProxyInstance 方法 , 由 JVM 自动生成字节码类 就是代理对象 , 之后返回一个代理对象 的实例对象 ;
客户端 : 框架开发者开发的 依赖注入 工具类 , 在该工具类中执行动态代理的调用操作 ;
二、动态代理 数据准备
执行动态代理前 , 首先要知道拦截接口方法 , 以及要注入的方法 ;
拦截到接口方法后 , 替换成自己注入的方法 , 就是调用自己的方法 ;
将二者封装到 Map 集合中 , 方便在拦截后 , 调用 Map 的 get 方法 , 查看是否有要注入的方法 ;
// 拦截 callbackMethod 方法 , 执行 method[i] 方法 // 这个 method[i] 方法就是在 MainActivity 中用户自定义方法 // 被 OnClick 注解修饰的方法 // 将其封装到 Map 集合中 Map<String, Method> methodMap = new HashMap<>(); methodMap.put(callbackMethod, methods[i]);
三、动态代理 调用处理程序
在该动态代理中 , 首先要注入 Activity 和 上面准备的 Map 集合 , Map 集合中封装了 要拦截的接口方法 和 要注入的方法 ;
首先获取被代理接口中的 回调的方法名称, 该方法是 onClick 或者 onLongClick 或者 onTouch 等方法 ;
Method 方法在参数中有 , 直接调用 Method method 参数的 getName() 方法获取接口名称 ;
// 获取回调的方法名称, 该方法是 onClick 或者 onLongClick 或者 onTouch 等方法 String name = method.getName();
然后到 Map 集合中查找 , 是否要拦截该 接口方法 , 如果要拦截 , 肯定能从 Map 集合中获取到要注入的方法 , 如果不需要拦截 , 获取的结果是 null ;
// 获取对应的被调用方法 Method method1 = methodMap.get(name);
如果被调用的方法 需要被拦截 , 则能获取到被拦截后替换的方法 , 执行该注入的方法即可 ;
// 如果被调用的方法 需要被拦截 , 则能获取到被拦截后替换的方法 if (method1 != null) { // 执行用户 Activity 中的相应方法 return method1.invoke(activity, args); }
如果不拦截该方法 , 则获取的注入方法为 null , 直接返回该方法 , 注意调用 method.invoke(proxy, args) , 正常执行该接口方法即可 ;
// 其它方法正常执行 return method.invoke(proxy, args);
代码示例 :
package kim.hsl.ioc_lib; import android.app.Activity; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Map; public class EventInvocationHandler implements InvocationHandler { /** * 客户端 Activity */ private Activity activity; /** * 拦截 callbackMethod 方法 , 执行 method[i] 方法 * 这个 method[i] 方法就是在 MainActivity 中用户自定义方法 * 被 OnClick 注解修饰的方法 * 将其封装到 Map 集合中 */ private Map<String, Method> methodMap; public EventInvocationHandler(Activity activity, Map<String, Method> methodMap) { this.activity = activity; this.methodMap = methodMap; } /** * 拦截方法 , 并使用自己的方法替换 * 如 : 发现是 onClick 方法 , 则替换成用户自定义的方法 (被 @OnClick 注解修饰的方法) * @param proxy * @param method * @param args * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 获取回调的方法名称, 该方法是 onClick 或者 onLongClick 或者 onTouch 等方法 String name = method.getName(); // 获取对应的被调用方法 Method method1 = methodMap.get(name); // 如果被调用的方法 需要被拦截 , 则能获取到被拦截后替换的方法 if (method1 != null) { // 执行用户 Activity 中的相应方法 return method1.invoke(activity, args); } // 其它方法正常执行 return method.invoke(proxy, args); } }
四、动态代理 实例对象创建
调用 Proxy.newProxyInstance 方法 , 创建动态代理的 实例对象 , 传入到代理的接口数组 , 这个接口数组元素可以是 View.OnClickListener.class 或 View.OnLongClickListener.class 或 View.OnTouchListener.class 等字节码类 ;
在调用处理程序中 , 拦截上述接口中的方法 , 并替换成自己的方法 , 也就是用户在 MainActivity 中使用 @OnClick 注解修饰的方法 ;
// 获取监听器 View.OnClickListener 接口的代理对象 EventInvocationHandler eventInvocationHandler = new EventInvocationHandler(activity, methodMap); Object proxy = Proxy.newProxyInstance( listenerType.getClassLoader(), // 类加载器 new Class<?>[]{listenerType}, // 接口数组 eventInvocationHandler); // 调用处理程序
该动态代理实例对象创建后 , 将其当做 View.OnClickListener.class 或 View.OnLongClickListener.class 或 View.OnTouchListener.class 等字节码类的实例对象使用即可 ;