IOC技术主要内容:
1.运行时注入, eventBus,springMVC,xUtils
2.源码时注入, android studio插件
3.编译时注入, butterknife,dagger2
需要的基础知识:泛型,反射,注解,动态代理
反射的核心:利用加载到JVM内存中的字节码获取对象的信息,并调用相关反射API进行创建对象,调用方法等操作。
核心思想
- IOC是一种设计思想
- 与设计模式要区分开
IOC是原来由程序代码中主动获取资源的方式,转变为由第三方获取资源并使原来的代码被动接收资源的方式,以达到解耦的效果,称为控制反转。
一张图解释下IOC:
手写实现运行时注入核心原理
布局的注入
@Target(ElementType.TYPE)//ElementType.TYPE表示这个注解是用在类上面的
@Retention(RetentionPolicy.RUNTIME)//三种注解类型:java-->class-->runtime
public @interface ContentView {
int value();
}
控件的注入
private static void injectView(Object context) {
Class<?> clazz = context.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
int valueId = viewInject.value();
//运行到这里,每个按钮的ID已经取到了
//注入就是反射执行findViewById方法
try {
Method method = clazz.getMethod("findViewById", int.class);
//View view = mainActivity.findViewById(valueId);
View view = (View) method.invoke(context, valueId);
field.setAccessible(true);
//mainActivity的field字段的值设置为view
field.set(context, view);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
事件的注入
事件的三要素
1.textView 事件源
2 new View.OnClickListener() 事件
3 onClick() 事件处理
进行订阅事件 setOnClickListener
textView.setOnClickListener(new View.OnClickListener() {
//onClick() 事件处理
@Override
public void onClick(View v) {
}
});
注解的多态:
比如定义了注解EventBase :
@Target(ElementType.ANNOTATION_TYPE)//ANNOTATION_TYPE表示这个注解是用在其他注解上面的,比如写在OnClick注解上
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {
// textView.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
//
// }
// });
//1.订阅关系,如:setOnClickListener()方法
String listenerSetter();
//2.订阅关的事件本身, 如:new View.OnClickListener()
Class<?> listenerType();
//3.事件处理方法,如:onClick()方法
String callbackMethod();
}
则可以在OnClick和OnLongClick 注解中的使用:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)//java-->class-->runtime
@EventBase(listenerSetter = "setOnClickListener"
,listenerType = View.OnClickListener.class
,callbackMethod = "onClick")
public @interface OnClick {
int[] value() default -1;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)//java-->class-->runtime
@EventBase(listenerSetter = "setOnLongClickListener"
,listenerType = View.OnLongClickListener.class
,callbackMethod = "onLongClick")
public @interface OnLongClick {
int[] value() default -1;
}
事件的注入
private static void injectClick(Object context) {
//需要一次性处理安卓中23种事件
Class<?> clazz = context.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
//注意,别把代码写死了 method.getAnnotation(OnClick.class);
//调用method.getAnnotations()得到方法上所有的注解
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
//annotation是事件,比如onClick、OnLongClick ,取对应的注解
Class<?> annotationClass = annotation.annotationType();
EventBase eventBase = annotationClass.getAnnotation(EventBase.class);
//如果没有eventBase,则表示当前方法不是一个事件处理的方法
if (eventBase == null) {
continue;
}
//否则就是一个事件处理的方法
//开始获取事件处理的相关信息(三要素)
//用于确定是哪种事件
// btn.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
//
// }
// });
//1.订阅关系,如:setOnClickListener()方法
// String listenerSetter();
String listenerSetter = eventBase.listenerSetter();
//2.订阅关的事件本身, 如:new View.OnClickListener()
// Class<?> listenerType();
Class<?> listenerType = eventBase.listenerType();
//3.事件处理方法,如:onClick()方法
// String callbackMethod();
String callBackMethod = eventBase.callbackMethod();
//得到3要素之后,就可以执行代码了
Method valueMethod = null;
try {
//反射调用OnClick、OnLongClick注解的value()方法,得到所有view的id,再根据id得到对应的VIEW(Button)
valueMethod = annotationClass.getDeclaredMethod("value");
int[] viewId = (int[]) valueMethod.invoke(annotation);
for (int id : viewId) {
//为了得到Button对象,使用findViewById
Method findViewByIdMethod = clazz.getMethod("findViewById", int.class);
View view = (View) findViewByIdMethod.invoke(context, id);
//运行到这里,view就相当于Activity中我们写的Button
if (view == null) {
continue;
}
//activity==context click===method
ListenerInvocationHandler listenerInvocationHandler =
new ListenerInvocationHandler(context, method);
//做代理 new View.OnClickListener()对象
Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader()
, new Class[]{listenerType}, listenerInvocationHandler);
//执行 让proxy执行的onClick()
//参数1 setOnClickListener()
//参数2 new View.OnClickListener()对象
//view.setOnClickListener(new View.OnClickListener())
//获取view对象的setOnClickListener方法,方法的形参是OnClickListener
Method setOnClickListeneMethod = view.getClass().getMethod(listenerSetter, listenerType);
setOnClickListeneMethod.invoke(view, proxy);//执行view对象的setOnClickListener方法,传递的实参是proxy
// 那么当系统调用view设置的监听器的所有方法时(自然包括onClick()、onLongClick()),都会被转为调用proxy的invoke方法()。
// 这时候,如果点击按钮,就会去执行代理类proxy中的invoke方法(),最终调用activity中标注了注解的方法
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Class的getMethod方法:public Method getMethod(String name, Class<?>... parameterTypes)
的作用是获得对象所声明的公开方法。该方法的第一个参数name是要方法的名称,第二个参数parameterTypes是该方法的按声明顺序的所有形参类型。
如何让系统执行点击事件的方法时调用Activity中标注了注解的方法?
使用动态代理。即调用view对象的setOnClickListener方法,传递的实参是proxy动态代理实例,那么view对象里面的监听器对象就是一个动态代理实例,当系统在View内部调用监听器对象的所有方式时都会转为调用动态代理实例的invoke()方法,然后在invoke()方法里面调用Activity中标注了注解的方法。
看一下调用路径:
longClick()
java.lang.Exception
at com.example.javaadvanced.OpenSourceFramework.ioc_runtime.IOCMainActivity.longClick(IOCMainActivity.java:55)
at java.lang.reflect.Method.invoke(Native Method)
at com.example.javaadvanced.OpenSourceFramework.ioc_runtime.ListenerInvocationHandler.invoke(ListenerInvocationHandler.java:30)
at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
at $Proxy8.onLongClick(Unknown Source)
at android.view.View.performLongClickInternal(View.java:7528)
at android.view.View.performLongClick(View.java:7486)
at android.widget.TextView.performLongClick(TextView.java:12390)
at android.view.View.performLongClick(View.java:7504)
at android.view.View$CheckForLongPress.run(View.java:28254)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
可以看到 $Proxy8
就是jdk生成的动态代理类,后面显示的是(Unknown Source),而其他方法调用都显示java文件信息,因为这个类并没有java文件,是jdk在内存中直接生成字节码的。
总结
1.得到activity上所有的方法
2.再取到每一个方法上所有的注解
3.找到方法上有EventBase注解的,就是事件方法
4.如果是事件处理方法,就要得到这个方法对应的三要素
5.得到三要素后,需要一个动态代理来执行系统的onClick
6.自定义一个动态代理设置到View的OnClickListener对象上
7.让框架执行代理(系统对onClick的调用转为了调用代理中的invoke方法)
8.由代理去执行用户写的abc()点击事件相关方法