学习源码-ButterKnife之AnnotationProcessor

前言

俗话说的好前人栽树,后人乘凉,说实话,当我拿到源码是,我确实不知道该从何看起。于是百度了各位先辈的源码分析,进而了解了APT(AnnotationProcessor)注释解释器。

了解AbstractProcessor

可以参考一下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

Java lib

之后你可以在新建的module中继承AbstractProcessor尝试自己编写注解解释器

回归Butterknife源码尝试了解ButterKnifeProcessor

依据前面我们的了解,其中process方法的实现尤为重要,我们来看一下

    @Override 
    public boolean process(Set 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所需要的具体内容。我们继续跟踪查看BindingSetbrewJava方法。

  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文件中。

你可能感兴趣的:(学习源码-ButterKnife之AnnotationProcessor)