什么是IOC?
官方定义:IOC是原来由程序中主动获取的资源,转变为由第三方提供,并有原来的程序被动接受的方式,已达到解耦的效果.
当然,这种以专业名词解释专业名词的方式,看了还是一头雾水.
直白点说:Inversion of control ,直接翻译过来就是控制翻转,这个理论主要用于实现对象之间的解耦.
也叫依赖注入,就是IOC容器在运行过程中,动态的将某种依赖关系注入到对象之中.
比如说对象A依赖对象B,通常我们使用new关键字来实现两个对象之间组合,这样耦合性就很高,引入IOC之后,当A对象需要B对象时,IOC容器就会创建一个对象B送给对象A,而A对象不需要去关心B对象是怎么创建的,该怎么销毁,这些都由IOC去完成.
IOC最核心的技术就是反射.
Android中有不少应用了IOC技术的框架,如xUtils,EventBus.
这里不是介绍那些框架怎么使用, 而是以一种简单的方式,来理解这些的框架的思想.理解了它的思想,不管是去使用,还是去开发新的框架,都会更容易打开思路.
下面结合代码,来看运行时注入,在android中是怎么使用的.
首先,看View的注入,也包括layout,contentView的注入,代码中加了注释,应该很容易看明白,
//定义一个类似IOC容器的类,这个类通常是一个基类中使用,比如在BaseActivity中使用,
// 那么所以继承自BaseActivity的类都具有了这个功能
//这个工具类中实现了,怎样去注入一个view,及注入布局资源。
public class IOCInjectUtils {
public static void inject(Object context) {
injectLayout(context);
injectView(context);
}
private static void injectLayout(Object context) {
//通过反射去调用MainActivity的setContentView。
int layoutId = 0;
Class<?> mainActivityClass = context.getClass();
//通过MainActivity上的注解,拿到布局id
InjectContentView injectContentView = mainActivityClass.getAnnotation(InjectContentView.class);
if (null != injectContentView) {
layoutId = injectContentView.value();
//反射调用setContentView
try {
Method method = mainActivityClass.getMethod("setContentView", int.class);
method.invoke(context, layoutId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static void injectView(Object context) {
Class<?> mainActivityClass = context.getClass();
//拿到MainActivity中所有的属性变量
Field[] fields = mainActivityClass.getDeclaredFields();
for (Field field : fields) {
//找到被InjectViewComponent注解过的组件
InjecViewComponent injecViewComponent = field.getAnnotation(InjecViewComponent.class);
if (null != injecViewComponent) {
int componentId = injecViewComponent.value();
try {
//通过反射调用findViewById为组件赋值
Method method = mainActivityClass.getMethod("findViewById", int.class);
View view = (View) method.invoke(context, componentId);
field.setAccessible(true);
//修改MainActivity中控件的值为view对象。
field.set(context, view);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
这个工具类,在基类中使用。
public class IOCBaseActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
IOCInjectUtils.inject(this);
}
}
MainActivity继承使用了注入工具的基础类,
//资源文件的注入
@InjectContentView(R.layout.activity_main)
public class MainActivity extends IOCBaseActivity {
// 控件的注入
@InjecViewComponent(R.id.button)
Button btn;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//通过注入实现布局资源设置
//setContentView(R.layout.activity_main);
// 控件的注入
btn.setText("IOC注入");
}
}
对布局文件的注解,要写在类上的,对Button view的注解写在具体控件上,
这里定义了两个注解,InjectContentView用来注入布局文件,InjectViewComponent用来注入view
@Target(ElementType.TYPE)//注解应用在什么地方,
@Retention(RetentionPolicy.RUNTIME)//注解的生命周期
public @interface InjectContentView {
int value();
}
@Target(ElementType.FIELD)//注解应用在什么地方,
@Retention(RetentionPolicy.RUNTIME)//注解的生命周期
public @interface InjecViewComponent {
int value();
}
接着,看下对事件的注入是怎么实现的.
先看常规的事件处理代码:
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//
}
});
这段代码中,有几个关键因素必不可少,一,btn对象,事件作用对象,二,onClickListener这是一个什么类型的事件,三,onClick,具体处理事件的函数,四,setOnClickListener,绑定事件订阅关系的方法.
在事件注入时,这三个关键因素都是会变化的,所以,针对事件的注解的定义,要考虑到这几个因素.
这里用到了注解的多态,我们先定义一个基础的注解,让他作用在别的注解上面,也即是写在具体的事件注解类上面.
@Target(ElementType.ANNOTATION_TYPE)//注解应用在什么地方,
@Retention(RetentionPolicy.RUNTIME)//注解的生命周期
public @interface InjectBaseEvent {
String listenerSetter();//表示订阅关系
Class<?> listenerType();//表示事件本身
String callbackMethod();//表示事件处理的方法名
}
定义一个针对事件类型的注解,基础的注解,可以作用在这个注解上,并且给事件相关的因素赋值。
//可能会有多个控件,同时应用了这样的事件注入,所以value属性定义成了数组。
@Target(ElementType.METHOD)//注解应用在什么地方,
@Retention(RetentionPolicy.RUNTIME)//注解的生命周期
@InjectBaseEvent( listenerSetter = "setOnClickListener",
listenerType = View.OnClickListener.class,
callbackMethod = "onClick")
public @interface InjectOnClick {
int[] value() default -1;
}
在MainActivity中的使用方式,这里可以填入多个控件的id,
@InjectOnClick(R.id.button)
public void injectClickEvent(View view) {
}
如果要添加对长按的注入,可以仿照InjectOnClick注解,同时修改相应的事件因素,这样就很容易增加多种类型的事件注入。这个做法就是注解的多态。
最后,看下在注入工具类中,是怎么实现事件注入的。我把注释加在了代码中,跟随代码更容易说清楚。
public class IOCInjectUtils {
public static void inject(Object context) {
//injectLayout(context);
//injectView(context);
injectEvent(context);
}
private static void injectEvent(Object context) {
Class<?> mainActivityClass = context.getClass();
//拿到所有的方法
Method[] methods = mainActivityClass.getDeclaredMethods();
for (Method method : methods) {
//方法上所有的注解
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
//拿到注解对应的class,来判断它是不是一个需要处理事件的方法
Class<?> annotationClass = annotation.annotationType();
InjectBaseEvent annotationBaseEvent = annotationClass.getAnnotation(InjectBaseEvent.class);
//如果没有InjectBaseEvent注解,说明它不是一个需要处理事件的方法。
if (null == annotationBaseEvent) {
continue;
}
//否则获取事件处理的因素,确定是那种事件
String listenerSetter = annotationBaseEvent.listenerSetter();
Class<?> listenerType = annotationBaseEvent.listenerType();
String callbackMethod = annotationBaseEvent.callbackMethod();
Method value = null;
try {
//拿到注解(如InjectOnClick注解)中的value方法,
// 是为了拿到value中都有哪些控件ID,需要应用这个注解。
value = annotationClass.getDeclaredMethod("value");
//获取控件id
int[] viewIds = (int[])value.invoke(annotation);
for (int viewId : viewIds) {
//拿到id对应的控件对象, 如Button btn
Method findViewById = mainActivityClass.getMethod("findViewById", int.class);
View view = (View) findViewById.invoke(context,viewId);
//下面就可以执行控件对象身上的事件处理方法了,如onClick方法,
// 但是因为onClick中,具体的业务代码是未知的,不确定的,
// 所以这里用到了动态代理,去执行程序员定义的业务代码,
// 让这个动态代理去告诉MainActivity,有事件(如onClick)被执行了
if (null == view) {
continue;
}
//现在让动态代理跟事件处理的方法(如onClick)绑定起来,
// 然后让代理类去执行MainActivity中真实的业务处理方法,如果injectClickEvent
ListenerInvocationHandler listenerInvocationHandler =
new ListenerInvocationHandler(context, method);
//生成代理对象,代理对象,代理的是View.OnClickListener,
// 执行的它的onClick方法
Object proxyInstance = Proxy.newProxyInstance(listenerType.getClassLoader(),
new Class[]{listenerType}, listenerInvocationHandler);
//让proxy去执行View上的onClick方法。
//viewMethod拿到的是button控件上的setOnClickListener方法,
Method viewMethod = view.getClass().getMethod(listenerSetter, listenerType);
//执行控件button的setOnClickListener方法,
// 然后由proxy执行MainActivity中使用事件注解的方法,
// 也就是MainActivity中自定义的injectClickEvent方法
viewMethod.invoke(view, proxyInstance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
下面是动态代理类的实现代码:
//让这个代理类去实现 事件本身定义的接口,如View.OnClicklistener,
// 执行接口中方法,也即是事件处理中的方法,如onClick
//在事件处理方法中(onClick),实际要执行的是MainActivity中的injectClickEvent这个自定义的方法,
// 也即是添加了注解@InjectOnClick(R.id.button)
public class ListenerInvocationHandler implements InvocationHandler {
//要执行的是那个类中的那个方法,如果MainActivity中的injectClickEvent方法,
private Object activity;
private Method methodInActivity;
public ListenerInvocationHandler(Object activity, Method methodInActivity) {
this.activity = activity;
this.methodInActivity = methodInActivity;
}
//这里要执行的实际是事件处理方法,如onClick,也即是说,当点击button时,
// framework层执行的onClick方法将会跳转到这里来,
// 所以我们的注入框架中,会去处理invoke和onClick的关联
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//这里去调用真实要去执行的方法,如MainActivity中的injectClickEvent。
return methodInActivity.invoke(activity, args);
}
}
到这里就完成了了事件注入的代码实现。
IOC概念的理解,参考博文:
https://www.cnblogs.com/DebugLZQ/archive/2013/06/05/3107957.html