前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入代码示例 )

【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入代码示例 )

作者头像
韩曙亮
发布2023-03-29 17:27:32
2880
发布2023-03-29 17:27:32
举报
文章被收录于专栏:韩曙亮的移动开发专栏

文章目录

总结

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

一、Android 事件依赖注入示例


1、创建依赖注入库

首先在 Android 应用中 , 创建一个 " Android Library " ,

设置主应用依赖该 Android 依赖库 :

代码语言:javascript
复制
dependencies {
    implementation project(path: ':ioc_lib')
}

2、声明注解

(1)、修饰注解的注解

代码语言:javascript
复制
package kim.hsl.ioc_lib;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解
 *  用于注解上的注解
 *  用于依赖注入视图
 */
@Target(ElementType.ANNOTATION_TYPE)   // 该注解作用于注解上
@Retention(RetentionPolicy.RUNTIME)    // 注解保留到运行时
public @interface EventBase {
    /**
     * 设置事件监听的方法
     * @return
     */
    String listenerSetter();

    /**
     * 设置监听器类型
     * @return
     */
    Class<?> listenerType();

    /**
     * 事件触发后的回调方法
     * @return
     */
    String callbackMethod();
}

(2)、修饰方法的注解

代码语言:javascript
复制
package kim.hsl.ioc_lib;

import android.view.View;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解
 *  用于依赖注入视图
 */
@Target(ElementType.METHOD)   // 该注解作用于方法上
@Retention(RetentionPolicy.RUNTIME)    // 注解保留到运行时
@EventBase(
        listenerSetter = "setOnClickListener",
        listenerType = View.OnClickListener.class,
        callbackMethod = "onClick")
public @interface OnClick {
    int[] value();    // 接收 int 类型数组
}

@Target(ElementType.METHOD) 表示该注解作用于方法上 ;

@Retention(RetentionPolicy.RUNTIME) 注解保留到运行时 , Java 源码时期 RetentionPolicy.SOURCE -> Class 字节码时期 RetentionPolicy.CLASS -> JVM 运行时时期 RetentionPolicy.RUNTIME ;

int value() 表示该注解接收一个 int 类型的值 ;

3、Activity 基类

代码语言:javascript
复制
package kim.hsl.ioc_lib;

import android.app.Activity;
import android.os.Bundle;

import androidx.annotation.Nullable;

public class BaseActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 在此处注入布局
        //      此处传入的 Activity 参数是 MainActivity 子类对象
        InjectUtils.inject(this);
    }
}

4、动态代理类调用处理程序

代码语言: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);
    }
}

5、依赖注入工具类

将上一篇博客 【IOC 控制反转】Android 布局依赖注入 ( 布局依赖注入步骤 | 布局依赖注入代码示例 ) 中的布局注入 , 抽到 injectLayout 方法中 ; 将注入视图组件定义在 injectViews 方法中 ;

代码语言:javascript
复制
package kim.hsl.ioc_lib;

