文章目
- 前言
- 一、创建 事件监听器 对应的 动态代理
- 二、动态代理 数据准备
- 三、动态代理 调用处理程序
- 四、动态代理 实例对象创建
前言
Android 依赖注入的核心就是通过反射获取 类 / 方法 / 字段 上的注解 , 以及注解属性 ; 在 Activity 基类中 , 获取该注解 以及 注解属性 , 进行相关操作 ;
在博客 【IOC 控制反转】Android 事件依赖注入 ( 事件三要素 | 修饰注解的注解 | 事件依赖注入步骤 ) 中 , 定义了
个注解 ,
- 第一个是方法上的注解 , 用于修饰方法 ;
- 第二个是修饰注解的注解 , 该注解用于配置注入的方法 ( 事件监听方法 | 监听器类型 | 监听器回调方法 ) ;
事件依赖注入比较复杂 , 涉及到动态代理 , 本博客分析 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入代码示例 ) 事件依赖注入的详细步骤 ;
本博客的核心是 : 使用动态代理 , 创建 View.OnClickListener
或 View.OnLongClickListener
或 View.onTouchListener
等接口的动态代理类 ;
拦截相应的 onClick
, onLongClick
, onTouch
方法 , 执行自己的方法 , 其它方法正常执行 ;
一、创建 事件监听器 对应的 动态代理
为组件设置的监听器可能是 View.OnClickListener
或 View.OnLongClickListener
或 View.onTouchListener
等监听器 , 因此使用 静态代理 , 需要为每个监听器都要设置一个单独的类 , 比较繁琐 ;
这里使用动态代理实现上述功能 ;
动态代理是作用于接口上的 , 根据接口动态创建该接口子类的代理对象 ;
原来是设置了一个匿名内部类 , 这个匿名内部类就是代理模式中的 被代理对象 ;
代码语言:javascript复制 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);
如果被调用的方法 需要被拦截 , 则能获取到被拦截后替换的方法 , 执行该注入的方法即可 ;
代码语言:javascript复制 // 如果被调用的方法 需要被拦截 , 则能获取到被拦截后替换的方法
if (method1 != null) {
// 执行用户 Activity 中的相应方法
return method1.invoke(activity, args);
}
如果不拦截该方法 , 则获取的注入方法为 null
, 直接返回该方法 , 注意调用 method.invoke(proxy, args)
, 正常执行该接口方法即可 ;
// 其它方法正常执行
return method.invoke(proxy, args);
代码示例 :
代码语言:javascript复制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
等字节码类的实例对象使用即可 ;