在android开发中咱们常常用到xUtils,eventBus,ButterKnife等第三方框架来帮助咱们快捷的实现布局绑定,数据传递等功能,对于这类框架的使用相信大多数的开发者都已轻车熟路,但对于其中的实现机制,可能还不是很熟悉。今天咱们来简单解析下这类框架的内部实现机制,并实现相似xUtils中的布局,控件,事件注入功能。android
对于这类框架其实内部都是用IOC注入技术实现的:git
IOC是原来由程序代码主动获取的资源,转变由第三方获取并使原来的代码被动接收的方式,以达到解耦的效果,称为控制反转;
复制代码
IOC技术有三种类型: 1.运行时注入 xUtils,eventBus,springMVC 2.源码时注入 android studio插件 3.编译时注入 butterknife dagger2;github
少说多写,下面咱们来用代码来实现一个简单的布局注入: 一般状况下咱们在新建Activity时,自动会使用:spring
setContentView(R.layout.activity_main);
复制代码
进行布局绑定。而如今咱们但愿作到的是使用框架
@ContentView(R.layout.activity_main)
复制代码
这种以注解的形式进行绑定布局;ide
1.首先咱们新建一个InjectUtils工具类:函数
public class InjectUtils {
public static void inject(Object context){
//布局的注入
injectLayout(context);
}
复制代码
2.新建注解工具
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
int value();
}
复制代码
注:@Target指的是注解的做用目标布局
咱们的注解目标是类(指的是activity)因此此处选择的是 ElementType.TYPEthis
@Retention:注解的保留位置
这里选择的是运行时注解
还须要注意的是注解的写法@interface,和接口写法一字之差,int value()是指定注解的里值的类型;
而后咱们回到InjectUtils里
private static void injectLayout(Object context) {
int layoutId = 0;
//经过反射拿到须要注入的Activity;
Class<?> clazz = context.getClass();
//在clazz上面去执行setContentView
ContentView contentView = clazz.getAnnotation(ContentView.class);
//获取注解括号后面的内容;
if (contentView != null){
layoutId= contentView.value();
//反射去执行setContentView;
try {
//获取反射类里的方法;
Method method = context.getClass().getMethod("setContentView",int.class);
//调用方法
method.invoke(context,layoutId);
}catch (Exception e){
e.printStackTrace();
}
}
}
复制代码
这样咱们就能够在activity里进行引用了;
接下来咱们在实现我控件绑定,这里的步骤和布局绑定同样:
1.新建注解:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
int value();
}
复制代码
2.一样在InjectUtils里写上控件注入的方法:
//控件的注入;
private static void injectView(Object context) {
Class<?> clazz = context.getClass();
//经过注解获取到反射类的全部的字段(此处是Activity中全部的控件)
Field[] fields= clazz.getDeclaredFields();
for (Field field : fields) {
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null){
//获取控件的id
int valued = viewInject.value();
//反射执行findViewById方法;
try {
Method method = clazz.getMethod("findViewById",int.class);
View view = (View) method.invoke(context,valued);
//AccessibleTest类中的成员变量为private,故必须进行此操做
field.setAccessible(true);
field.set(context,view);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
复制代码
这样咱们就能够在activity中使用了:
@ViewInject(R.id.btn1)
Button btn1;
@ViewInject(R.id.btn2)
Button btn2;
复制代码
最后咱们来实现一个稍微有点难度的,事件注解 先看下咱们要实现的效果:
@OnClick({R.id.btn1,R.id.btn2})
public void onclick(View view){
}
@OnLongClick({R.id.btn2})
public boolean onLongClick(View view){
return false;
}
复制代码
这里看到咱们这里使用了不一样的事件注解,因此在这里咱们须要考虑的是android中23种事件,我不可能在InjectUtils类中写23个方法去实现这些事件;这里采用的是注解的多态,和代理来实现事件的注入。来看下咱们具体的实现步骤:
1.须要建立一个EventBase注解:
//此处使用ANNOTATION_TYPE,表示该注解在其余注解之上
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {
//setOnClickListener 订阅关系
String listenerSetter();
//new View.OnClickListener() 事件自己
Class<?> listenerType();
//3.事件处理程序
String callbackMethod();
}
复制代码
建立具体的事件注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerSetter = "setOnClickListener"
,listenerType = View.OnClickListener.class
,callbackMethod = "OnClick")
public @interface OnClick {
int[] value() default -1; //此处表示赋值-1;
}
复制代码
3.建立代理类
public class ListenerInvocationHandler implements InvocationHandler {
//须要在onClick中执行Activity.click();
private Object activity;
private Method activityMethod;
public ListenerInvocationHandler(Object activity, Method activityMethod) {
this.activity = activity;
this.activityMethod = activityMethod;
}
/**
* 表示onClick的执行;
* 程序执行onClick方法时,就会转到这里;
* 框架中不直接执行onClick;
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//此处调用被注解的click();
return activityMethod.invoke(activity,args);
}
}
复制代码
这个类用来代理,new View.OnClickListener()对象; 并执行这个对象的onClick方法
4.在InjectUtils中处理具体的事件:
private static void injectClick(Object context) {
//须要一次性注入android的23种事件;
Class<?> clazz = context.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods)
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations){
//annotation 是事件好比onClick 就去取对应的注解;
Class<?> annotationClass = annotation.annotationType();
EventBase eventBase = annotationClass.getAnnotation(EventBase.class);
//若是没有eventBase 则表示当前方法不是一个事件处理的方法
if (eventBase == null)
//不是事件处理方法;
continue;
//开始获取事件处理的相关信息;
String listenerSetter = eventBase.listenerSetter();
Class<?> listenerType = eventBase.listenerType();
//事件处理程序
String callBackMethod = eventBase.callbackMethod();
Method valueMethod = null;
try {
//反射获得Id,再根据ID号获得对应的VIEW;
valueMethod = annotationClass.getDeclaredMethod("value");
int[] viewId = (int[]) valueMethod.invoke(annotation);
for (int id : viewId) {
//为了获得Button对象;
Method findViewById = clazz.getMethod("findViewById",int.class);
View view = (View) findViewById.invoke(context,id);
if (view == null){
continue;
}
//activity == context; click = method;
ListenerInvocationHandler listenerInvocationHandler =
new ListenerInvocationHandler(context,method);
//创建代理关系;
Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(),
new Class[]{listenerType},listenerInvocationHandler);
//让proxy去执行的onClick();
Method onClickMethod = view.getClass().getMethod(listenerSetter,listenerType);
onClickMethod.invoke(view,proxy);
//此时,点击按钮就会去执行代理中invoke,方法;
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
复制代码
这样咱们就能够实现事件绑定了; 这里只是IOC的简单实现。