zoukankan      html  css  js  c++  java
  • IOC架构设计之ButterKnife源码&原理(二)中篇

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680
    承接上文IOC架构设计之ButterKnife源码&原理(二)上篇

    第二步、parseBindView的 获取值

     BindingClass bindingClass = targetClassMap.get(enclosingElement);
        if (bindingClass != null) {
          ViewBindings viewBindings = bindingClass.getViewBinding(getId(id));
          if (viewBindings != null && viewBindings.getFieldBinding() != null) {
            FieldViewBinding existingBinding = viewBindings.getFieldBinding();
            error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
                BindView.class.getSimpleName(), id, existingBinding.getName(),
                enclosingElement.getQualifiedName(), element.getSimpleName());
            return;
          }
        } else {
          bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
        }
    

    如果map(缓存)中已经有了就直接到到这个BindingClass实例。BindingClass这个我们后面还会再说。

    1.如果不为空,则根据id获取保存在bindingClass中的ViewBindings实例,
    如果viewBindings不为空且viewBindings.getFieldBinding不为空则抛出异常,什么意思呢?就是说一个id不能bind多次。
    2.如果为空,则bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
    获取并返回,参数是最初的那个map和父节点。

     private BindingClass getOrCreateTargetClass(Map<TypeElement, BindingClass> targetClassMap,
          TypeElement enclosingElement) {
        BindingClass bindingClass = targetClassMap.get(enclosingElement);
        //再次判断
        if (bindingClass == null) {
         //获取父节点的类型名字,这个不用太关系
          TypeName targetType = TypeName.get(enclosingElement.asType());
          if (targetType instanceof ParameterizedTypeName) {
            targetType = ((ParameterizedTypeName) targetType).rawType;
          }
    
     //获取该enclosingElement就是父节点所在的包名称
    
          String packageName = getPackageName(enclosingElement);
        //类名字
          String className = getClassName(enclosingElement, packageName);
          //根据包名称和类名称获取bindingClassName实体
          //并且加入了_ViewBinding 哈哈,有点意思是了。不是吗??
          ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");
    
          //是否是final 类 
          boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
    
          //创建了一个BindingClass实例
          bindingClass = new BindingClass(targetType, bindingClassName, isFinal);
          //加入集合,缓存
          targetClassMap.put(enclosingElement, bindingClass);
        }
        return bindingClass;
      }
    

    第三步、parseBindView

    //@BindView(R.id.word)
        // TextView word;  
        //name就是word
        String name = element.getSimpleName().toString();
         //类型的名字
        TypeName type = TypeName.get(elementType);
        //是否要求可为空
        boolean required = isFieldRequired(element);
         //生成FieldViewBinding实体
        FieldViewBinding binding = new FieldViewBinding(name, type, required);
    

    第四步

    添加到bindingClass中的成员变量的实体的集合中,方便生成java源文件也就是xxxxx_ViewBinding文件的成员变量的初始化存在
    bindingClass.addField(getId(id), binding);
    

    其它的注解都是一样的。至此查找并解析成员变量的流程就完了。
    接下来是处理控件事件的监听的流程。

    注解事件源码流程分析(OnClick,OnItemClick等)
    我们回到findAndParseTargets方法。

       //... 省略成员变量的注解
    
     // Process each annotation that corresponds to a listener.
    
        //处理方法的比如一些OnClick,OnItemClick等
        for (Class<? extends Annotation> listener : LISTENERS) {
          findAndParseListener(env, listener, targetClassMap, erasedTargetNames);
        }
    
        // Try to find a parent binder for each.
        for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
          TypeElement parentType = findParentType(entry.getKey(), erasedTargetNames);
          if (parentType != null) {
            BindingClass bindingClass = entry.getValue();
            BindingClass parentBindingClass = targetClassMap.get(parentType);
            bindingClass.setParent(parentBindingClass);
          }
        }
    
        return targetClassMap;
       }
    

    处理注解事件同样也分为查找和解析2个大步骤。
    LISTENERS是butterknife支持的注解集合

      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 //
      );
    
     private void findAndParseListener(RoundEnvironment env,
          Class<? extends Annotation> annotationClass, Map<TypeElement, BindingClass> targetClassMap,
          Set<TypeElement> erasedTargetNames) {
        for (Element element : env.getElementsAnnotatedWith(annotationClass)) {
         //检查合法性问题
          if (!SuperficialValidation.validateElement(element)) continue;
          try {
             //解析注解
            parseListenerAnnotation(annotationClass, element, targetClassMap, erasedTargetNames);
          } catch (Exception e) {
            StringWriter stackTrace = new StringWriter();
            e.printStackTrace(new PrintWriter(stackTrace));
    
            error(element, "Unable to generate view binder for @%s.
    
    %s",
                annotationClass.getSimpleName(), stackTrace.toString());
          }
        }
      }
    

    我们看一下parseListenerAnnotation方法,传入了注解类annotationClass,该节点element,最初的那个集合targetClassMap。 比较长,我们在方法里注释效果会比较好。

    private void parseListenerAnnotation(Class<? extends Annotation> annotationClass, Element element,
          Map<TypeElement, BindingClass> targetClassMap, Set<TypeElement> erasedTargetNames)
          throws Exception {
        // This should be guarded by the annotation's @Target but it's worth a check for safe casting.
        //必需是方法类型的,节点元素为ExecutableElement
        if (!(element instanceof ExecutableElement) || element.getKind() != METHOD) {
          throw new IllegalStateException(
              String.format("@%s annotation must be on a method.", annotationClass.getSimpleName()));
        }
    
    //方法对应的是ExecutableElement,前文我们已经简单的说明了一下
        ExecutableElement executableElement = (ExecutableElement) element;
        //获取父节点
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    
        // Assemble information on the method.
        // 获取注解信息
        Annotation annotation = element.getAnnotation(annotationClass);
        //该注解value方法,每一个注解都有(butterknife提供的都是数组)
        //为什么是数组?因为支持下面这种
          @OnClick({R.id.hello,R.id.hello2}) 
          void sayHello() {
          }
          //反射注解方法value
        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);
    
         //一个注解的方法不能有形同的id,or抛出异常
        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.class注解,什么意思呢?就是   
         //butterknife提供的方法注解 包含了另外一个注解
         //可以跳过代码看下面的文字说明。
        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的合法性,里面有个Optional注解
        for (int id : ids) {
           //id 为 -1 ,不合法     
          if (id == NO_ID.value) {
            if (ids.length == 1) {
            //一个参数情况,且方法的参数适用了Optional注解,则抛出异常
              if (!required) {
                error(element, "ID-free binding must not be annotated with @Optional. (%s.%s)",
                    enclosingElement.getQualifiedName(), element.getSimpleName());
                hasError = true;
              }
            } else {
              error(element, "@%s annotation contains invalid ID %d. (%s.%s)",
                  annotationClass.getSimpleName(), id, enclosingElement.getQualifiedName(),
                  element.getSimpleName());
              hasError = true;
            }
          }
        }
    
    
         //获取实现的方法
        ListenerMethod method;
        ListenerMethod[] methods = listener.method();
    
        // methods就是OnItemClick 注解的ListenerClass注解的初始化值,
        比如下面这种,肯定是个
        //    method = @ListenerMethod(
        //   name = "onItemClick",
        //   parameters = {
        //   "android.widget.AdapterView<?>",
        //    "android.view.View",
        //        "int",
        //        "long"
        //    }
        //  )
    
    
    
    
    
    
        if (methods.length > 1) {
        //抛异常,不可能走到这因为butterknife提供的都是1个默认的,能骗到我,我可是上过小学的人,哈哈
          throw new IllegalStateException(String.format("Multiple listener methods specified on @%s.",
              annotationClass.getSimpleName()));
        } else if (methods.length == 1) {
    
    //如果有method属性值即这种onItemClick,则callbacks必须为空,也就是2者不能同时使用
    
          if (listener.callbacks() != ListenerClass.NONE.class) {
            throw new IllegalStateException(
                String.format("Both method() and callback() defined on @%s.",
                    annotationClass.getSimpleName()));
          }
          method = methods[0];
        } else {
        // 否则使用callback
        //反射ListenerClass注解中的callback方法
    
          Method annotationCallback = annotationClass.getDeclaredMethod("callback");
          Enum<?> callback = (Enum<?>) annotationCallback.invoke(annotation);
          Field callbackField = callback.getDeclaringClass().getField(callback.name());
          method = callbackField.getAnnotation(ListenerMethod.class);
    
          //如果没有ListenerMethod.class注解 抛出异常,也就是说你使用了callback,则必须提供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()));
          }
        }
    
        //检查方法的合法性,就是你使用的注解的方法的参数不能butterknife的参数的个数(也就是android系统的那种)
    
        // 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;
        }
    
    //检查返回值,就是你使用的注解的方法的参数不能butterknife的参数的个数(也就是android系统的那种)
    
        // 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])
                  || 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("
    
      Parameter #")
                    .append(j + 1)
                    .append(": ")
                    .append(methodParameters.get(j).asType().toString())
                    .append("
        ");
                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("
    
    Methods may have up to ")
                  .append(method.parameters().length)
                  .append(" parameter(s):
    ");
              for (String parameterType : method.parameters()) {
                builder.append("
      ").append(parameterType);
              }
              builder.append(
                  "
    
    These may be listed in any order but will be searched for from top to bottom.");
              error(executableElement, builder.toString());
              return;
            }
          }
        }
    
    //最后构造MethodViewBinding实体,形成方法的实体
    
        MethodViewBinding binding = new MethodViewBinding(name, Arrays.asList(parameters), required);
        //构造BindingClass
        BindingClass bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
        for (int id : ids) {
    
        //将生成的方法加入到bindingClass的方法集合中,一切都是为了生存java代码而准备。
    
          if (!bindingClass.addMethod(getId(id), 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);
      }
    

    ListenerClass/ListenerMethod 注解说明

    @Target(METHOD)
    @Retention(CLASS)
    @ListenerClass(
        targetType = "android.widget.AdapterView<?>",
        setter = "setOnItemClickListener",
        type = "android.widget.AdapterView.OnItemClickListener",
        method = @ListenerMethod(
            name = "onItemClick",
            parameters = {
                "android.widget.AdapterView<?>",
                "android.view.View",
                "int",
                "long"
            }
        )
    )
    public @interface OnItemClick {
      /** View IDs to which the method will be bound. */
      @IdRes int[] value() default { View.NO_ID };
    }
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    
    import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
    import static java.lang.annotation.RetentionPolicy.RUNTIME;
    
    @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 { }
    }
    
    package butterknife.internal;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    
    import static java.lang.annotation.ElementType.FIELD;
    import static java.lang.annotation.RetentionPolicy.RUNTIME;
    
    @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";
    }
    

    可以把这3个整体来看。ListenerMethod 这个注解包含了方法的返回值,名字,参数,是实现的那些方法;ListenerClass是set的那些方法属性,包含setter等,我们看到了OnItemClick设置的值就是我们平常写的那种,至此我们的findAndParseTargets方法算是走完了。
    原文链接:https://blog.csdn.net/ta893115871/article/details/52497396
    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680

  • 相关阅读:
    【NOIP模拟赛】正方形大阵
    【优化王牌】二分查找
    【Ah20160703】咏叹 By C_SUNSHINE
    【NOIP模拟赛】lover——心上人
    【小奇模拟赛】小奇挖矿2
    【NOIP模拟赛】工资
    关于博客装修的说明
    【快速处理】分块算法
    【集训】 考试笔记
    【HDNOIP】HD201404最短路径
  • 原文地址:https://www.cnblogs.com/Android-Alvin/p/12103080.html
Copyright © 2011-2022 走看看