概述
对于Android从业者来说,绝大多数人都听说或者用过IOC框架,典型的第三方库有butterknife和xutils;它们的优势很明显,可以让你避免写一堆findViewById()代码,通过注解帮你注入所有的控件。举个例子:一个Activity需要setContentView()方法设置布局,你可以通过注解该Activity来代替;该Activity有许多控件,需要findViewById()来一个一个初始化,你可以通过注解该控件来代替;具体实现如下:
@ContentView(value = R.layout.activity_main)
public class MainActivity extends BaseActivity
{
@ViewInject(R.id.id_btn)
private Button mBtn1;
@ViewInject(R.id.id_btn02)
private Button mBtn2;
...
整体来讲,Android IOC框架可以通过注解+反射+动态代理来实现;注解对需要用的控件进行标注;代码执行时,利用反射技术获取被标注的类、属性和方法,解析注解内容,生成对应的类、属性并执行对应的方法;应用动态代理实现动作的监听及触发。下面是时候表演真正的技术了。
布局注入
- 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContentView {
int value();
}
解释下注解内容:@interface表示注解关键字;@Target表示该注解应用的对象,典型值有TYPE(类)、FIELD(属性)、METHOD(方法)、ANNOTATION_TYPE(其他注解);@Retention表示注解在什么情况下可用,典型的值有RUNTIME(运行时)、SOURCE(源代码)、CLASS(源码和class文件);
ContentView用于对类进行注解,主要用于设置Activity的布局文件,使用方式如下
@ContentView(value = R.layout.activity_main)
public class MainActivity
- 执行注入
private static void InjectLayout(Activity activity) {
Class<? extends Activity> clazz = activity.getClass();
ContentView contentView = clazz.getAnnotation(ContentView.class);
int layoutId = contentView.value();
activity.setContentView(layoutId);
}
通过反射获取注解内容并执行setContentView()方法。至此,布局注入完成。
属性注入
- 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ViewInject {
int value();
}
ViewInject用于注入属性,使用方式如下:
@ViewInject(R.id.id_btn)
private Button mBtn1;
- 执行注入
private static void InjectView(Activity activity) {
Class<? extends Activity> clazz = activity.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field f: fields){
ViewInject viewInject = f.getAnnotation(ViewInject.class);
if (viewInject == null){
continue;
}
int id = viewInject.value();
View view = activity.findViewById(id);
try {
f.setAccessible(true);
f.set(activity,view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
利用发射获取注解内容并执行findViewById()方法初始化属性。
事件注入
- 定义基类注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE) //用在另一个注解上
public @interface EventBase {
//事件三要素
/**
* 设置事件监听的方法
* @return
*/
String listenerSetter();
/**
* 事件监听的类型
* @return
*/
Class<?> listenerType();
/**
* 设置事件监听的回调方法
* @return
*/
String callbackMethod();
}
EventBase 定义事件的三要素,同时也是基类注解,事件注解必须应用该注解。
- 定义事件注解
@Retention(RetentionPolicy.RUNTIME)//运行时存在类上
@Target(ElementType.METHOD) //用在方法上
//注解的注解,指定事件的三要素(拓展性强,当需要拓展时,不需要修改该类,直接添加一个onLongClick注解即可)
@EventBase(listenerSetter = "setOnClickListener",listenerType = View.OnClickListener.class,callbackMethod = "onClick")
public @interface Onclick {
int[] value();
}
- 执行事件
private static void InjectEvent(Activity activity) {
Class<? extends Activity> clazz = activity.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods){
Annotation[] annotations = m.getAnnotations();
for (Annotation annotation : annotations){
//每一个注解代表一个事件类型
Class<?> annotationType = annotation.annotationType();
EventBase eventBase = annotationType.getAnnotation(EventBase.class);
//获取事件的三要素
if (eventBase == null){
continue;
}
String listenerSetter = eventBase.listenerSetter();
Class<?> listenerType = eventBase.listenerType();
String callbackMethod = eventBase.callbackMethod();
Map<String,Method> methodMap = new HashMap<>();
methodMap.put(callbackMethod,m);
//获取View
try {
Method valueMethod = annotationType.getDeclaredMethod("value");
int[] viewIds = (int[]) valueMethod.invoke(annotation);
for (int id : viewIds){
View view = activity.findViewById(id);
if (view == null){
continue;
}
//执行setXXX方法
/*btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});*/
//应用动态代理模式
Method setListenerMtd = view.getClass().getMethod(listenerSetter,listenerType);
//listenerType代理对象
InvocationHandler handler = new EventInvocationHandler(activity,methodMap);
Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(),new Class<?>[]{listenerType},handler);
setListenerMtd.invoke(view,proxy);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
同样是通过反射机制获取注解内容,不同的是,应用动态代理的方式实现事件的监听和触发。关于动态代理机制的原理在此不做详细介绍,下面附上InvocationHandler类的实现:
/**
* 用于实现事件监听时的代理
*/
public class EventInvocationHandler implements InvocationHandler {
private Activity mActivity;
private Map<String,Method> methodMap;
public EventInvocationHandler(Activity activity, Map<String,Method> methodMap) {
this.mActivity = activity;
this.methodMap = methodMap;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果是Onclick方法,进行拦截,使用代理,执行其他方法
Method mtd = methodMap.get(method.getName());
if (mtd != null){
return mtd.invoke(mActivity,args);
}
return method.invoke(proxy,args);
}
}
受xUtils库的启发,IOC框架不仅仅能实现布局、属性、事件的注入,还能应用于各种事情;举个例子:用注解标注一个方法,当有网络时则执行该方法,没有网络时给出提示并且不执行方法。理解了该框架的思想后就能做各种事情了。