IOC(inversion of control)的中文解释是“控制反转”或者“依赖注入”,它的实现目的是:我们可以通过配置文件来控制程序的流程,达到程序代码的优化。通俗的讲就是将程序要做的一些事交给框架处理,达到很好的解耦性。
android中常用的视图绑定框架有多个,像Butterknife,XUtils等,今天就窥探下其原理。
分三步
一、视图绑定,就以activity为例,setContentView来指定加载的视图
定义一个注解:
package com.example.inject;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* zhiyunPlayer
* Created by swl on 2019-09-03
* Describe:
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface LayoutID {
int value(); //值就是视图的id
}
使用:
@LayoutID(R.layout.activity_main)
public class MainActivity extends AppCompatActivity
接下来就是怎么去通过注解的id注入到activity的setContentView中
/**
*
* @param obj 就是注解的类
*/
private static void injectContentView(Object obj) {
Class<?> clazz = obj.getClass();
LayoutID layoutID = clazz.getAnnotation(LayoutID.class);
if(null==layoutID){
return;
}
int value = layoutID.value();
try {
Method setContentView = clazz.getMethod("setContentView", int.class);
setContentView.invoke(obj,value);
} catch (Exception e) {
e.printStackTrace();
}
}
二、控件注解绑定
也定义一个注解:
package com.example.inject;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by swl on 2019-09-03
* Describe:
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface ViewID {
int value() ;
}
使用:
@ViewID(R.id.tv)
TextView mTextView;
注解绑定:
private static void injectView(Object obj) {
Class<?> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
ViewID viewID = field.getAnnotation(ViewID.class);
if(null==viewID){
continue;
}
try {
Method findViewById = clazz.getMethod("findViewById", int.class);
Object view = findViewById.invoke(obj, viewID.value());
field.setAccessible(true);
field.set(obj,view);
} catch (Exception e) {
e.printStackTrace();
}
}
}
三、事件注解绑定:前两者相对比较简单的,这个稍微复杂点
先看个例子:
mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
这里有其实有4个不确定的元素,一个是控件(mTextView),一个是事件监听设置(setOnClickListener),一个是事件监听器(View.OnClickListener),一个是具体事件回调(onClick)
根据这个来定义注解:
控件还是通过ID:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Click {
/* mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});*/
int[] value() default -1;
}
这时要思考的就是其他3个不确定元素怎么通过注解定义出来,这时就可以使用注解的注解。
再定义一个标识另外三个不确定元素的注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface EventType {
String eventMethod(); //就是setOnClikListener
Class<?> eventClazz(); //View.OnClickListener
String event(); //onClick
}
然后我们再修改前面的Click注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventType(eventMethod = "setOnClickListener",eventClazz = View.OnClickListener.class,event = "onClick")
public @interface Click {
/* mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});*/
int[] value() default -1;
}
使用:
@Click(R.id.tv) //多个控件,就传数组
public void click(View view){
Toast.makeText(this, "点击了"+view.getId(), Toast.LENGTH_SHORT).show();
}
接下来就是怎么注入绑定了?
private static void injectEvent(Object obj) {
Class<?> clazz = obj.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
Annotation[] annotations = method.getAnnotations();//拿到方法上所有的注解
for (Annotation annotation : annotations) {
Class<? extends Annotation> annotationType = annotation.annotationType();
EventType eventType = annotationType.getAnnotation(EventType.class);//拿到注解上的注解
if(null==eventType){
continue;
}
String eventMethod = eventType.eventMethod();
Class<?> eventClazz = eventType.eventClazz();
String event = eventType.event();
//得到注解的value值
try {
Method value = annotationType.getMethod("value");
int[] viewId = (int[]) value.invoke(annotation);
//遍历数组,通过反射方式获取对应的View
for (int i : viewId) {
Method findViewById = clazz.getMethod("findViewById", int.class);
Object view = findViewById.invoke(obj, i);
if(null==view){
continue;
}
/*
* 这里拿到了view,设置事件监听的方法名,事件监听类以及事件回调方法名,就只要考虑怎么把这事件设置给view,转交给obj里面注解了的方法去执行,这时我们就会想到动态代理,创建一个InvacationHandler,把obj和注解的Method传进去
public class EventInvocationHandler implements InvocationHandler {
public EventInvocationHandler(Object obj, Method methodName) {
this.obj = obj;
this.mMethod = methodName;
}
private Object obj;
private Method mMethod;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return mMethod.invoke(obj,args);
}
}
*/
EventInvocationHandler invocationHandler = new EventInvocationHandler(obj, method);
Object proxyInstance = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{eventClazz}, invocationHandler);
//反射方式获取setOnClickListerner(View.OnClickListener())方法
Method viewEvent = view.getClass().getMethod(eventMethod, eventClazz);
//和代理对象发生关联,一旦主体对象(View.OnClickLiserner)的OnClick(View view)执行了,就会执行代理对象的invoke方法,这样就把方法执行转交给obj了
viewEvent.invoke(view,proxyInstance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
InvocationHandler的定义:
public class EventInvocationHandler implements InvocationHandler {
public EventInvocationHandler(Object obj, Method methodName) {
this.obj = obj;
this.mMethod = methodName;
}
private Object obj;
private Method mMethod;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return mMethod.invoke(obj,args);
}
}
至此,视图、控件、事件的绑定就基本完成了,而且具有比较好的扩展性,假如新加入一个事件,比如onLongClikListener,就依样画葫芦
定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventType(eventMethod = "setOnLongClickListener",eventClazz = View.OnLongClickListener.class,event = "onLongClick")
public @interface LongClick {
/* mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});*/
int[] value() default -1;
}
使用也是一样
@LongClick(R.id.tv)
public boolean longClik(View view){ //这里注意下,这里的返回值得跟主体调用的方法得返回保持一致
Toast.makeText(this, "长点击了"+view.getId(), Toast.LENGTH_SHORT).show();
return true;
}