文章目录

  • 总结
  • 一、Android 事件依赖注入示例
    • 1、创建依赖注入库
    • 2、声明注解
      • (1)、修饰注解的注解
      • (2)、修饰方法的注解
    • 3、Activity 基类
    • 4、动态代理类调用处理程序
    • 5、依赖注入工具类
    • 6、客户端 Activity
  • 二、博客源码

总结

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

一、Android 事件依赖注入示例


1、创建依赖注入库

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

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

dependencies {implementation project(path: ':ioc_lib')
}

2、声明注解

(1)、修饰注解的注解

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)、修饰方法的注解

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 基类

package kim.hsl.ioc_lib;import android.app.Activity;
import android.os.Bundle;import androidx.annotation.Nullable;public class BaseActivity extends Activity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 在此处注入布局//      此处传入的 Activity 参数是 MainActivity 子类对象InjectUtils.inject(this);}
}

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

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*/@Overridepublic 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 方法中 ;

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);// 获取注解中的布局 IDint 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() {@Overridepublic void onClick(View v) {}});*/// 点击事件 View.setOnClickListenerString listenerSetter = eventBase.listenerSetter();// 监听器类型 View.OnClickListenerClass<?> 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

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;@Overrideprotected 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 打印结果 :

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

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

  1. Spring→简介核心作用范围、框架、接口编程、IOC控制反转、单元测试、Bean容器、注入、作用域、生命周期、自动装配注入、自动扫描@注解

    官网 https://spring.io/ 文档 https://spring.io/projects/spring-framework Spring框架 框架 接口 IOC控制反转 单元测试 Bea ...

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

    文章目录 前言 一.创建 事件监听器 对应的 动态代理 二.动态代理 数据准备 三.动态代理 调用处理程序 四.动态代理 实例对象创建 前言 Android 依赖注入的核心就是通过反射获取 类 / 方 ...

  3. 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 获取要注入事件的 View 对象 | 通过反射获取 View 组件的事件设置方法 )

    文章目录 前言 一.获取要注入事件的 View 对象 二.通过反射获取 View 组件的事件设置方法并执行 前言 Android 依赖注入的核心就是通过反射获取 类 / 方法 / 字段 上的注解 , ...

  4. 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 获取 Activity 中的所有方法 | 获取方法上的注解 | 获取注解上的注解 | 通过注解属性获取事件信息 )

    文章目录 前言 一.获取 Activity 中的所有方法 二.获取方法上的注解 三.获取注解上的注解 四.通过注解属性获取相关事件信息 前言 Android 依赖注入的核心就是通过反射获取 类 / 方 ...

  5. 【IOC 控制反转】Android 视图依赖注入 ( 视图依赖注入步骤 | 视图依赖注入代码示例 )

    文章目录 总结 一.Android 视图依赖注入步骤 二.Android 布局依赖注入示例 1.创建依赖注入库 2.声明注解 3.Activity 基类 4.依赖注入工具类 5.客户端 Activit ...

  6. 【IOC 控制反转】Android 布局依赖注入 ( 布局依赖注入步骤 | 布局依赖注入代码示例 )

    文章目录 总结 一.Android 布局依赖注入步骤 二.Android 布局依赖注入示例 1.创建依赖注入库 2.声明注解 3.Activity 基类 4.依赖注入工具类 5.客户端 Activit ...

  7. 【IOC 控制反转】IOC 简介 ( 依赖注入的两种实现方式 | 编译期注入 | 运行期注入 )

    文章目录 一.IOC 简介 二.依赖注入的两种实现方式 一.IOC 简介 IOC 是 Inversion Of Control 的缩写 , 控制反转 ; 其最主要的作用是 降低代码的耦合度 , 最常见 ...

  8. IOC控制反转(依赖注入思想)

    IOC控制反转(依赖注入思想) 获取map 类型的 request session application 的方法: 实现RequestAware,SessionAware,ApplicationAw ...

  9. 【Java从0到架构师】Spring - IoC 控制反转、DI 依赖注入

    IoC 控制反转.DI 依赖注入 Spring 简介 Spring 基本使用 - IoC 容器 依赖注入 (Dependency Injection) 基于 setter 的注入 - 自定义对象 be ...

最新文章

  1. 机器学习中的梯度下降( Gradient Descent)算法
  2. java future用法_Java中的多线程知识点
  3. 影像组学视频学习笔记(43)-标准差、标准误及95%置信区间CI、Li‘s have a solution and plan.
  4. Oracle归档日志删除
  5. Dijkstra算法.
  6. 利用STM32 的串口来发送和接收数据实验
  7. 【OpenYurt 深度解析】边缘网关缓存能力的优雅实现
  8. A - Greed CodeForces - 892A(水题)
  9. Topshelf创建Windows服务
  10. linux文本文件和win文本文件的格式互换
  11. HDU - 2122 Ice_cream’s world III
  12. 在Java中安全地将long转换为int
  13. visual studio 2013连接Oracle 11g并获取数据:(一:环境搭建)
  14. Julia: reduce 、mapreduce、filter
  15. twisted系列教程二–缓慢的诗
  16. 10个比较不错的 JavaScript 库
  17. Spring 学习记录6 BeanFactory(2)
  18. 文档大小超出上传限制怎么办_有道翻译和翻译狗,哪个更适合翻译文档?
  19. Kubernetes快速部署
  20. java 时间类 joda_Java Joda-Time 处理时间工具类(JDK1.7以上)

热门文章

  1. CSS兼容性(IE和Firefox)技巧大全
  2. 5.1 指针与地址 (C)
  3. 故障排除:硬盘坏道修复术
  4. 爬取百度网盘资源报user is not authorized, hitcode:119
  5. windowSoftInputMode属性详解
  6. U盘安装Linux CentOS 6.5 64位操作系统(来自互联网)
  7. sql语句转linq的一个小工具
  8. 一步步构建多层架构系列二之设计模式运用篇
  9. 使用apply调用函数
  10. monkeyrunner环境配置