import android.app.Activity;
import android.icu.lang.UProperty;
import android.view.View;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class InjectUtils {
    /**
     * 为 Activity 注入布局
     * @param activity  该 Activity 是继承了 BaseActivity 的 MainActivity 实例对象
     */
    public static void inject(Activity activity) {
        // 注入布局文件
        injectLayout(activity);

        // 注入视图组件
        injectViews(activity);

        // 注入事件
        injectEvents(activity);
    }

    /**
     * 注入布局文件
     */
    private static void injectLayout(Activity activity) {
        // 获取 Class 字节码对象
        Class<? extends Activity> clazz = activity.getClass();
        // 反射获取类上的注解
        ContentView contentView = clazz.getAnnotation(ContentView.class);
        // 获取注解中的布局 ID
        int layoutId = contentView.value();
        // 为 Activity 设置显示的布局
        activity.setContentView(layoutId);
    }

    /**
     * 注入视图组件
     */
    private static void injectViews(Activity activity) {
        // 获取 Class 字节码对象
        Class<? extends Activity> clazz = activity.getClass();
        // 获取类的属性字段
        Field[] fields = clazz.getDeclaredFields();

        // 循环遍历类的属性字段
        for (int i = 0; i < fields.length; i ++) {
            // 获取字段
            Field field = fields[i];
            // 属性有可能是私有的, 这里设置可访问性
            field.setAccessible(true);
            // 获取字段上的注解, @BindView 注解
            //  注意不是所有的属性字段都有 @BindView 注解
            BindView bindView = field.getAnnotation(BindView.class);
            if (bindView == null) {
                // 如果没有获取到 BindView 注解 , 执行下一次循环
                continue;
            }
            // 获取注入的视图组件
            int viewId = bindView.value();
            // 根据组件 id 获取对应组件对象
            View view = activity.findViewById(viewId);
            try {
                // 通过反射设置 Activity 的对应属性字段的值
                field.set(activity, view);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 注入事件
     */
    private static void injectEvents(Activity activity) {
        // 获取 Class 字节码对象
        Class<? extends Activity> clazz = activity.getClass();
        // 获取所有方法
        Method[] methods = clazz.getDeclaredMethods();

        // 循环遍历类的方法
        for (int i = 0; i < methods.length; i ++) {
            // 获取方法的所有注解
            Annotation[] annotations = methods[i].getDeclaredAnnotations();

            // 遍历所有的注解
            for (int j = 0; j < annotations.length; j ++) {
                // 获取注解类型
                Class<? extends Annotation> annotationType = annotations[j].annotationType();
                // 获取 @EventBase 注解
                EventBase eventBase = annotationType.getAnnotation(EventBase.class);
                if (eventBase == null) {
                    // 如果没有获取到 EventBase 注解 , 执行下一次循环
                    continue;
                }

                // 如果获取到了 EventBase 注解 , 则开始获取事件注入的三要素
                /*
                通过反射执行下面的方法
                textView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {

                    }
                });
                 */
                // 点击事件 View.setOnClickListener
                String listenerSetter = eventBase.listenerSetter();
                // 监听器类型 View.OnClickListener
                Class<?> listenerType = eventBase.listenerType();
                // 事件触发回调方法 public void onClick(View v)
                String callbackMethod = eventBase.callbackMethod();

                // 拦截 callbackMethod 方法 , 执行 method[i] 方法
                //      这个 method[i] 方法就是在 MainActivity 中用户自定义方法
                //      被 OnClick 注解修饰的方法
                //      将其封装到 Map 集合中
                Map<String, Method> methodMap = new HashMap<>();
                methodMap.put(callbackMethod, methods[i]);

                // 通过反射注入事件 , 设置组件的点击方法

                // 通过反射获取注解中的属性
                //      int[] value(); // 接收 int 类型数组
                try {
                    // 通过反射获取 OnClick 注解的 int[] value() 方法
                    Method valueMethod = annotationType.getDeclaredMethod("value");
                    // 调用 value() 方法 , 获取视图组件 ID 数组
                    int[] viewIds = (int[]) valueMethod.invoke(annotations[j]);

                    // 遍历 ID 数组
                    for (int k = 0; k < viewIds.length; k ++) {
                        // 获取组件实例对象
                        View view = activity.findViewById(viewIds[k]);
                        if (view == null) {
                            continue;
                        }

                        // 获取 View 视图组件的 listenerSetter 对应方法
                        //      这里是 View.setOnClickListener
                        //      参数一是方法名称 , 参数二是方法参数类型
                        Method listenerSetterMethod =
                                view.getClass().getMethod(listenerSetter, listenerType);

                        // 获取监听器 View.OnClickListener 接口的代理对象
                        EventInvocationHandler eventInvocationHandler =
                                new EventInvocationHandler(activity, methodMap);
                        Object proxy = Proxy.newProxyInstance(
                                listenerType.getClassLoader(),  // 类加载器
                                new Class<?>[]{listenerType},   // 接口数组
                                eventInvocationHandler);        // 调用处理程序

                        // 执行 View 的 setOnClickListener 方法, 为其设置点击事件
                        listenerSetterMethod.invoke(view, proxy);
                    }

                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }

            }
        }

    }
}

6、客户端 Activity

代码语言:javascript
复制
package kim.hsl.ioc_demo;

import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import kim.hsl.ioc_lib.BaseActivity;
import kim.hsl.ioc_lib.BindView;
import kim.hsl.ioc_lib.ContentView;
import kim.hsl.ioc_lib.OnClick;

/**
 * 当该 MainActivity 启动时 , 调用 BaseActivity 的 onCreate 方法
 *      在 BaseActivity 的 onCreate 方法中注入布局
 */
@ContentView(R.layout.activity_main)    // 布局注入
public class MainActivity extends BaseActivity {

    /**
     * 视图注入
     */
    @BindView(R.id.textView)
    private TextView textView;

    @Override
    protected void onResume() {
        super.onResume();
        // 验证 textView 是否注入成功
        Log.i("MainActivity", "textView : " + textView);
    }

    @OnClick({R.id.textView})   // 事件注入
    public void onClick(View view) {
        Toast.makeText(this, "点击 TextView 组件", Toast.LENGTH_LONG).show();
    }
}

运行结果 :

Logcat 打印结果 :

代码语言:javascript
复制
2021-09-22 08:25:31.759 29044-29044/kim.hsl.ioc_demo I/MainActivity: textView : android.widget.TextView{a988249 VFED..C.. ........ 440,840-640,891 #7f08017e app:id/textView}

二、博客源码

GitHub : https://github.com/han1202012/IOC_Demo

CSDN : https://download.csdn.net/download/han1202012/24031128

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-09-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 总结
  • 一、Android 事件依赖注入示例
    • 1、创建依赖注入库
      • 2、声明注解
        • (1)、修饰注解的注解
        • (2)、修饰方法的注解
      • 3、Activity 基类
        • 4、动态代理类调用处理程序
          • 5、依赖注入工具类
            • 6、客户端 Activity
            • 二、博客源码
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档