前言
俗话说的好前人栽树,后人乘凉
,说实话,当我拿到源码是,我确实不知道该从何看起。于是百度了各位先辈的源码分析,进而了解了APT(AnnotationProcessor)
注释解释器。
了解AbstractProcessor
可以参考一下AbstractProcessor介绍
AbstractProcessor
中的方法
方法的简介
void init(ProcessingEnvironment processingEnv)
该方法主要用于一些初始化的操作,通过该方法的参数
ProcessingEnvironment
可以获取一些列有用的工具类。
ProcessingEnvironment
public interface ProcessingEnvironment {
/**
* 返回用来在元素上进行操作的某些实用工具方法的实现。
*
* Elements是一个工具类,可以处理相关Element(包括ExecutableElement, PackageElement, TypeElement, TypeParameterElement, VariableElement)
*/
Elements getElementUtils();
/**
* 返回用来报告错误、警报和其他通知的 Messager。
*/
Messager getMessager();
/**
* 用来创建新源、类或辅助文件的 Filer。
*/
Filer getFiler();
/**
* 返回用来在类型上进行操作的某些实用工具方法的实现。
*/
Types getTypeUtils();
// 返回任何生成的源和类文件应该符合的源版本。
SourceVersion getSourceVersion();
// 返回当前语言环境;如果没有有效的语言环境,则返回 null。
Locale getLocale();
// 返回传递给注释处理工具的特定于 processor 的选项
Map getOptions();
}
getSupportedSourceVersion
返回此注释 Processor 支持的最新的源版本,该方法可以通过注解@SupportedSourceVersion指定。
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
getSupportedAnnotationTypes
返回此 Processor 支持的注释类型的名称。结果元素可能是某一受支持注释类型的规范(完全限定)名称。它也可能是 ” name.” 形式的名称,表示所有以 ” name.” 开头的规范名称的注释类型集合。最后,自身表示所有注释类型的集合,包括空集。注意,Processor 不应声明 “*”,除非它实际处理了所有文件;声明不必要的注释可能导致在某些环境中的性能下降。
process
注解处理器的核心方法,处理具体的注解。
其中,getSupportedSourceVersion和getSupportedAnnotationTypes也可以通过给注解处理器添加注解指定具体值。
如下:
@SupportedOptions()
@SupportedAnnotationTypes()
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class ButterKnifeProcessor extends AbstractProcessor {
// 省略具体代码
}
如何在项目中添加AbstractProcessor
的依赖
在项目中添加新的Module
,选择Java Library
之后你可以在新建的
module
中继承AbstractProcessor
尝试自己编写注解解释器
回归Butterknife源码尝试了解ButterKnifeProcessor
依据前面我们的了解,其中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();
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;
}
我们从方法的第一行开始了解,即findAndParseTargets(env)
private Map findAndParseTargets(RoundEnvironment env) {
Map builderMap = new LinkedHashMap<>();
Set erasedTargetNames = new LinkedHashSet<>();
// Process each @BindAnim element.
for (Element element : env.getElementsAnnotatedWith(BindAnim.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceAnimation(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindAnim.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);
}
}
......
}
代码中有大部分的模板代码,其中省略了很大一部分代码,我们目前先只关注parseBindView
的具体内容
private void parseBindView(Element element, Map builderMap,
Set erasedTargetNames) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// 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.
// 验证目标的类型是View的子类(继承自View)
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
Name qualifiedName = enclosingElement.getQualifiedName();
Name simpleName = element.getSimpleName();
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;
}
// Assemble information on the field.
// 收集局部变量的信息
int id = element.getAnnotation(BindView.class).value();
BindingSet.Builder builder = builderMap.get(enclosingElement);
Id resourceId = elementToId(element, BindView.class, id);
if (builder != null) {
String existingBindingName = builder.findExistingBindingName(resourceId);
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);
}
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
builder.addField(resourceId, new FieldViewBinding(name, type, required));
// Add the type-erased version to the valid binding targets set.
// 添加对象
erasedTargetNames.add(enclosingElement);
}
这个方法的作用是验证注解的使用的正确性以及收集所有的@BindView
注解中的resId
信息
由此可知,上述findAndParseTargets(env)
方法的作用是收集所有支持的注解以及注解的具体信息并将其交给process
方法中的bindingMap
以做后续处理
接下来我们继续关注process
方法中的后续语句
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
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());
}
其中binding.brewJava(sdk,debuggable)
方法获取到了生成JavaFile
所需要的具体内容。我们继续跟踪查看BindingSet
的brewJava
方法。
JavaFile brewJava(int sdk, boolean debuggable) {
TypeSpec bindingConfiguration = createType(sdk, debuggable);
return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
方法中通过createType(sdk,debuggable)
得到了TypeSpec
实例,我们继续查看createType
方法
private TypeSpec createType(int sdk, boolean debuggable) {
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(PUBLIC)
.addOriginatingElement(enclosingElement);
if (isFinal) {
result.addModifiers(FINAL);
}
if (parentBinding != null) {
result.superclass(parentBinding.getBindingClassName());
} else {
result.addSuperinterface(UNBINDER);
}
if (hasTargetField()) {
result.addField(targetTypeName, "target", PRIVATE);
}
if (isView) {
result.addMethod(createBindingConstructorForView());
} else if (isActivity) {
result.addMethod(createBindingConstructorForActivity());
} else if (isDialog) {
result.addMethod(createBindingConstructorForDialog());
}
if (!constructorNeedsView()) {
// Add a delegating constructor with a target type + view signature for reflective use.
result.addMethod(createBindingViewDelegateConstructor());
}
result.addMethod(createBindingConstructor(sdk, debuggable));
if (hasViewBindings() || parentBinding == null) {
result.addMethod(createBindingUnbindMethod(result));
}
return result.build();
}
方法中又使用了很多本地方法,比较难找,那么既然我们知道最终生成了findViewById
这样的代码,不如我们,从此入手查看。全局搜索发现文件中的addViewBinding
方法中有这样的代码。
private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) {
if (binding.isSingleFieldBinding()) {
// Optimize the common case where there's a single binding directly to a field.
FieldViewBinding fieldBinding = requireNonNull(binding.getFieldBinding());
CodeBlock.Builder builder = CodeBlock.builder()
.add("target.$L = ", fieldBinding.getName());
boolean requiresCast = requiresCast(fieldBinding.getType());
if (!debuggable || (!requiresCast && !fieldBinding.isRequired())) {
if (requiresCast) {
builder.add("($T) ", fieldBinding.getType());
}
builder.add("source.findViewById($L)", binding.getId().code);
} else {
builder.add("$T.find", UTILS);
builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");
if (requiresCast) {
builder.add("AsType");
}
builder.add("(source, $L", binding.getId().code);
if (fieldBinding.isRequired() || requiresCast) {
builder.add(", $S", asHumanDescription(singletonList(fieldBinding)));
}
if (requiresCast) {
builder.add(", $T.class", fieldBinding.getRawType());
}
builder.add(")");
}
result.addStatement("$L", builder.build());
return;
}
List requiredBindings = binding.getRequiredBindings();
if (!debuggable || requiredBindings.isEmpty()) {
result.addStatement("view = source.findViewById($L)", binding.getId().code);
} else if (!binding.isBoundToRoot()) {
result.addStatement("view = $T.findRequiredView(source, $L, $S)", UTILS,
binding.getId().code, asHumanDescription(requiredBindings));
}
addFieldBinding(result, binding, debuggable);
addMethodBindings(result, binding, debuggable);
}
我们从反方向查看addViewBinding
方法中途又被private MethodSpec createBindingConstructor(int sdk, boolean debuggable)
方法使用。
private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC);
......
if (hasViewBindings()) {
if (hasViewLocal()) {
// Local variable in which all views will be temporarily stored.
constructor.addStatement("$T view", VIEW);
}
for (ViewBinding binding : viewBindings) {
addViewBinding(constructor, binding, debuggable);
}
for (FieldCollectionViewBinding binding : collectionBindings) {
constructor.addStatement("$L", binding.render(debuggable));
}
if (!resourceBindings.isEmpty()) {
constructor.addCode("\n");
}
}
......
return constructor.build();
}
最终我们找到了在createType
方法中调用了createBindingConstructor
方法。
通过上面的一些步骤,验证了我们的想法。
ButterKnife
使用了AnnotationProcessor
自定义注释解释器,将@BindView
等等注解,在项目编译时,生成了对应的findViewById
等代码注入到了以_ViewBinding
为结尾的java文件中。