一.先复习一下java中的注解
以Retrofit的get注解为例
//标志注解的注解是元注解Documented,Target,Retention都是元注解
@Documented
//docemented是没有参数的注解,是生成javadoc的时候注解是否也生成
@Target(METHOD)
/*target是表明这个注解的描述的位置:
1.CONSTRUCTOR:用于描述构造器
2.FIELD:用于描述域
3.LOCAL_VARIABLE:用于描述局部变量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述参数
7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
*/
@Retention(RUNTIME)
/*retention是指作用域
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在运行时有效(即运行时保留)
*/
//用 @interface 声明为一个注解
public @interface GET {
//如果只有一个参数的话,名字为value() -->约定俗成,调用的时候,不用写成@GET(value="user") @GET("users"),
String value() default "";
/* int age() default 18;
float count() default 10.0;
…………
参数类型支持
1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
2.String类型
3.Class类型
4.enum类型
5.Annotation类型
6.以上所有类型的数组
*/
}
注释可能让真相更模糊了,其实就简单几行
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
String value() default "";
}
根据上面的注释我们可以知道,以上的意思就是这个get注解只对方法有效,然后是运行时存在并起作用的。它有一个String类型的参数,因为只有一个参数,所以名称默认是value,可以取成别的,但是不符合规范。默认是value,然后调用的时候就可以直接写值,让代码更简洁。
@GET("users")
二.好了,现在开始ButterKnife
背景:自从java5后,加入了一个东西,就是注解处理器,什么是注解处理器,就是在编译之前你可以做一些其他的操作,比如说生成了个文件,打印个数据之类的,你想干嘛干嘛,那么本身jdk为我们提供的
AbstractProcessor类是一个抽象类
所以要继承AbstractProcessor,实现抽象方法process,而process()就是关键所在了,也就是注解处理器的入口方法了。
那么在ButterKnife中
ButterKnifeProcessor extends AbstractProcessor
已经很明显了,那么显然,一定会有process方法
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
JavaFile javaFile = binding.brewJava(sdk);
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
乍一看,不懂啊~~其实我一看我不懂,查阅资料之后有了一种新的方法—猜。其实也不能叫猜,大神写的代码其实很有规律,基本上都是见名知意,所以我们在看上面的代码
首先:parse
然后:遍历生成javafile,往文件里写
先一放,我们回头看butterknife在编译时其实做了一个非常重要的工作,就是生成了一个文件,比如在MainActivity中使用Bk
public class MainActivity$$ViewBinder<T extends MainActivity> implements ViewBinder<T> {
public MainActivity$$ViewBinder() {
}
生成了一个以$$ViewBinder为suffix的java类文件
//…………代码省略
view = (View)finder.findRequiredView(source, 2131427423, "field \'recyclerView\'");
target.recyclerView = (RecyclerView)finder.castView(view, 2131427423, "field \'recyclerView\'");
view = (View)finder.findRequiredView(source, 2131427417, "method \'turn2SimpleCustomView\'");
view.setOnClickListener(new DebouncingOnClickListener() {
public void doClick(View p0) {
target.turn2SimpleCustomView();
}
});
view = (View)finder.findRequiredView(source, 2131427418, "method \'turn2CustomViewGroup\'");
view.setOnClickListener(new DebouncingOnClickListener() {
public void doClick(View p0) {
target.turn2CustomViewGroup();
}
});
//…………代码省略
看到这里貌似跟我们以前写的findviewbyid很相似啊,其实最后调用还是finviewbyid这个方法,不然,@Bind(R.id.id)传个id干嘛,哈哈~
回到上面process的代码
先前说过
首先:parse,获取到对应的注解id的map
然后:遍历生成javafile,往文件里写,生成的文件就是以$$Viewbinder结尾的的文件,而这些文件替我们做了findviewbyid这些重复的操作。
好了,基本原理大概就是这样了,现在我们来看缺点,凡是有利有弊嘛
缺点:
- 生成了很多隐藏文件,以$$ViewBinder结尾的类
- 编译时要去生成文件等一系列操作,难免要增加编译时间
优点:
- 代码简洁,已读
- 最重要的一点就是减少开发者重复性的工作
结论:相对于Afinal这类通过反射实现注入,明显在移动端,效率更有优势,相对于运行时的效率,牺牲一点编译时的时间是值得的。
三.AndroidAnnotations和Dragger2
其实这两个框架本人并没有使用过,但是通过阅读资料得知,这个两个框架与ButterKnife是异曲同工,都是利用里比编译时通过注解处理器进行依赖的注入。有时间也会去研究一下下,^_^~~