[Android] ButterKnifeProcessor 工作流程分析

前言

[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 中找到:

butterknife-compiler

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)了:

  1. 方法修飾符不能為 private 和 static
  2. 包類型不能為非 Class
  3. 類的修飾符不能為 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í)。

參考資料

AndroidSutdio 編譯時(shí)自動(dòng)生成源代碼

Android 淺析 ButterKnife (二) 源碼解析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容