一、预备知识
通过上篇文章的介绍,相信已经对ButterKnife的使用熟悉了,网上也有很多讲解其使用的文章。 本篇主要探究一下ButterKnife的实现原理。但是在开始分析之前,可能还需要了解一下Java注解器Annotation Processor.
推荐一篇文章: Java注解处理器
读完这篇文章,相信你肯定会对Java注解处理器有了比较深的了解。
二、原理
如果让我们实现一些注解的框架时,很容易联想到利用反射来实现。但是我们知道如果通过反射,是在运行时(Runtime)来处理View的绑定等一些列事件的,这样比较耗费资源,会影响应用的性能。
所以,ButterKnife并不是直接使用了注解+反射的方式实现,而是使用了Java 注解器Annotation Processor ,自定义注解,并注册相应的注解处理器,在编译时(而不是运行时)生成了相应的辅助类。在使用时直接通过辅助类来完成相关操作,这样就不会产生过多的消耗而出现影响性能的问题。
在此做个延伸,我们可能还熟悉另外一个注解框架xUtils, github地址:点击打开链接
这个框架注解机制,其实就是使用了注解+反射实现的。研究其源码可以看到:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
int value();
/* parent view id */
int parentId() default 0;
}
其自定义注解ViewInject 便是RetentionPolicy.RUNTIME.
这里我们不做太多解释,后面会针对xUtils开一篇博文探究其实现原理。
三、源码分析
接下来,我们就进入正题了,对ButterKnife的实现原理一步一步地分析了。
3.1 自定义注解
ButterKnife自定义了很多我们常用的注解,比如@BindView,@OnClick等.
通过@Target(FIELD) 注解可以看出,BindView注解是使用在成员变量上的。
还有其他的注解,可以在源码butterknife-annotations.butterknife包下查看。
有了这些注解,那还得需要有注解处理器呀!ButterKnife的注解处理器ButterKnifeProcessor. 那接下来就来看看这个处理器。
3.2 ButterKnifeProcessor
在分析ButterKnifeProcessor之前,可以看到它 继承自 AbstractProcessor. 我们就先来介绍一下AbstractProcessor.
自定义的Processor都必须继承自AbstractProcessor,并重写process方法,不过往往还会重写其他的方法.
public class MyProcessor extends AbstractProcessor {
//用来指定你使用的 java 版本。通常你应该返回 SourceVersion.latestSupported()
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
//会被处理器调用,可以在这里获取Filer,Elements,Messager等辅助类,后面会解释
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
}
//这个方法返回stirng类型的set集合,集合里包含了你需要处理的注解
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotataions = new LinkedHashSet<String>();
annotataions.add("com.example.MyAnnotation");
return annotataions;
}
//核心方法,这个一般的流程就是先扫描查找注解,再生成 java 文件
//这2个步骤设计的知识点细节很多。
@Override
public boolean process(Set<? extends TypeElement> annoations,
RoundEnvironment env) {
return false;
}
}
每个方法的具体功能,注释都写的很清楚,不必再过多解释。
要想JVM调用你的处理器,首先需要注册,怎样让JVM知道呢,google 为我们提供了一个库,简单的一个注解就可以。
compile 'com.google.auto.service:auto-service:1.0-rc2'
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
//...省略非关键代码
}
那接下来看ButterKnifeProcessor源码:
@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
private Elements elementUtils;
private Types typeUtils;
private Filer filer;
private Trees trees;
...
@Override public synchronized void init(ProcessingEnvironment env) {
super.init(env);
String sdk = env.getOptions().get(OPTION_SDK_INT);
if (sdk != null) {
try {
this.sdk = Integer.parseInt(sdk);
} catch (NumberFormatException e) {
env.getMessager()
.printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '"
+ sdk
+ "'. Falling back to API 1 support.");
}
}
elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
filer = env.getFiler();
try {
trees = Trees.instance(processingEnv);
} catch (IllegalArgumentException ignored) {
}
}
...
@Override public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
...
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(BindArray.class);
annotations.add(BindBitmap.class);
annotations.add(BindBool.class);
annotations.add(BindColor.class);
annotations.add(BindDimen.class);
annotations.add(BindDrawable.class);
annotations.add(BindFloat.class);
annotations.add(BindInt.class);
annotations.add(BindString.class);
annotations.add(BindView.class);
annotations.add(BindViews.class);
annotations.addAll(LISTENERS);
return annotations;
}
@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;
}
......
}
同样,ButterKnifeProcessor也是实现了那几个方法。先来看看init方法
init()
init(ProcessingEnvironment env)方法,ProcessingEnviroment参数提供很多有用的工具类Elements, Types和Filer。在解析器工作的时候,会扫描所有的Java源文件。源代码的每一个部分都是一个特定类型的Element。也就是说Element代表程序的元素,例如包、类或者方法。而Types是用来TypeMirror的工具类,Filer用来创建生成辅助文件,在这之前先看一下Element这个类我们看一下官方注释。
* Represents a program element such as a package, class, or method.
* Each element represents a static, language-level construct
* (and not, for example, a runtime construct of the virtual machine).
换句话说:Element代表程序中的元素,比如说 包,类,方法。每一个元素代表一个静态的,语言级别的结构. 例如:
public class ClassA { // TypeElement
private int var1; // VariableElement
public ClassA() {} // ExecuteableElement
public void setA( // ExecuteableElement
int newA // TypeElement
) {
}
}
这些是理解源码的基础。
getSupportedAnnotationTypes()
getSupportedAnnotationTypes()方法主要是指定ButterknifeProcessor是注册给哪些注解的。它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。源码中可以看出ButterknifeProcessor可以处理BindView.class等一系列注解。
getSupportedSourceVersion()
getSupportedSourceVersion()用来指定使用的Java版本,通常这里返回SourceVersion.latestSupported(),Butterknife也是如此。
process()
process()首先通过findAndParseTargets(env)方法扫描所有具有注解的类,并根据这些类的信息生成BindingSet,最后形成以TypeElement为键,BindingSet为值的键值对。接着循环遍历这个键值对,根据TypeElement和BindingSet里面的信息生成对应的java类。例如MainActivity生成的类即MainActivity$$ViewBinder类, 这就是前边所说的辅助类,后面会详细介绍。也就是说,process()方法主要干了两件事,一是收集信息,二是生成代码。
查看process()实现可以看出,通过findAndParseTargets 方法收集信息最终保存在Map 集合中,现在来看看findAndParseTargets源码。
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
scanForRClasses(env);
// Process each @BindArray element.
for (Element element : env.getElementsAnnotatedWith(BindArray.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceArray(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindArray.class, e);
}
}
//省略部分代码
// Process each @BindView element.
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
// we don't SuperficialValidation.validateElement(element)
// so that an unresolved View type can be generated by later processing rounds
try {
parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
// Process each @BindViews element.
for (Element element : env.getElementsAnnotatedWith(BindViews.class)) {
// we don't SuperficialValidation.validateElement(element)
// so that an unresolved View type can be generated by later processing rounds
try {
parseBindViews(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindViews.class, e);
}
}
// Process each annotation that corresponds to a listener.
for (Class<? extends Annotation> listener : LISTENERS) {
findAndParseListener(env, listener, 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;
}
省略的部分是将各注解进行分拆遍历,并且进行解析,最后将解析的结果放入bindingMap,原理和解析Bind注解是一样的,这里我们只用分析一个就可以了。就分析BindView吧,通过遍历找到所有使用时BindView注解的Element,接着将得到的Element传递给parseBindView(element, builderMap, erasedTargetNames)方法,从方法名就可以看出,BindView的解析工作是在该方法中完成的,看源码吧;
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();//获取父节点
// 1. 检查用户的合法性:是否使用private 和static修饰; 是否在android或者java开头的包名中。
// Start by verifying common generated code restrictions.
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
// Verify that the target type extends from View.
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
if (elementType.getKind() == TypeKind.ERROR) {
note(element, "@%s field with unresolved type (%s) "
+ "must elsewhere be generated as a View or interface. (%s.%s)",
BindView.class.getSimpleName(), elementType, enclosingElement.getQualifiedName(),
element.getSimpleName());
} else {
error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
BindView.class.getSimpleName(), enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
}
//不合法就直接返回
if (hasError) {
return;
}
// 2. 获取Id值
// Assemble information on the field.
int id = element.getAnnotation(BindView.class).value();
// 3. 获取BindingSet, 有缓存机制,如果没有,则会创建
BindingSet.Builder builder = builderMap.get(enclosingElement);
if (builder != null) {
String existingBindingName = builder.findExistingBindingName(getId(id));
if (existingBindingName != null) {
error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
BindView.class.getSimpleName(), id, existingBindingName,
enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
} else {
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
// 4. 生成FieldViewBinding实体
String name = element.getSimpleName().toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
// 5. 加入到bindingSet的集合中
builder.addField(getId(id), new FieldViewBinding(name, type, required));
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
基本上就是以上5个步骤。 到这里就把信息收集好了。
下一篇文章将分析一下检查用户的合法性、缓存机制创建builder 和OnClick注册事件。