前言
在 [Android] ButterKnife 淺析 中,我們了解了 ButterKnife 的用法,比較簡(jiǎn)單。
本次文章我們來學(xué)習(xí)一下 ButterKnife 的 ButterKnifeProcessor 注解處理器,注解處理器能夠解析代碼中的注解信息,生成相應(yīng)的 Java 類,這也是 ButterKnife 的關(guān)鍵實(shí)現(xiàn)原理。
建議在閱讀前先了解下 Java 中『注解』的概念。
準(zhǔn)備內(nèi)容
APT
APT(Annotation processing tool)是在編譯時(shí),掃描和處理注解的一個(gè)構(gòu)建工具,可以在編譯源代碼時(shí)額外生成 Java 源代碼。
AbstractProcessor
AbstractProcessor 是掃描和處理注解的關(guān)鍵類,ButterKnife 自定義的 Processor 就需要繼承自該類。
代碼示例如下:
public class MyProcessor extends AbstractProcessor {
// 在 Processor 創(chuàng)建時(shí)調(diào)用并執(zhí)行的初始化操作
@Override
public synchronized void init(ProcessingEnvironment env){ }
// 關(guān)鍵方法,進(jìn)行掃描和處理注解,并生成新的源代碼
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
// 指定需要注冊(cè)的注解
@Override
public Set<String> getSupportedAnnotationTypes() { }
// 指定支持的 Java 版本
@Override
public SourceVersion getSupportedSourceVersion() { }
}
添加依賴
還記得我們使用 ButterKnife 前所做的添加嗎?
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
dependencies {
compile 'com.jakewharton:butterknife:8.0.1'
apt 'com.jakewharton:butterknife-compiler:8.0.1'
}
這里 com.jakewharton:butterknife-compiler 就是自定義的注解處理器,我們?cè)?Gradle 中注冊(cè)使用它。
然而我在項(xiàng)目結(jié)構(gòu)中找了很久也沒有找到這個(gè)庫(kù)的文件,有可能是在編譯時(shí)才去訪問的,如果需要可以在 GitHub 中找到:
ButterKnifeProcessor 工作流程
上面注冊(cè)完自定義的注解處理器后,我們就知道,在編譯源代碼時(shí),APT 會(huì)調(diào)用 Processor 來查找解析注解。
主要邏輯
這里主要看 process 處理方法的內(nèi)容。
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
// 查找并解析注解
Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
// 去除注解的鍵值
for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingClass bindingClass = entry.getValue();
// 生成源代碼
try {
bindingClass.brewJava().writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
e.getMessage());
}
}
return true;
}
可以看到 process 方法的邏輯還是比較容易理解的,就是獲取到注解的鍵值并生成源代碼。
查找和解析注解
首先看 findAndParseTargets(RoundEnvironment env) 方法,通過參數(shù) RoundEnvironment env 可以找到我們想要的某一個(gè)被注解的元素。
該方法會(huì)查找所有 ButterKnife 的注解來進(jìn)行解析,我們選擇最簡(jiǎn)單的 @BindInt 來看一下:
private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
// Process each @BindInt element.
for (Element element : env.getElementsAnnotatedWith(BindInt.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceInt(element, targetClassMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindInt.class, e);
}
}
...
這里調(diào)用了 parseResourceInt 方法傳入了被注解的元素進(jìn)行解析:
private void parseResourceInt(Element element, Map<TypeElement, BindingClass> targetClassMap,
Set<TypeElement> erasedTargetNames) {
boolean hasError = false;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Verify that the target type is int.
if (element.asType().getKind() != TypeKind.INT) {
error(element, "@%s field type must be 'int'. (%s.%s)", BindInt.class.getSimpleName(),
enclosingElement.getQualifiedName(), element.getSimpleName());
hasError = true;
}
// Verify common generated code restrictions.
hasError |= isInaccessibleViaGeneratedCode(BindInt.class, "fields", element);
hasError |= isBindingInWrongPackage(BindInt.class, element);
if (hasError) {
return;
}
// Assemble information on the field.
String name = element.getSimpleName().toString();
int id = element.getAnnotation(BindInt.class).value();
BindingClass bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
FieldResourceBinding binding = new FieldResourceBinding(id, name, "getInteger", false);
bindingClass.addResource(binding);
erasedTargetNames.add(enclosingElement);
}
方法分為前后兩部分,先進(jìn)行了一些檢驗(yàn),檢驗(yàn)通過則調(diào)用 getOrCreateTargetClass 獲取或生成一個(gè)類放入數(shù)組中。
isInaccessibleViaGeneratedCode 方法檢驗(yàn)了:
- 方法修飾符不能為 private 和 static
- 包類型不能為非 Class
- 類的修飾符不能為 private
isBindingInWrongPackage 則檢驗(yàn)了包名,不能以 android 或 java 開頭。
生成類文件
解析完每個(gè)被注解的元素之后會(huì)得到一個(gè) Map<TypeElement, BindingClass> targetClassMap,接著就會(huì)調(diào)用 Map 中每個(gè) bindingClass 生成 Java 源代碼:
bindingClass.brewJava().writeTo(filer);
brewJava() 的內(nèi)容如下所示,添加了方法,父類,接口等信息,最后生成了一個(gè) JavaFile,簡(jiǎn)單了解即可:
JavaFile brewJava() {
TypeSpec.Builder result = TypeSpec.classBuilder(generatedClassName)
.addModifiers(PUBLIC);
if (isFinal) {
result.addModifiers(Modifier.FINAL);
} else {
result.addTypeVariable(TypeVariableName.get("T", targetTypeName));
}
TypeName targetType = isFinal ? targetTypeName : TypeVariableName.get("T");
if (hasParentBinding()) {
result.superclass(ParameterizedTypeName.get(parentBinding.generatedClassName, targetType));
} else {
result.addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, targetType));
}
result.addMethod(createBindMethod(targetType));
if (isGeneratingUnbinder()) {
result.addType(createUnbinderClass(targetType));
} else if (!isFinal) {
result.addMethod(createBindToTargetMethod());
}
return JavaFile.builder(generatedClassName.packageName(), result.build())
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
于是我們編譯過后就能在項(xiàng)目中找到類似 MainActivity$$ViewBinder 這樣的文件。
到這里 ButterKnifeProcessor 的工作就結(jié)束了,ButterKnife 就能夠調(diào)用生成的類來進(jìn)行綁定工作,我們將在下一篇文章中對(duì)余下的流程進(jìn)行學(xué)習(xí)。