IOC是Inversion of Control(控制反转)的缩写,是一种设计模式,其核心思想是将对象的控制权转移给容器或框架,而不是在代码中直接实例化和管理对象。在这种模式中,对象的创建和组装由容器完成,而应用程序只需要定义对象需要的属性和依赖关系,容器会自动完成对象的创建和依赖注入。
在本文中,我们将介绍一个简单的IOC框架,它基于注解,可以实现视图的自动绑定和事件的自动绑定。该框架的核心代码如下:
一、@onclick注入
public class ViewUtils {
public static final int NETWORK_NULL = 0;
public static final int NETWORK_WIFI = 1;
public static final int NETWORK_MOBILE_DATA = 2;
public static final int NETWORK_UNKOWN = 3;
/**
* 注入Activity
*/
public static void inject(Activity activity) {
inject(new ViewFinder(activity), activity);
}
/**
* 注入View
*/
public static void inject(View view) {
inject(new ViewFinder(view), view);
}
/**
* 注入任意对象
*/
public static void inject(View view, Object object) {
inject(new ViewFinder(view), object);
}
/**
* 兼容上面三个方法
*
* @param finder
* @param object 反射执行的类
*/
private static void inject(ViewFinder finder, Object object) {
injectFiled(finder, object);
injectEvent(finder, object);
}
/**
* 事件的注入
*
* @param finder
* @param object
*/
private static void injectEvent(ViewFinder finder, Object object) {
//1.获取类里面所有方法
Class<?> clazz = object.getClass();
Method[] methods = clazz.getDeclaredMethods();
//2.获取OnClick的value值
for (Method method : methods) {
OnClick onClick = method.getAnnotation(OnClick.class);
if (onClick != null) {
int[] viewIds = onClick.value();
for (int viewId : viewIds) {
//3.findViewById找到View
View view = finder.findViewById(viewId);
//拓展功能,比如检查网络
boolean isCheckNet = method.getAnnotation(CheckNet.class) != null;
if (view != null) {
//4.view.setOnClickListener
view.setOnClickListener(new DeclaredOnClickListener(method, object, isCheckNet));
}
}
}
}
}
}
以上这段代码是一个事件注入的实现,主要用于将被注解的方法绑定到指定的 View 上,实现点击事件的响应。具体的实现过程如下:
-
获取传入对象的 Class 对象和其声明的所有方法。
-
遍历方法数组,获取被 @OnClick 注解的方法,并获取其注解中的 value 值,即指定的 View ID 数组。
-
遍历 View ID 数组,通过 ViewFinder 找到对应的 View。
-
将 View 和被注解的方法绑定,生成一个 OnClickListener 并设置给 View。
其中,为了实现拓展功能,还对注解中的 CheckNet 进行了判断,如果该注解存在,则生成的 OnClickListener 中会对网络进行检查。
二、findViewById注入
/**
* findViewById注入
*
* @param finder
* @param object
*/
private static void injectFiled(ViewFinder finder, Object object) {
//1.获取类里面所有属性
Class<?> clazz = object.getClass();
Field[] declaredFields = clazz.getDeclaredFields();
//2.获取ViewById里面的value值
for (Field declaredField : declaredFields) {
ViewById viewById = declaredField.getAnnotation(ViewById.class);
if (viewById != null) {
//获取注解里面的id值
int viewId = viewById.value();
//3.findViewById找到View
View view = finder.findViewById(viewId);
if (view != null) {
//能够注入所有修饰符
declaredField.setAccessible(true);
//4.动态的注入找到View
try {
//设置属性,将declaredField的属性值设置为通过findViewById找到的view。
declaredField.set(object, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
以上这段代码实现的是通过注解方式实现findViewById的功能,将xml中定义的控件和Java代码中定义的属性绑定在一起,以便在Java代码中操作这些控件。其实现步骤如下:
- 通过反射获取目标类中所有的属性(Field);
- 遍历每个属性,如果该属性使用了ViewById注解,则获取注解中的id值;
- 调用ViewFinder中的findViewById方法根据id值找到View;
- 将查找到的View设置给该属性。
需要注意的是,由于该属性有可能被定义为private等访问修饰符,因此在将查找到的View设置给属性时,需要先调用setAccessible方法设置访问权限。
三、@CheckNet注入
private static class DeclaredOnClickListener implements View.OnClickListener {
private Object mObject;
private Method mMethod;
private boolean mIsCheckNet;
public DeclaredOnClickListener(Method method, Object object, boolean isCheckNet) {
this.mMethod = method;
this.mObject = object;
this.mIsCheckNet = isCheckNet;
}
@Override
public void onClick(View v) {
//是否需要检测网络
if (mIsCheckNet) {
int network_state = networkAvailable(v.getContext());
if (network_state == NETWORK_WIFI) {
Toast.makeText(v.getContext(), "当前已连接WIFI", Toast.LENGTH_SHORT).show();
return;
} else if (network_state == NETWORK_MOBILE_DATA) {
//打印Toast
Toast.makeText(v.getContext(), "请注意流量消耗", Toast.LENGTH_SHORT).show();
return;
} else if(network_state == NETWORK_NULL){
//打印Toast
Toast.makeText(v.getContext(), "网络不太给力", Toast.LENGTH_SHORT).show();
return;
}
}
try {
mMethod.setAccessible(true);
//5.反射执行方法
mMethod.invoke(mObject, v);
} catch (Exception e) {
e.printStackTrace();
try {
mMethod.invoke(mObject);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
以上这段代码定义了一个私有静态内部类 DeclaredOnClickListener
实现了 View.OnClickListener
接口,并在 injectEvent
方法中使用。这个类的主要作用是在响应点击事件时调用对应的方法,同时支持网络检测的功能。
这个类的构造方法接收三个参数:方法、对象、是否需要检测网络。当 View 被点击时,onClick
方法会被调用,首先会根据 mIsCheckNet
参数判断是否需要进行网络检测。如果需要,会调用 networkAvailable
方法检测网络状态,并根据结果打印相应的 Toast 提示。如果不需要网络检测或者网络状态良好,就会通过反射调用传入的方法 mMethod
,执行响应的点击事件逻辑。
这个类的实现相对较为简单,但使用起来非常灵活,可以通过注解的方式自由添加和删除方法的点击事件,并在必要时添加网络检测的功能。
四、网络状态判断方法
private static int networkAvailable(Context context) {
try {
//得到连接管理器对象
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Network network = connectivityManager.getActiveNetwork();
NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
if (capabilities != null) {
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
// WiFi is active
return NETWORK_WIFI;
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
// Mobile data is active
return NETWORK_MOBILE_DATA;
}
} else {
// No active network
return NETWORK_NULL;
}
} else {
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
if (activeNetworkInfo != null && activeNetworkInfo.isConnected()) {
int type = activeNetworkInfo.getType();
if (type == ConnectivityManager.TYPE_WIFI) {
// 当前网络是 WiFi
return NETWORK_WIFI;
} else if (type == ConnectivityManager.TYPE_MOBILE) {
// 当前网络是移动数据网络
return NETWORK_MOBILE_DATA;
}
} else {
// 没有活动的网络连接
return NETWORK_NULL;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return NETWORK_UNKOWN;
}
这段代码实现了一个判断当前网络状态的方法,返回值为整型常量,表示当前网络状态,常量值如下:
- NETWORK_WIFI:当前网络连接为 WiFi。
- NETWORK_MOBILE_DATA:当前网络连接为移动数据网络。
- NETWORK_NULL:当前没有可用的网络连接。
- NETWORK_UNKOWN:当前网络状态未知。
在方法中首先获取连接管理器对象 ConnectivityManager,然后判断 Android 版本是否大于等于 23(Build.VERSION_CODES.M),如果是则使用新 API 获取网络连接状态,否则使用旧的方法。如果当前有活动的网络连接,则判断当前网络类型为 WiFi 还是移动数据网络,最后根据判断结果返回对应的常量值。如果没有可用的网络连接,则返回 NETWORK_NULL。如果出现异常,则返回 NETWORK_UNKOWN。
五、使用方法
以上就是一个简单的IOC框架的实现代码,下面我们来详细介绍一下它的实现原理以及使用方法。
IOC(Inversion of Control)控制反转,是一种设计模式,它是指在应用程序开发中,将对象的创建、依赖关系的管理交给框架或容器来实现,而不是由应用程序自己来完成。IOC的优点是降低了代码之间的耦合度,提高了代码的可维护性和可扩展性。
ViewUtils类中的inject方法就是一个典型的IOC实现,它的作用是自动注入控件和事件。在使用该方法之前,我们需要在控件上添加ViewById和OnClick注解,注解中包含了控件的ID和点击事件的方法名。ViewUtils在运行时利用反射机制获取到这些注解,然后自动注入控件和事件。
ViewUtils的注入方法非常灵活,它可以注入Activity、View以及任意Java对象中的控件和事件。在注入控件时,ViewUtils会通过反射机制获取到该对象的所有属性,并获取到对应的控件ID,然后使用findViewById方法找到对应的控件,并通过反射机制动态的将控件赋值给对应的属性。
在注入事件时,ViewUtils会通过反射机制获取到该对象的所有方法,并获取到被OnClick注解修饰的方法名,然后通过反射机制动态的为对应的控件设置OnClickListener事件。
ViewUtils还支持扩展功能,比如在事件执行前检测网络状态。在ViewUtils中,我们可以为需要检测网络状态的方法添加CheckNet注解,当点击对应的控件时,ViewUtils会首先检测网络状态,然后再执行对应的方法。通过这种方式,我们可以非常方便地为应用程序添加网络状态检测功能。
使用ViewUtils非常简单,我们只需要在需要注入控件或事件的对象中调用对应的inject方法即可。例如,在Activity的onCreate方法中调用ViewUtils.inject(this)方法,即可实现自动注入控件和事件。使用代码如下:
//findViewById注入
@ViewById(R.id.img_collect)
private ImageView imgCollect;
//点击事件注入
@OnClick(R.id.img_card)
//检查网络状态
@CheckNet
private void go2Card() {
Intent intent = new Intent(this, CardActivity.class);
intent.putExtra("num", num);
startActivityForResult(intent, MyTag.CARD);
}
完整代码见:ioc: Android实现注解框架