本文主要是记录一次Java编译时注解的学习,从ButterKnife的原理去了解编译时注解
- 自定义注解
@Target(ElementType.FIELD) // 注解作用域
@Retention(RetentionPolicy.SOURCE) // 注解生命周期
public @interface BindView {
// 控件id
long value();
}
- 注解其实就相当于是一种标识,要对我们打上标记的地方做处理,就需要我们自定义注解处理器来处理
/**
3. 拿到我们自定义的注解,帮我们生成相关代码(findViewById)
*/
@AutoService(Processor.class)
public class WatermelonProcessor extends AbstractProcessor {
// 生成Java文件的对象
private Filer filer;
/**
* 注解处理器要处理的事情,生成Java文件
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
/*
* public class ClassName { TypeElement
* private int i; VariableElement
* private void method() ExecutableElement
*/
// 获得所有使用了BindView注解的成员变量,也就是控件
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
// 创建一个装控件的节点容器,结构化所有控件
Map<String, List<VariableElement>> map = new HashMap<>();
for (Element element : elements) {
VariableElement ve = (VariableElement) element;
String activityName = getActivityName(ve);
List<VariableElement> list = map.get(activityName);
if (list == null) {
list = new ArrayList<>();
map.put(activityName, list);
}
list.add(ve);
}
// 开始生成文件
// 遍历集合拿到类名
for (String activityName : map.keySet()) {
List<VariableElement> list = map.get(activityName);
String newActivityName = activityName + "$$ViewBinder";
String packageName = getPackageName(list.get(0));
// 开始写文件
Writer writer = null;
try {
JavaFileObject source = filer.createSourceFile(newActivityName);
writer = source.openWriter();
// 开始写第一行
writer.write("package " + packageName + ";\n\n");
// 第二行
writer.write("public class " + newActivityName + " implements ViewBinder<" + activityName + "> {\n\n");
// 第三行
writer.write("\tpublic void bind(" + activityName + " target) {\n");
// 为控件赋值
for (VariableElement element : list) {
writer.write("\t\ttarget." + element.getSimpleName() + " = target.findViewById(" + element.getAnnotation(BindView.class).value() + ");\n");
}
// 结束
writer.write("\t}\n}");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return false;
}
/**
* 通过控件节点拿到包名
*/
private String getPackageName(VariableElement element) {
return processingEnv.getElementUtils().getPackageOf(element).toString();
}
/**
* 通过控件节点,获取这个节点的activity name
*/
private String getActivityName(VariableElement element) {
// 获取上一层的节点
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
return typeElement.getSimpleName().toString();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
// 初始化处理文件的对象
filer = processingEnvironment.getFiler();
}
/**
* 声明我们的注解处理器要处理哪些注解
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> set = new LinkedHashSet<>();
set.add(BindView.class.getCanonicalName());
return set;
}
/**
* 告诉JVM,我们注解处理器的版本
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return processingEnv.getSourceVersion();
}
}
这一步的工作是将我们使用了注解的地方,帮我们生成相关的类来初始化打上标记的变量,不需要我们重复写一样的模板代码。就是拼接文件,写入文件的操作
- 在使用ButterKnife时,都要有个绑定的操作,将当前的对象传给ButterKnife,就会帮我们完成里面成员变量的初始化。自动生成的代码是
public class MainActivity$$ViewBinder implements ViewBinder<MainActivity> {
public void bind(MainActivity target) {
// 在调用bind方法时传进来的对象
target.tv = target.findViewById(2131165326);
target.btn = target.findViewById(2131165218);
}
}
- 所有生成的类都实现了ViewBinder这个接口,这样做是为了更方便的调用里面的方法,也更高效,看看ViewBinder的源码
/**
6. 所有生成的java文件都实现了这个接口
7. @param <T>
*/
public interface ViewBinder<T> {
void bind(T t);
}
- 进到bind方法源码
public static void bind(Activity activity) {
// 拿到生成的类名
String cName = activity.getClass().getCanonicalName() + "$$ViewBinder";
try {
// 通过反射创建ViewBinder实例,再调用bind方法
// bind方法在自动生成的文件中已经做了实现
// 到这一步就已经帮我们实现了所有带有BindView注解属性的初始化工作
// 这里为什么要用反射???
// 因为我们无法预知ViewBinder的实现类
// 要根据类名动态创建实例
// 换种说法,我们要创建的类还不存在,需要编译后才生成
Class<?> clazz = Class.forName(cName);
ViewBinder binder = (ViewBinder) clazz.newInstance();
binder.bind(activity);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
后记:ButterKnife的代码远不止这些,但主流程都是差不多的,像ButterKnife里面对实例化过的对象还有缓存的操作等等。但对编译期注解的学习,这是一个很好的入手点,写完马上就能看到效果。
最后附上githup地址:https://github.com/rongjianrun/WatermelonKnife.git