APT
类似于ButterKnife这种注入框架以前也有很多的,像xutils、afinal,包括Dagger(Dagger2重构前的版本),但是这些框架都是依赖于运行时注解和反射,严重影响效率。后来的框架(ButterKnife、Dagger2等)都是依赖于编译时注解,被称为APT(Android Processing Tool)技术。
APT的实现原理是这样的:定义需要的注解类的@Retention为CLASS,定义自己的Procesor继承自AbstractProcessor类,那么在代码编译时,编译器会自动调用Processor类的process方法。
Element
在正式分析之前,先简单了解一下Element类。
Elemet被称为元素类,它有以下几个常用的子类。
- VariableElement //一般代表成员变量
- ExecutableElement //一般代表类中的方法
- TypeElement //一般代表代表类
- PackageElement //一般代表Package
ButterKnifeProcessor
在ButterKnife中,类ButterKnifeProcessor继承自AbstractProcessor,提供给我们使用的注解的@Retention都为CLASS。
找到ButterKnifeProcessor类,代码较多,我们需要关注的只有4个方法
- init() //初始化需要的数据
- getSupportedAnnotationTypes() //返回支持的注解类型
- getSupportedOptions //返回支持的源码版本
- process() //逻辑处理,重点关注
先来看看init()
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
//获取sdk版本
String sdk = env.getOptions().get(OPTION_SDK_INT);
this.sdk = Integer.parseInt(sdk);
//操作Element的工具类
elementUtils = env.getElementUtils();
//操作TypeMirror的工具类
typeUtils = env.getTypeUtils();
//用来创建辅助文件的
filer = env.getFiler();
}
注释已经解释的很详细了,init()方法就是用来创建一些辅助类。getSupportedAnnotationTypes() 和 getSupportedOptions中的代码也很简单,这里就不细看了。
接着就看看它的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);
javaFile.writeTo(filer);
}
return false;
}
首先一进这个函数就执行findAndParseTargets(env),来看看都做了那些工作:
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
scanForRClasses(env);
...
// Process each @BindView element.
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
parseBindView(element, builderMap, erasedTargetNames);
}
...
// Associate superclass binders with their subclass binders. This is a queue-based tree walk
// which starts at the roots (superclasses) and walks to the leafs (subclasses).
Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
new ArrayDeque<>(builderMap.entrySet());
Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
while (!entries.isEmpty()) {
Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();
TypeElement type = entry.getKey();
BindingSet.Builder builder = entry.getValue();
TypeElement parentType = findParentType(type, erasedTargetNames);
if (parentType == null) {
bindingMap.put(type, builder.build());
} else {
BindingSet parentBinding = bindingMap.get(parentType);
if (parentBinding != null) {
builder.setParent(parentBinding);
bindingMap.put(type, builder.build());
} else {
// Has a superclass binding but we haven't built it yet. Re-enqueue for later.
entries.addLast(entry);
}
}
}
return bindingMap;
}
这里处理各个注解的代码较多,不一一贴出了,就以我们的例子中的BindView注解为例,其他的注解也是类似的。
上来先创建了两个容器,builderMap和erasedTargetNames,然后开始遍历所有被BindView注解的Element。(BindingSet类后文会有介绍)
在循环中,只调用了一个方法,parseBindView(element, builderMap, erasedTargetNames),注意这个方法并没有返回值。
private void parseBindView(Element element,Map<TypeElement,BindingSet.Builder> builderMap,Set<TypeElement> erasedTargetNames){
//获取外围类元素
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
//获取视图的ID
int id = element.getAnnotation(BindView.class).value();
//在builderMap中查找BindingSet.Builder
BindingSet.Builder builder = builderMap.get(enclosingElement);
if (builder != null) {
//查找此ID是否有对应的数据
String existingBindingName = builder.findExistingBindingName(getId(id));
//如果有就返回
if (existingBindingName != null) {
return;
}
}else{//创建一个BindingSet.Builder,并保存到builderMap中
builder = getOrCreateBindingBuilder(builderMap,enclosingElement);
}
String name = element.getSimpleName().toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
builder.addField(getId(id),new FieldViewBinding(name,type,required));
//Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
先获取外围类元素,再获取需要绑定的View的ID,接着是一些防止重复的判断,这里我们是第一次进入,肯定不会重复了。通过getOrCreateBindingBuilder(builderMap,enclosingElement)方法创建一个BindingSet.Builder,并保存到builderMap中。最后再将一些信息保存到builder中去。
回过头来接着看findAndParseTargets()方法,
Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries = new ArrayDeque<>(builderMap.entrySet());
Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
将builder的entrySet()保存到一个队列中。接着又创建一个Map容器。
//遍历队列
while (!entries.isEmpty()) {
Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();
//
TypeElement type = entry.getKey();
BindingSet.Builder builder = entry.getValue();
TypeElement parentType = findParentType(type, erasedTargetNames);
if (parentType == null) {
//调用builder.build();
bindingMap.put(type, builder.build());
} else {
BindingSet parentBinding = bindingMap.get(parentType);
if (parentBinding != null) {
builder.setParent(parentBinding);
//调用builder.build();
bindingMap.put(type, builder.build());
} else {
// Has a superclass binding but we haven't built it yet. Re-enqueue for later.
entries.addLast(entry);
}
}
}
这一步主要是调用了builder.build()方法将builderMap数据保存到bindingMap中,if-else判断主要是为了提高健壮性,就不带大家细看了。最后将bindingMap返回到process()方法中。
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
JavaFile javaFile = binding.brewJava(sdk);
javaFile.writeTo(filer);
这一步,遍历取出相关信息,并生成文件(MainActivity_ViewBinding类),这里使用的是JavaPoet库去生成Java源文件的,你当然也可以使用别的方式去生成。
关于BindingSet
有的小伙伴可能会纠结于BindingSet类到底是什么,其实BindingSet类的作用很简单,一是通过BindingSet.builder收集信息,二是封装JavaPoet的代码。这种收集信息和生成Java源代码的方式,在ButterKnife不同的版本中也是不一样的,我们了解一下就可以了。
流程图
通过下面的流程图,加深理解一下生成MainActivity_ViewBinding的过程
ButterKnife运行的整个流程
到这里,我们就可以很清楚的了解到ButterKnife实现的整个流程了。
- 在MainActivity中通过注解,标记button和onClick方法
- 编译时执行ButterKnifePrcessor的Process方法,生成MainActivity_ViewBinding类
- 运行时,执行Bind()方法完成试图和监听的绑定
理清楚了,是不是很简单。