前面已经讲到,ButterKnife使用了ButterKnifeProcessor注解处理器实现了编译时自动生成中间类,完成了findViewById操作,那么它是如何生成的呢? 首先我们需要了解 的相关基础知识了.
一.注解:
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。
Java 内置定义了一套注解,比如我们常见的@Override,@Deprecated,@SuppressWarnings等,此处不展开讲解. ButterKnife使用时就是自定义了自己的一套注解,详细参考ButterKnife支持的注解
,然后使用自己的注解器解释并生成了相应的java类.
二,注解处理器
注解处理器(Annotation Processor)是javac内置的一个用于编译时扫描和处理注解(Annotation)的工具。简单的说,在源代码阶段,通过注解处理器,我们可以获取源文件内注解(Annotation)相关内容.
有了这2项,那样就可以编译期间获取相关注解数据,然后动态生成.java源文件,解决了手工编写重复代码的问题.常用的工具,如: Dagger, EventBus都用到了注解相关技术.
三 AbstractProcessor 流程分析
AbstractProcessor 是扫描和处理注解的关键类,ButterKnife 自定义的 Processor 就需要继承自该类。下面,我们看看ButterKnifeProcessor的工作流程.
1. init()函数
@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.");
}
}
debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));
typeUtils = env.getTypeUtils();
filer = env.getFiler();
try {
trees = Trees.instance(processingEnv);
} catch (IllegalArgumentException ignored) {
try {
// Get original ProcessingEnvironment from Gradle-wrapped one or KAPT-wrapped one.
for (Field field : processingEnv.getClass().getDeclaredFields()) {
if (field.getName().equals("delegate") || field.getName().equals("processingEnv")) {
field.setAccessible(true);
ProcessingEnvironment javacEnv = (ProcessingEnvironment) field.get(processingEnv);
trees = Trees.instance(javacEnv);
break;
}
}
} catch (Throwable ignored2) {
}
}
}
该方法主要是判断sdk及相关帮助类的初始化
2.getSupportedAnnotationTypes()
@Override public Set getSupportedAnnotationTypes() {
Set types = new LinkedHashSet<>();
for (Class extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
private Set> getSupportedAnnotations() {
Set> annotations = new LinkedHashSet<>();
annotations.add(BindAnim.class);
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(BindFont.class);
annotations.add(BindInt.class);
annotations.add(BindString.class);
annotations.add(BindView.class);
annotations.add(BindViews.class);
annotations.addAll(LISTENERS);
return annotations;
}
要处理的注解类的名称集合,即 ButterKnife 支持的注解
3.process () //最主要的逻辑
@Override public boolean process(Set extends TypeElement> elements, RoundEnvironment env) {
// 查找并解析注解
Map bindingMap = findAndParseTargets(env);
// 去除注解的键值
for (Map.Entry entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
// 生成java代码
JavaFile javaFile = binding.brewJava(sdk, debuggable);
try {
//写入文件
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
可以看到,process里面通过查找注解并写入了文件.
private Map findAndParseTargets(RoundEnvironment env) {
Map builderMap = new LinkedHashMap<>();
Set erasedTargetNames = new LinkedHashSet<>();
scanForRClasses(env);
......//省略其余代码
......
// env.getElementsAnnotatedWith(BindView.class)获取所有使用BindView注解的元素
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
try {
parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
......
......
// 将builderMap中的数据添加到队列中
Deque> entries =
new ArrayDeque<>(builderMap.entrySet());
Map bindingMap = new LinkedHashMap<>();
while (!entries.isEmpty()) {
// 出队列
Map.Entry entry = entries.removeFirst();
TypeElement type = entry.getKey();
BindingSet.Builder builder = entry.getValue();
// 查找当前类元素的父类元素
TypeElement parentType = findParentType(type, erasedTargetNames);
// 如果没找到则保存TypeElement和对应BindingSet
if (parentType == null) {
bindingMap.put(type, builder.build());
} else {
BindingSet parentBinding = bindingMap.get(parentType);
if (parentBinding != null) {
// 如果找到父类元素,则给当前类元素对应的BindingSet.Builder设置父BindingSet
builder.setParent(parentBinding);
bindingMap.put(type, builder.build());
} else {
// 再次入队列
entries.addLast(entry);
}
}
}
return bindingMap;
}
先将扫描得到的注解相关信息保存到builderMap和erasedTargetNames中,最后对这些信息进行重新整理返回一个以TypeElement为 key 、BindingSet为 value 的 Map,其中TypeElement代表使用了 ButterKnife 的类,即 Activity、Fragment等,BindingSet是butterknife-compiler中的一个自定义类,用来存储要生成类的基本信息以及注解元素的相关信息
parseBindView()方法用来解析使用了BindView注解的元素:
private void parseBindView(Element element, Map builderMap,
Set erasedTargetNames) {
// 首先要注意,此时element是VariableElement类型的,即成员变量
// enclosingElement是当前元素的父类元素,一般就是我们使用ButteKnife时定义的View类型成员变量所在的类,可以理解为之前例子中的MainActivity
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// 进行相关校验
// 1、isInaccessibleViaGeneratedCode(),先判断当前元素的是否是private或static类型,
// 再判断其父元素是否是一个类以及是否是private类型。
// 2、isBindingInWrongPackage(),是否在系统相关的类中使用了ButteKnife注解
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
// TypeMirror表示Java编程语言中的一种类型。 类型包括基元类型,声明的类型(类和接口类型),数组类型,类型变量和空类型。
// 还表示了通配符类型参数,可执行文件的签名和返回类型,以及与包和关键字void相对应的伪类型。
TypeMirror elementType = element.asType();
// 如果当前元素是类的成员变量
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
Name qualifiedName = enclosingElement.getQualifiedName();
Name simpleName = element.getSimpleName();
// 判断当前元素是否是 View 的子类,或者是接口,不是的话抛出异常
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, qualifiedName, simpleName);
} else {
error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
BindView.class.getSimpleName(), qualifiedName, simpleName);
hasError = true;
}
}
if (hasError) {
return;
}
// 获得元素使用BindView注解时设置的属性值,即 View 对应的xml中的id
int id = element.getAnnotation(BindView.class).value();
// 尝试获取父元素对应的BindingSet.Builder
BindingSet.Builder builder = builderMap.get(enclosingElement);
// QualifiedId记录了当前元素的包信息以及id
QualifiedId qualifiedId = elementToQualifiedId(element, id);
if (builder != null) {
String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
// 如果当前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 {
// 创建一个新的BindingSet.Builder并返回,并且以enclosingElement 为key添加到builderMap中
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
// 判断当前元素是否使用了Nullable注解
boolean required = isFieldRequired(element);
// 创建一个FieldViewBinding,它包含了元素名、类型、是否是Nullable
// 然后和元素id一同添加到BindingSet.Builder
builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));
// 记录当前元素的父类元素
erasedTargetNames.add(enclosingElement);
}
回到process()方法
// 得到java类源码
JavaFile javaFile = binding.brewJava(sdk, debuggable);
try {
// 生成java文件
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
这个过程用到了JavaPoet开源库,提供了一种友好的方式来辅助生成 java 类代码,同时将类代码生成文件,否则需要自己拼接字符串来实现,可以发现BindingSet
除了保存信息目标类信息外,还封装了 JavaPoet 生成目标类代码的过程。关于JavaPoet的知识就不继续探究了,有兴趣的可以自己探索.
四. 自定义MyProcessor
那么我们如何自定义一个MyProcessor呢?
- create new module--->java or kotlin lib; //一定要是java lib ; 不能是android lib
extends AbstractProcessor并复写它的几个方法
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
// 在 Processor 创建时调用并执行的初始化操作
@Override
public synchronized void init(ProcessingEnvironment env){ }
// 关键方法,进行扫描和处理注解,并生成新的源代码
@Override
public boolean process(Set extends TypeElement> annoations, RoundEnvironment env) { }
// 指定需要注册的注解
@Override
public Set getSupportedAnnotationTypes() { }
// 指定支持的 Java 版本
@Override
public SourceVersion getSupportedSourceVersion() { }
}
- @AutoService(Processor.class) :向javac注册我们这个自定义的注解处理器,这样,在javac编译时,才会调用到我们这个自定义的注解处理器方法。
AutoService这里主要是用来生成META-INF/services/javax.annotation.processing.Processor文件的。如果不加上这个注解,那么,你需要自己进行手动配置进行注册
- init(ProcessingEnvironment env):每个Annotation Processor必须***
有一个空的构造函数 *。编译期间,init()会自动被注解处理工具调用,并传入ProcessingEnviroment参数,通过该参数可以获取到很多有用的工具类: Elements , Types , Filer **等等
- process(Set extends TypeElement> annoations, RoundEnvironment roundEnv):Annotation Processor扫描出的结果会存储进roundEnv中,可以在这里获取到注解内容,编写你的操作逻辑。注意,process()函数中不能直接进行异常抛出,否则的话,运行Annotation Processor的进程会异常崩溃
- getSupportedAnnotationTypes(): 该函数用于指定该自定义注解处理器(Annotation Processor)是注册给哪些注解的(Annotation),注解(Annotation)指定必须是完整的包名+类名(eg:com.example.MyAnnotation)
- getSupportedSourceVersion():用于指定你的java版本,一般返回:SourceVersion.latestSupported()。当然,你也可以指定具体java版本:比如: return SourceVersion.RELEASE_7;
完整的demo就不跑了,详情请看注解处理器(Annotation Processor)简析
我的ButterKnife相关文章
ButterKnife(一): 使用篇
ButterKnife(二): 原理解析篇
ButterKnife(三): ButterKnifeProcessor解析
参考文献
ButterKnife 原理解析
注解处理器(Annotation Processor)简析