文章目录


总结

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






一、Android 事件依赖注入示例




1、创建依赖注入库



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

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

设置主应用依赖该 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 {
@Override
protected 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
*/
@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​​ 方法中 ;

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



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();
}
}

运行结果 :

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

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}

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






二、博客源码



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