一、XUtils的介绍和使用
https://blog.csdn.net/u013472738/article/details/73253103
二、IOC定义
官方定义:控制反转(Inversion of Control,缩写为IOC),是面向对象编程中的一种设计原则,可以用来降低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入。通过控制反转,对象在被创建的时候,有一个调用系统内所有对象的外界实体,将其所依赖的对象的引用传递(注入)给它。
很俗的例子:王宝强将公司交给经纪人老宋管理,再将工作安排交给老宋管理,甚至将自己的老婆交给老宋管理,最后老宋控制了王宝强的一切,这就叫做控制反转。
XUtils的设计模式就是基于IOC注入式框架来编写的,这篇文章主要介绍ViewUtils的设计思想。
三、手写XUtils的ViewUtils
使用过XUtils的同学们会知道,它省去了setContentView和findViewById的操作,都是通过一个注解注入工具类实现,所以很显然用到了注解类,我们先来看它如何省略掉setContentView的操作的:
步骤一、先定义一个ContentView的注解:
//程序运行时,注解也能生效
@Retention(RetentionPolicy.RUNTIME)
//注解定义的位置,TYPE是定义在类上面
@Target(ElementType.TYPE)
public @interface ContentView {
int value();
}
步骤二、MainActivity类指定ContentView的注解:
步骤三、定义注解管理类InjectUtils
private static void injectLayout(Object context) {
int layoutId = 0;
Class> clazz = context.getClass();
//反射找到class类中使用的ContentView注解
ContentView contentView = clazz.getAnnotation(ContentView.class);
if (null != contentView) {
//拿到注解的value(R.layout.activity_main)
layoutId = contentView.value();
try {
//反射找到setContentView的方法
Method method = context.getClass().getMethod("setContentView", int.class);
//最后,反射执行setContentView
method.invoke(context, layoutId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
findViewById的注解注入方法与setContentView的类似,这里就不过多描述。
延伸的问题,如何定义Click点击事件的注解呢?我们了解过的Android 的View的点击事件可能有多个事件类型(短按、长按、dialog点击、viewHolder点击)等等,所以如何区分这些点击事件,并且代码看起来不那么冗余呢?这里用到了注解之上再使用注解的方法,并结合动态代理的设计模式去调用真正的onClick事件。
四、如何处理多事件的注入
首先理解事件三要素:
举个例子:
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
1.setOnClickListener: 事件订阅者
2.onClickListener: 事件观察者
3.onClick:事件被观察者
依据这个事件三要素的思想,我们定义一个注解之上的注解EventBase:
@Retention(RetentionPolicy.RUNTIME)
//该注解在另一个注解上使用
@Target(ElementType.ANNOTATION_TYPE)
public @interface EventBase {
//事件三要素之一的:订阅者
String listenerSetter();
//事件三要素之二:事件源
Class> listenerType();
//事件三要素之三:事件类型(长按或者短按)
String callbackMethod();
}
在OnClick注解类结合EventBase注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnClickListener",
listenerType = View.OnClickListener.class,
callbackMethod = "onClick")
public @interface OnClick {
int[] value();
}
同理,OnLongClick注解这样定义:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnLongClickListener",
listenerType = View.OnLongClickListener.class,
callbackMethod = "onClick")
public @interface OnLongClick {
int[] value();
}
好了,接下来重点是怎么去处理MainActivity中被OnClick定义的注解的方法,然后包装内部的点击事件的处理,我们来看injectClick这个方法:
private static void injectClick(Object context) {
Class> aClass = context.getClass();
//先拿到MainActivity的所有方法
Method[] methods = aClass.getDeclaredMethods();
for (Method method : methods) {
//找到被注解的方法
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
//annotation 就是类似:OnClick这个注解
Class extends Annotation> annotationType = annotation.annotationType();
//找到注解上面的注解(EventBase)
EventBase eventBase = annotationType.getAnnotation(EventBase.class);
if (eventBase == null) {
continue;
}
//获取事件三要素
String listenerSetter = eventBase.listenerSetter();
Class> listenerType = eventBase.listenerType();
String callbackMethod = eventBase.callbackMethod();
//事件三要素有了,现在还差调用这个事件的主角(textView)
Method valueMethod = null;
try {
//怎么取拿到view的对象呢?用反射,拿到value,然后再通过viewId反射拿对象
valueMethod = annotationType.getDeclaredMethod("vaule");
int[] viewId = (int[]) valueMethod.invoke(annotation);
for (int id : viewId) {
Method findViewById = aClass.getMethod("findViewById", int.class);
View view = (View) findViewById.invoke(context, id);
if (view == null) continue;
//这里拿到的是MainActivity中被@OnClick注解的方法
Method onClickMethod = view.getClass().getMethod(listenerSetter, listenerType);
//这里用到了动态代理,在使用动态代理之前一定要搞清楚你代理的是哪个对象的哪个方法
//现在我们需要代理MainActivity对象的click方法,mothod是MainActivity内部的方法
ListenerInvocationHandler listenerInvocationHandler =
new ListenerInvocationHandler(context, method);
Object proxyInstance =
Proxy.newProxyInstance(listenerType.getClassLoader(),
new Class[]{listenerType},
listenerInvocationHandler);
onClickMethod.invoke(view, proxyInstance);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
写在结尾:虽然XUtils这个开源框架已经逐渐退出历史舞台,但是它内部的设计思想始终值得我们学习和借鉴,学完了这篇文章,我们要知道XUtils内部的ViewUtils凭什么能用最少量的代码去实现23中事件注入?如果面试问到了该怎么回答呢:
自己总结的答案:XUtils事件注入的设计方式是注解之上再次封装了一层注解EventBase,最为顶层注解,你要知道任何一个事件都逃不开三要素:1.事件订阅者(setOnClickListener);2.事件被观察者(也就是事件源onClickListener);3.事件观察者(也就是消费者onClick)。所以依据这三点取定义顶层注解的方法,在onClick和onLongClick注解就引用这个顶层注解,表明它的作用域,所以我们后续再注解方法的时候,就可以通过层层反射来拿到我们想要拿到的东西,再通过一次动态代理,去实现Activity或者其他类中被注解的方法。回答结束!
Demo地址:
https://github.com/cWX411904/IOC_XUtils