【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 创建 事件监听器 对应的 动态代理 | 动态代理的数据准备 | 创建调用处理程序 | 创建动态代理实例对象 )

2023-03-29 17:28:24 浏览数 (2)

文章目

  • 前言
  • 一、创建 事件监听器 对应的 动态代理
  • 二、动态代理 数据准备
  • 三、动态代理 调用处理程序
  • 四、动态代理 实例对象创建

前言

Android 依赖注入的核心就是通过反射获取 类 / 方法 / 字段 上的注解 , 以及注解属性 ; 在 Activity 基类中 , 获取该注解 以及 注解属性 , 进行相关操作 ;

在博客 【IOC 控制反转】Android 事件依赖注入 ( 事件三要素 | 修饰注解的注解 | 事件依赖注入步骤 ) 中 , 定义了

2

个注解 ,

  • 第一个是方法上的注解 , 用于修饰方法 ;
  • 第二个是修饰注解的注解 , 该注解用于配置注入的方法 ( 事件监听方法 | 监听器类型 | 监听器回调方法 ) ;

事件依赖注入比较复杂 , 涉及到动态代理 , 本博客分析 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入代码示例 ) 事件依赖注入的详细步骤 ;

本博客的核心是 : 使用动态代理 , 创建 View.OnClickListener View.OnLongClickListener View.onTouchListener 等接口的动态代理类 ;

拦截相应的 onClick , onLongClick , onTouch 方法 , 执行自己的方法 , 其它方法正常执行 ;

一、创建 事件监听器 对应的 动态代理


为组件设置的监听器可能是 View.OnClickListenerView.OnLongClickListenerView.onTouchListener 等监听器 , 因此使用 静态代理 , 需要为每个监听器都要设置一个单独的类 , 比较繁琐 ;

这里使用动态代理实现上述功能 ;

动态代理是作用于接口上的 , 根据接口动态创建该接口子类的代理对象 ;

原来是设置了一个匿名内部类 , 这个匿名内部类就是代理模式中的 被代理对象 ;

代码语言:javascript复制
                textView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {

                    }
                });

现在使用 动态代理 , 创建一个 代理对象 , 代理 上述 匿名内部类 被代理对象 , 要在调用 onClick 方法时 , 注入自己的业务逻辑 ;

该动态代理中的元素梳理 :

  • 目标对象 ( 主题对象 ) : View.OnClickListener 接口 ;
  • 被代理对象 : View.OnClickListener 接口匿名内部类 ;
代码语言:javascript复制
				new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {

                    }
                }
  • 代理对象 : 使用 Proxy.newProxyInstance 方法 , 由 JVM 自动生成字节码类 就是代理对象 , 之后返回一个代理对象 的实例对象 ;
  • 客户端 : 框架开发者开发的 依赖注入 工具类 , 在该工具类中执行动态代理的调用操作 ;

二、动态代理 数据准备


执行动态代理前 , 首先要知道拦截接口方法 , 以及要注入的方法 ;

拦截到接口方法后 , 替换成自己注入的方法 , 就是调用自己的方法 ;

将二者封装到 Map 集合中 , 方便在拦截后 , 调用 Mapget 方法 , 查看是否有要注入的方法 ;

代码语言:javascript复制
                // 拦截 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() 方法获取接口名称 ;

代码语言:javascript复制
        // 获取回调的方法名称, 该方法是 onClick 或者 onLongClick 或者 onTouch 等方法
        String name = method.getName();

然后到 Map 集合中查找 , 是否要拦截该 接口方法 , 如果要拦截 , 肯定能从 Map 集合中获取到要注入的方法 , 如果不需要拦截 , 获取的结果是 null ;

代码语言:javascript复制
        // 获取对应的被调用方法
        Method method1 = methodMap.get(name);

如果被调用的方法 需要被拦截 , 则能获取到被拦截后替换的方法 , 执行该注入的方法即可 ;

代码语言:javascript复制
        // 如果被调用的方法 需要被拦截 , 则能获取到被拦截后替换的方法
        if (method1 != null) {
            // 执行用户 Activity 中的相应方法
            return method1.invoke(activity, args);
        }

如果不拦截该方法 , 则获取的注入方法为 null , 直接返回该方法 , 注意调用 method.invoke(proxy, args) , 正常执行该接口方法即可 ;

代码语言:javascript复制
        // 其它方法正常执行
        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.classView.OnLongClickListener.classView.OnTouchListener.class 等字节码类 ;

在调用处理程序中 , 拦截上述接口中的方法 , 并替换成自己的方法 , 也就是用户在 MainActivity 中使用 @OnClick 注解修饰的方法 ;

代码语言:javascript复制
                        // 获取监听器 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 等字节码类的实例对象使用即可 ;

0 人点赞