说到Bufferknife,相信基本都用过。在Activity中使用:
class ExampleActivity extends Activity {
@BindView(R.id.title) TextView title;
@BindView(R.id.subtitle) TextView subtitle;
@BindView(R.id.footer) TextView footer;
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
// TODO Use fields...
}
}
这样的话 我们就初始化绑定了View,那么其内部是什么原理呢? 我相信很多小伙伴都能说出来:注解+反射,但是具体怎么实现的,懵逼了吧?= - = ? 看完本篇博客我相信你也可以自己写一个。
需要掌握的知识点
- JavaPoet
- 注解
- 注解处理器(AbstractProcessor)
- apt (annotationProcessor)
上面的四个知识点是我们手写Bufferknife比需要掌握和理解的,下面我们一个一个讲解
apt (annotationProcessor)
ATP是一种处理注解的工具,它对源代码文件进行检测找出其中的Annotation,根据注解自动生成代码。 这里的源代码文件就是我们自己的包含@BindView注解的XXXActivity或者XXX Fragment。
annotationProcessor是APT工具的一种,是Google开发的内置框架,不需要特殊引入,比如我们是不是在引入Bufferknife的时候需要加入:
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.0'
这就表明,要自动检测我们的代码从而在编译的时候生成代码类。生成什么代码类呢?
public class MainActivity$ViewBinder implements ViewBinder<MainActivity>
也就是生成类似上面的代码类,这些自动生成的类的功能就是:findViewById 和 setOnClickListener。 好,那么生成这些类有什么用处呢? 试想一下,如果我们把这个类和我们的自己的XXXActivity关联起来,
也就是说让这些生成的类代替XXXActivity执行findViewById的操作,那么是不是就相当于实现了Bufferknife呢? 我想说是的!
好,现在总结一点:就是说,我们如果有我们自己的注解处理器,然后在我们的工程中引入:
annotationProcessor XXXX
这样的话,就可以自动扫描我们代码中使用该注解的Java文件,最后自动在生成Java文件。
我们创建了注解BindView, 然后在XXXActivity中使用该注解,通过APT在编译器的扫描,那最后可以产出Java文件,而这个Java文件是我们自己定义创建的。
好,这样的话APT我们知道了,上图中的注解是怎么定义的呢?
注解
说到注解我们肯定很熟悉Override吧? 看下源代码
package java.lang;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
这是一个标准的注解定义,首先Retention定义了注解的“生命周期”,也就是起作用的时间段:只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
其次Target定义了该注解要用在什么用途?换句话说就是:这个注解要用在字段?类?方法?接口?枚举?
说到Retention,分三种类型:
- RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
- RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
- RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
RUNTIME是运行时注解,在程序运行的时候才起作用,然后在程序运行的时候,再通过反射,所具体的操作。
比如:
/**
* 绑定View
* @param object
* @param view
*/
private static void bindView(Object object, View view) {
Field[] declaredFields = object.getClass().getDeclaredFields();
for(Field field : declaredFields) {
boolean annotationPresent = field.isAnnotationPresent(BindView.class);
if(!annotationPresent) {
return;
}
BindView fieldAnnotation = field.getAnnotation(BindView.class);
field.setAccessible(true);
try {
field.set(object, view.findViewById(fieldAnnotation.resIdValue()));
//view设置点击事件
bindClick(object, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}