让我们在源码的世界里荡起双桨之——butterknife (一) 解析、校验、组装

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixiao1999/article/details/79809231

概述

想要浏览ButterKnife的注解处理器实现原理,前提需要掌握Java编程元素接口的相关知识,所以下面的代码遨游默认老铁们都是掌握了这一部分知识。

注解处理器源码解析

话不多说,开始遨游。

  1. 支持的注解类型\集合

    private Set<Class<? extends Annotation>> getSupportedAnnotations() {
       Set<Class<? extends Annotation>> 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;
    }

    LISTENERS,监听回调类型的注解集合

    private static final List<Class<? extends Annotation>> LISTENERS = Arrays.asList(//
      OnCheckedChanged.class, //
      OnClick.class, //
      OnEditorAction.class, //
      OnFocusChange.class, //
      OnItemClick.class, //
      OnItemLongClick.class, //
      OnItemSelected.class, //
      OnLongClick.class, //
      OnPageChange.class, //
      OnTextChanged.class, //
      OnTouch.class //
    );
  2. process(…)方法,解析被支持的注解标记的元素,然后生成Java文件

    @Override 
    public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
        Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
    
        for (Map.Entry<TypeElement, BindingSet> 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<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
        Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
        //paichu
        Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    
        scanForRClasses(env);
    
        // 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);
          }
        }
        .... \\省略代码,代码套路基本一致,略微存在一些差异    
    }

    我们先以被@BindAnim注解标记的元素解析为例,看看ButterKnife的代码是怎么样的一个思路,上面的代码可知,首先是获取到被@BindAnim标记的元素的集合,然后遍历集合中所有的元素,其主要解析的方法是parseResourceAnimation(...) ,代码如下:

    private void parseResourceAnimation(Element element,
      Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
        boolean hasError = false;
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    
        // Verify that the target type is Animation.
        if (!ANIMATION_TYPE.equals(element.asType().toString())) {
          error(element, "@%s field type must be 'Animation'. (%s.%s)",
              BindAnim.class.getSimpleName(), enclosingElement.getQualifiedName(),
              element.getSimpleName());
          hasError = true;
        }
    
        // Verify common generated code restrictions.
        hasError |= isInaccessibleViaGeneratedCode(BindAnim.class, "fields", element);
        hasError |= isBindingInWrongPackage(BindAnim.class, element);
    
        if (hasError) {
          return;
        }
    
        // Assemble information on the field.
        String name = element.getSimpleName().toString();
        int id = element.getAnnotation(BindAnim.class).value();
        QualifiedId qualifiedId = elementToQualifiedId(element, id);
        BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
        builder.addResource(new FieldAnimationBinding(getId(qualifiedId), name));
    
        erasedTargetNames.add(enclosingElement);
    }

    总览上面的代码,可以清晰的了解其思路,分为2个步骤:校验和信息组装收集,首先来看校验:

    1. 首先校验被标记的元素的类型是否是Animation。
    2. 再校验被标记的元素的修饰符是否是private和static,校验封装此元素的最里层元素enclosingElement的类别Kind是否为CLASS类别,这一个步骤的校验主要是为了便于后面生成的Java文件中给元素赋值的时候,保证其元素是可访问的。(这一步校验我把它称为适用校验

    如果上述校验通过,则开始组装收集信息,为生成Java文件做准备,我们分析其组装信息的代码,主要收集了被标记元素的simpleName、@BindAnim注解的value值(id),然后将封装此元素的最里层元素enclosingElement作为key值,一个BuilderSet.Builder对象最为value,存储进入Map集合。

    到这里解析就基本告一段落了,但是还有一个BuilderSet.Builder还没弄清楚,我们在来看看是什么东西。

    final class BindingSet {
        //...
        private final Map<Id, ViewBinding.Builder> viewIdMap = new LinkedHashMap<>();
        private final ImmutableList.Builder<FieldCollectionViewBinding> collectionBindings =
        ImmutableList.builder();
        private final ImmutableList.Builder<ResourceBinding> resourceBindings = ImmutableList.builder();
    
        private BindingSet(TypeName targetTypeName, ClassName bindingClassName, boolean isFinal,
          boolean isView, boolean isActivity, boolean isDialog, ImmutableList<ViewBinding> viewBindings,
          ImmutableList<FieldCollectionViewBinding> collectionBindings,
          ImmutableList<ResourceBinding> resourceBindings, BindingSet parentBinding) {
    
            this.isFinal = isFinal;
            this.targetTypeName = targetTypeName;
            this.bindingClassName = bindingClassName;
            this.isView = isView;
            this.isActivity = isActivity;
            this.isDialog = isDialog;
            this.viewBindings = viewBindings;
            this.collectionBindings = collectionBindings;
            this.resourceBindings = resourceBindings;
            this.parentBinding = parentBinding;
        }
    
        static final class Builder {
            private final TypeName targetTypeName;
            private final ClassName bindingClassName;
            private final boolean isFinal;
            private final boolean isView;
            private final boolean isActivity;
            private final boolean isDialog;
            ...
            private final Map<Id, ViewBinding.Builder> viewIdMap = new LinkedHashMap<>();
            private final ImmutableList.Builder<FieldCollectionViewBinding> collectionBindings =
        ImmutableList.builder();
            private final ImmutableList.Builder<ResourceBinding> resourceBindings = ImmutableList.builder();
    
            private Builder(TypeName targetTypeName, ClassName bindingClassName, boolean isFinal,
            boolean isView, boolean isActivity, boolean isDialog) {
              this.targetTypeName = targetTypeName;
              this.bindingClassName = bindingClassName;
              this.isFinal = isFinal;
              this.isView = isView;
              this.isActivity = isActivity;
              this.isDialog = isDialog;
            }
            //...
        }
    }

    整理了一下代码,然后我们可以清晰的看到,这是一个建造者模式的类,其中的成员变量用来保存需要组装的信息,因为这里省略了很多代码,但是其不仅仅是保存组装信息,还有涉及到javapoet的一些东西,由于篇幅以及保证思路清晰,在此不一一列举,留待下一篇再分析。
    最后,是一个封装了Animation id和需要绑定的变量名称name的FieldAnimationBinding对象,然后封装到Map集合中对应的enclosingElement为key的Builder对象中去。

  3. findAndParseListener

    除了解析一般的像是被@BindView、@BindAnim等这类比较简单普通的注解标记的元素外,ButterKnife还有像@Onclick这类比较稍微复杂的注解,这一类注解主要是用来实现监听回调用途,我们再来看看代码是如何写的,思路又是什么样的呢?

    private void findAndParseListener(RoundEnvironment env,
      Class<? extends Annotation> annotationClass,
      Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
        for (Element element : env.getElementsAnnotatedWith(annotationClass)) {
          if (!SuperficialValidation.validateElement(element)) continue;
          try {
            parseListenerAnnotation(annotationClass, element, builderMap, erasedTargetNames);
          } catch (Exception e) {
            StringWriter stackTrace = new StringWriter();
            e.printStackTrace(new PrintWriter(stackTrace));
    
            error(element, "Unable to generate view binder for @%s.\n\n%s",
                annotationClass.getSimpleName(), stackTrace.toString());
          }
        }
    }

    首先一开始和之前分析过的一样,利用env对象获取被注解标记的Elements集合,然后遍历集合,解析每一个被标记的元素。和之前的不一样在于,其主要解析方法是parseListenerAnnotation(…),话不多说,来看代码。

    private void parseListenerAnnotation(Class<? extends Annotation> annotationClass, Element element,
      Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames)
      throws Exception {
    // This should be guarded by the annotation's @Target but it's worth a check for safe casting.
    if (!(element instanceof ExecutableElement) || element.getKind() != METHOD) {
      throw new IllegalStateException(
          String.format("@%s annotation must be on a method.", annotationClass.getSimpleName()));
    }
    
    ExecutableElement executableElement = (ExecutableElement) element;
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    
    // Assemble information on the method.
    Annotation annotation = element.getAnnotation(annotationClass);
    Method annotationValue = annotationClass.getDeclaredMethod("value");
    if (annotationValue.getReturnType() != int[].class) {
      throw new IllegalStateException(
          String.format("@%s annotation value() type not int[].", annotationClass));
    }
    
    int[] ids = (int[]) annotationValue.invoke(annotation);
    String name = executableElement.getSimpleName().toString();
    boolean required = isListenerRequired(executableElement);
    
    // Verify that the method and its containing class are accessible via generated code.
    boolean hasError = isInaccessibleViaGeneratedCode(annotationClass, "methods", element);
    hasError |= isBindingInWrongPackage(annotationClass, element);
    
    Integer duplicateId = findDuplicate(ids);
    if (duplicateId != null) {
      error(element, "@%s annotation for method contains duplicate ID %d. (%s.%s)",
          annotationClass.getSimpleName(), duplicateId, enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }
    
    ListenerClass listener = annotationClass.getAnnotation(ListenerClass.class);
    if (listener == null) {
      throw new IllegalStateException(
          String.format("No @%s defined on @%s.", ListenerClass.class.getSimpleName(),
              annotationClass.getSimpleName()));
    }
    
    //...省略代码,对id数组进行正确性校验
    
    ListenerMethod method;
    ListenerMethod[] methods = listener.method();
    if (methods.length > 1) {
      throw new IllegalStateException(String.format("Multiple listener methods specified on @%s.",
          annotationClass.getSimpleName()));
    } else if (methods.length == 1) {
      if (listener.callbacks() != ListenerClass.NONE.class) {
        throw new IllegalStateException(
            String.format("Both method() and callback() defined on @%s.",
                annotationClass.getSimpleName()));
      }
      method = methods[0];
    } else {
      Method annotationCallback = annotationClass.getDeclaredMethod("callback");
      Enum<?> callback = (Enum<?>) annotationCallback.invoke(annotation);
      Field callbackField = callback.getDeclaringClass().getField(callback.name());
      method = callbackField.getAnnotation(ListenerMethod.class);
      if (method == null) {
        throw new IllegalStateException(
            String.format("No @%s defined on @%s's %s.%s.", ListenerMethod.class.getSimpleName(),
                annotationClass.getSimpleName(), callback.getDeclaringClass().getSimpleName(),
                callback.name()));
      }
    }
    
    // Verify that the method has equal to or less than the number of parameters as the listener.
    List<? extends VariableElement> methodParameters = executableElement.getParameters();
    if (methodParameters.size() > method.parameters().length) {
      error(element, "@%s methods can have at most %s parameter(s). (%s.%s)",
          annotationClass.getSimpleName(), method.parameters().length,
          enclosingElement.getQualifiedName(), element.getSimpleName());
      hasError = true;
    }
    
    // Verify method return type matches the listener.
    TypeMirror returnType = executableElement.getReturnType();
    if (returnType instanceof TypeVariable) {
      TypeVariable typeVariable = (TypeVariable) returnType;
      returnType = typeVariable.getUpperBound();
    }
    if (!returnType.toString().equals(method.returnType())) {
      error(element, "@%s methods must have a '%s' return type. (%s.%s)",
          annotationClass.getSimpleName(), method.returnType(),
          enclosingElement.getQualifiedName(), element.getSimpleName());
      hasError = true;
    }
    
    if (hasError) {
      return;
    }
    
    Parameter[] parameters = Parameter.NONE;
    if (!methodParameters.isEmpty()) {
      parameters = new Parameter[methodParameters.size()];
      BitSet methodParameterUsed = new BitSet(methodParameters.size());
      String[] parameterTypes = method.parameters();
      for (int i = 0; i < methodParameters.size(); i++) {
        VariableElement methodParameter = methodParameters.get(i);
        TypeMirror methodParameterType = methodParameter.asType();
        if (methodParameterType instanceof TypeVariable) {
          TypeVariable typeVariable = (TypeVariable) methodParameterType;
          methodParameterType = typeVariable.getUpperBound();
        }
    
        for (int j = 0; j < parameterTypes.length; j++) {
          if (methodParameterUsed.get(j)) {
            continue;
          }
          if ((isSubtypeOfType(methodParameterType, parameterTypes[j])
                  && isSubtypeOfType(methodParameterType, VIEW_TYPE))
              || isTypeEqual(methodParameterType, parameterTypes[j])
              || isInterface(methodParameterType)) {
            parameters[i] = new Parameter(j, TypeName.get(methodParameterType));
            methodParameterUsed.set(j);
            break;
          }
        }
        if (parameters[i] == null) {
          StringBuilder builder = new StringBuilder();
          builder.append("Unable to match @")
              .append(annotationClass.getSimpleName())
              .append(" method arguments. (")
              .append(enclosingElement.getQualifiedName())
              .append('.')
              .append(element.getSimpleName())
              .append(')');
          for (int j = 0; j < parameters.length; j++) {
            Parameter parameter = parameters[j];
            builder.append("\n\n  Parameter #")
                .append(j + 1)
                .append(": ")
                .append(methodParameters.get(j).asType().toString())
                .append("\n    ");
            if (parameter == null) {
              builder.append("did not match any listener parameters");
            } else {
              builder.append("matched listener parameter #")
                  .append(parameter.getListenerPosition() + 1)
                  .append(": ")
                  .append(parameter.getType());
            }
          }
          builder.append("\n\nMethods may have up to ")
              .append(method.parameters().length)
              .append(" parameter(s):\n");
          for (String parameterType : method.parameters()) {
            builder.append("\n  ").append(parameterType);
          }
          builder.append(
              "\n\nThese may be listed in any order but will be searched for from top to bottom.");
          error(executableElement, builder.toString());
          return;
        }
      }
    }
    
    MethodViewBinding binding = new MethodViewBinding(name, Arrays.asList(parameters), required);
    BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    for (int id : ids) {
      QualifiedId qualifiedId = elementToQualifiedId(element, id);
      if (!builder.addMethod(getId(qualifiedId), listener, method, binding)) {
        error(element, "Multiple listener methods with return value specified for ID %d. (%s.%s)",
            id, enclosingElement.getQualifiedName(), element.getSimpleName());
        return;
      }
    }
    
    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement);
    }
    

    代码较多,我们一步一步来看,过程中可能会偏离主线,然后再回到主线,所以老铁们要紧跟思路。

    首先校验集合中的每一个Element,判断元素是否是一个方法元素(ExecutableElement)。
    如果是满足上面的判断,则获取方法元素Element上指定注解类型Annotation对象,进而获得注解上定义的一些信息,这里我们以@Onclick注解类型详细来了解其编码思路。
    我们找到@Onclick的类定义:

    @Target(METHOD)
    @Retention(CLASS)
    @ListenerClass(
        targetType = "android.view.View",
        setter = "setOnClickListener",
        type = "butterknife.internal.DebouncingOnClickListener",
        method = @ListenerMethod(
            name = "doClick",
            parameters = "android.view.View"
        )
    )
    public @interface OnClick {
      /** View IDs to which the method will be bound. */
      @IdRes int[] value() default { View.NO_ID };
    }

    代码很清晰,@Onclick注解是一个编译时注解,适用于Method,有一个方法value(),返回类型为int[],默认值为只包含一个数值-1的数组,然后还被@ListenerClass注解标记,那么,接下来再看看@ListenerClass注解是如何定义的。

    @Retention(RUNTIME) @Target(ANNOTATION_TYPE)
    public @interface ListenerClass {
      String targetType();
    
      /** Name of the setter method on the {@linkplain #targetType() target type} for the listener. */
      String setter();
    
      /**
       * Name of the method on the {@linkplain #targetType() target type} to remove the listener. If
       * empty {@link #setter()} will be used by default.
       */
      String remover() default "";
    
      /** Fully-qualified class name of the listener type. */
      String type();
    
      /** Enum which declares the listener callback methods. Mutually exclusive to {@link #method()}. */
      Class<? extends Enum<?>> callbacks() default NONE.class;
    
      /**
       * Method data for single-method listener callbacks. Mutually exclusive with {@link #callbacks()}
       * and an error to specify more than one value.
       */
      ListenerMethod[] method() default { };
    
      /** Default value for {@link #callbacks()}. */
      enum NONE { }
    }

    可以很清晰的看到,@ListenerClass是一个元注解,也是一个运行时注解,其有几个方法,这里我们先不介绍,后面在详细分析。

    回到parseListenerAnnotation,我们上面分析到拿到指定注解类型的对象。

    那么接下来是反射对象,得到注解的value()方的返回值类型,判断是否是int[] 类型,如果符合,则继续反射调用invoke得到方法的具体返回值,注意,这个返回值就是R类中的id值。
    然后还需要对被标记的元素对象进行适用校验,这就不在赘述,之前已经分析过。

    得到了id数组,对数组中的id值做重叠校验。
    再接下来,是对指定注解上的@ListenerClass注解进行解析,这里分析的是@Onclick注解上的@ListenerClass。

    为了思路清晰,我们这里在贴一下部分代码:

    ListenerClass listener = annotationClass.getAnnotation(ListenerClass.class);
    ListenerMethod method;
    ListenerMethod[] methods = listener.method();
    if (methods.length > 1) {
      throw new IllegalStateException(String.format("Multiple listener methods specified on @%s.",
          annotationClass.getSimpleName()));
    } else if (methods.length == 1) {
      if (listener.callbacks() != ListenerClass.NONE.class) {
        throw new IllegalStateException(
            String.format("Both method() and callback() defined on @%s.",
                annotationClass.getSimpleName()));
      }
      method = methods[0];
    } else {
      Method annotationCallback = annotationClass.getDeclaredMethod("callback");
      Enum<?> callback = (Enum<?>) annotationCallback.invoke(annotation);
      Field callbackField = callback.getDeclaringClass().getField(callback.name());
      method = callbackField.getAnnotation(ListenerMethod.class);
      if (method == null) {
        throw new IllegalStateException(
            String.format("No @%s defined on @%s's %s.%s.", ListenerMethod.class.getSimpleName(),
                annotationClass.getSimpleName(), callback.getDeclaringClass().getSimpleName(),
                callback.name()));
      }
    }

    首先,获取@ListenerClass注解对象中method方法的返回值,method方法的返回值类型是@ListenerMethod数组。先来看看@ListenerMethod注解的定义:

    @Retention(RUNTIME) @Target(FIELD)
    public @interface ListenerMethod {
      /** Name of the listener method for which this annotation applies. */
      String name();
    
      /** List of method parameters. If the type is not a primitive it must be fully-qualified. */
      String[] parameters() default { };
    
      /** Primitive or fully-qualified return type of the listener method. May also be {@code void}. */
      String returnType() default "void";
    
      /** If {@link #returnType()} is not {@code void} this value is returned when no binding exists. */
      String defaultReturn() default "null";
    }

    @ListenerMethod注解是一个运行时注解,其适用于变量Field,包含方法name(回调方法名称),parameters(回调方法参数类型),returnType(回调方法返回值类型),比如这里@Onclick注解的回调方法是OnClickListener接口中的doOnclick方法。
    再回到@ListenenrClass的method方法,我们得到的是一个@ListenerMethod[ ] 数组,然后对这个数组进行校验。

    首先,这个数组必须满足length<=1,当length==1的时候,就是我们这里分析的例如@Onclick类型的注解,我们只需要拿到数组中唯一的一个值,也就是一个@ListernerMethod对象。而如果length<1,则是需要另行判断注解中的callback方法的返回值,callback的返回值是一个Enum枚举类型对象,callback方法适用于具有多个回调方法的接口,譬如OnTextChangListener接口,其对应的注解类型是@OnTextChanged注解,而@OnTextChanged注解中定义了一个Callback类型的枚举,其有3个实例,分别被@ListenerMethod标记,其中callback方法返回值默认为其中的Callback.TEXT_CHANGED实例。和length==1的时候一样,我们同样也需要拿到一个封装了回调方法信息的@ListenerMethod对象,具体就是这段代码:

    Annotation annotation = element.getAnnotation(annotationClass);
    Method annotationCallback = annotationClass.getDeclaredMethod("callback");
    //反射Annotation注解对象callback方法,拿到返回值,是一个枚举常量
    Enum<?> callback = (Enum<?>) annotationCallback.invoke(annotation);
    //获取枚举常量的枚举类型的Class对象
    //获取指定枚举名称name的实例(枚举成员变量)的反射-Field
    Field callbackField = callback.getDeclaringClass().getField(callback.name());
    //获取枚举常量上的@ListenerMethod注解信息对象
    ListenerMethod method = callbackField.getAnnotation(ListenerMethod.class);

    到这里,拿到了id,拿到了封装了回调方法的@ListenerMethod对象,接下来,还需要校验被注解标记的方法元素executableElement的方法参数、返回值类型是否和@ListenerMethod中定义的一致。如下代码:

    // Verify that the method has equal to or less than the number of parameters as the listener.
    List<? extends VariableElement> methodParameters = executableElement.getParameters();
    if (methodParameters.size() > method.parameters().length) {
      error(element, "@%s methods can have at most %s parameter(s). (%s.%s)",
          annotationClass.getSimpleName(), method.parameters().length,
          enclosingElement.getQualifiedName(), element.getSimpleName());
      hasError = true;
    }
    
    // Verify method return type matches the listener.
    TypeMirror returnType = executableElement.getReturnType();
    if (returnType instanceof TypeVariable) {
      TypeVariable typeVariable = (TypeVariable) returnType;
      returnType = typeVariable.getUpperBound();
    }
    if (!returnType.toString().equals(method.returnType())) {
      error(element, "@%s methods must have a '%s' return type. (%s.%s)",
          annotationClass.getSimpleName(), method.returnType(),
          enclosingElement.getQualifiedName(), element.getSimpleName());
      hasError = true;
    }

    然后还需要对executableElement的参数,按照@ListenerMethod中parameters方法定义的顺序,另行封装为一个Parameter[]数组,而Parameter封装了接口回调方法参数定义的顺序position和方法元素的参数类型TypeName,我们看代码:

    List<? extends VariableElement> methodParameters = executableElement.getParameters();
    //...
    Parameter[] parameters = Parameter.NONE;
    if (!methodParameters.isEmpty()) {
      parameters = new Parameter[methodParameters.size()];
      BitSet methodParameterUsed = new BitSet(methodParameters.size());
      String[] parameterTypes = method.parameters();
      for (int i = 0; i < methodParameters.size(); i++) {
        VariableElement methodParameter = methodParameters.get(i);
        TypeMirror methodParameterType = methodParameter.asType();
        if (methodParameterType instanceof TypeVariable) {
          TypeVariable typeVariable = (TypeVariable) methodParameterType;
          //泛型类型提升(如<K extends A>,则返回A类型)
          methodParameterType = typeVariable.getUpperBound();
        }
    
        for (int j = 0; j < parameterTypes.length; j++) {
          if (methodParameterUsed.get(j)) {
            continue;
          }
          if ((isSubtypeOfType(methodParameterType, parameterTypes[j])
                  && isSubtypeOfType(methodParameterType, VIEW_TYPE))
              || isTypeEqual(methodParameterType, parameterTypes[j])
              || isInterface(methodParameterType)) {
            //关键代码,校验类型,然后Parameter封装
            parameters[i] = new Parameter(j, TypeName.get(methodParameterType));
            methodParameterUsed.set(j);
            break;
          }
        }
        //...
      }

    最后,还需要将方法元素和回调方法的相关信息做最后一次封装,看代码。

    MethodViewBinding binding = new MethodViewBinding(name, Arrays.asList(parameters), required);
    BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    for (int id : ids) {
      QualifiedId qualifiedId = elementToQualifiedId(element, id);
      if (!builder.addMethod(getId(qualifiedId), listener, method, binding)) {
        error(element, "Multiple listener methods with return value specified for ID %d. (%s.%s)",
            id, enclosingElement.getQualifiedName(), element.getSimpleName());
        return;
      }
    }

    看这代码,是不是觉得和之前的FieldAnimationBinding有点类似呢?命名方式都是XxxBinding,因为每一种不同注解绑定的元素以及元素的类型都不一样,需要封装的信息自然也就不一样,所以篇幅原因就不一一解析了,我们只需要大概知道是为了之后的javapoet生成java文件做准备就可以了,有兴趣的可以自行遨游一波。

到这里我们的遨游就先差不多了,下一阶段,我们开始在ButterKnife中遨游:javapoet库如何利用注解解析器封装的信息生成出我们需要的Java文件以在编译时期就可以绑定指定的Target,我们下一篇再见。

猜你喜欢

转载自blog.csdn.net/weixiao1999/article/details/79809231
今日推荐