IOC介绍
IOC(inversion of control)的中文解释是“控制反转”,控制反转是一种软件设计的思想,指导我们设计出一种弱耦合的程序。
依赖注入(Dependency Injection,简称 DI)是IOC的一种实现方式:在类A的实例创建过程中就创建了实例B对象,通过类型或者名称来判断将不同的对象注入到不同的属性中。
简单应用
用过butterKnife的都知道butterKnife的便利性,butterKnife早期就是利用注解配置文件加上IOC控制 反射注解内容实现对象的注入。
我们可以参考butterKnife写一个简单的demo。
注入布局文件
我们传统的编码方式是:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
我们可以通过注解把布局文件定义到MainActivity类上。
1.定义注解
@Retention(RetentionPolicy.RUNTIME)//运行期
@Target(ElementType.TYPE)//类上
public @interface ContentView {
@LayoutRes int value();//布局文件id
}
2.定义控制单元即调用注解得到资源id,注入到程序中
为了通用性,这里控制单元作为一个工具类独立出来,供其他地方使用,并且可以处理其他的注解进行注入。
public class InjectUtils {
/**
* 反射设置资源
*
* @param context activity等
*/
public static void inject(Object context) {
injectContentView(context);
}
private static void injectContentView(Object context) {
ContentView contentView = context.getClass().getAnnotation(ContentView.class);
if (contentView != null) {
int contentViewId = contentView.value();
if (contentViewId == 0) {
return;
}
try {
Method setContentViewMethod = context.getClass().getMethod("setContentView", int.class);
setContentViewMethod.invoke(context, contentViewId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
这里要注意使用getMethod方法,这个方法可以获得父类中定义的方法。
injectContentView方法内部主要逻辑是从ContentView注解对象中得到布局资源id contentViewId,然后调用反射方法讲contentViewId对象注入到方法setContentView中。
3.使用
为了复用性,这里讲调用方法写到基类BaseActivity中
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectUtils.inject(this);
}
}
MainActivity.java
@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
这样就通过ioc处理注解配置布局文件,反射将布局注入到activity中了。
进阶应用
我们也可以像butterKnife那样注解成员变量,注入对象。
1.定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {
@IdRes int value();
}
2.处理注解中的资源
public class InjectUtils {
private static SparseArray<View> viewSparseArray = new SparseArray<>();
/**
* 反射设置资源
*
* @param context activity等
*/
public static void inject(Object context) {
injectContentView(context);
injectViews(context);
}
......
private static void injectViews(Object context) {
Method findViewByIdMethod = null;
try {
Field[] fields = context.getClass().getDeclaredFields();
for (Field field : fields) {
BindView bindViewAnnotation = field.getAnnotation(BindView.class);
if (bindViewAnnotation == null) {
break;
}
int viewId = bindViewAnnotation.value();
if (findViewByIdMethod == null) {
findViewByIdMethod = context.getClass().getMethod("findViewById", int.class);
}
Object view = findViewByIdMethod.invoke(context, viewId);
field.set(context, view);
//缓存起来
viewSparseArray.put(viewId, (View) view);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
这里通过反射findViewById方法通过资源id得到View对象,并且把对象设置给定义了注解的属性。
3.使用
@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
@BindView(R.id.button1)
Button button1;
@BindView(R.id.button2)
Button button2;
......
}
注册点击事件
事件的类型有很多种,比如onClic,onLongClick,onTouch,onCheck等等,为了能够动态的定义事件,这里在引入一个注解定义事件类型,事件方法,事件赋值方法。
1.定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface EventBase {
//订阅关系
String listenerSetter();
//事件类型 OnClickLi
Class<?> listenerType();
//事件处理
String actionMethod();
}
应用EventBase
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnClickListener",
listenerType = View.OnClickListener.class,
actionMethod = "onClick")
public @interface OnClick {
@IdRes int[] value();
}
2.处理注解
public class InjectUtils {
private static SparseArray<View> viewSparseArray = new SparseArray<>();
/**
* 反射设置资源
*
* @param context activity等
*/
public static void inject(Object context) {
injectContentView(context);
injectViews(context);
injectActionEvent(context);
}
......
private static void injectActionEvent(Object context) {
try {
Method[] declaredMethods = context.getClass().getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
Annotation[] annotations = declaredMethod.getAnnotations();//1
if (annotations.length > 0) {
for (Annotation annotation : annotations) {
//为了扩展性 这里不指定具体的注解了
// if (annotation.annotationType()== OnClick.class) {
//过滤出有EventBase注解修饰的 注解
EventBase eventBaseAnnotation = annotation.annotationType().getAnnotation(EventBase.class);
if (eventBaseAnnotation == null) {
continue;
}
Method valueMethod = annotation.getClass().getDeclaredMethod("value");
int[] value = (int[]) valueMethod.invoke(annotation);
if (value != null && value.length > 0) {
for (int id : value) {
View view = viewSparseArray.get(id);
if (view == null) {
//这里使用getMethod 得到父类的方法
//1.得到注册事件的view
Method findViewByIdMethod = context.getClass().getMethod("findViewById", int.class);
view = (View) findViewByIdMethod.invoke(context, id);
viewSparseArray.put(id, view);
}
//调用事件
// button1.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
//
// }
// });
if (view == null) {
continue;
}
String listenerSetter = eventBaseAnnotation.listenerSetter();//setOnClickListener
String actionMethod = eventBaseAnnotation.actionMethod();//onClick
Class<?> listenerType = eventBaseAnnotation.listenerType();//View.OnClickListener.class
Method setListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
//使用动态代理
setListenerMethod.invoke(view, Proxy.newProxyInstance(context.getClass().getClassLoader(), new Class[]{listenerType}, new ListenerInvocationHandler(context, declaredMethod)));
}
}
// }
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
因为方法会有很多种注解修饰,比如@TargetApi(), @Deprecated等等,所以注释1出获取所有注解,然后过滤含有EventBase注解修饰的注解,通过id获取view的时候,可以利用前边@BindView注解缓存的view对象,避免创建多余的对象。在反射调用事件Listener的时候,为了通用性,使用动态代理的方式,将接口的onXXX方法委托给代理对象处理。动态代理的定义如下:
public class ListenerInvocationHandler implements InvocationHandler {
private Object object;
private Method eventMethod;
public ListenerInvocationHandler(Object object, Method eventMethod) {
this.object = object;
this.eventMethod = eventMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return eventMethod.invoke(object, args);
}
}
注意在调用代理的时候是:eventMethod.invoke(object, args); 即将onXXX的参数也设置进去了。所以我们在定义方法的时候方法的参数要和Listner接口中事件方法的一样。另外这里恰好接口就一个事件的方法,如果有多个方法也会有问题了。
3.使用
@OnClick({R.id.button1, R.id.button2})
void onClick(View v){
if (v.getId()==R.id.button1) {
Toast.makeText(this, "点击了第一个button",Toast.LENGTH_LONG).show();
}else if (v.getId()==R.id.button2){
Toast.makeText(this, "点击了第二个button",Toast.LENGTH_LONG).show();
}
}
总结
这里只是通过这个例子学习一下IOC的原理,不推荐用到项目中,因为反射是在运行期调用的,所以效率低下,而且还有很多地方没有考虑到。
网友评论