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

  • 相关阅读:
    boost::asio在VS2008下的编译错误
    Java集合框架——接口
    ACM POJ 3981 字符串替换(简单题)
    ACM HDU 1042 N!(高精度计算阶乘)
    OneTwoThree (Uva)
    ACM POJ 3979 分数加减法(水题)
    ACM HDU 4004 The Frog's Games(2011ACM大连赛区第四题)
    Hexadecimal View (2011ACM亚洲大连赛区现场赛D题)
    ACM HDU 4002 Find the maximum(2011年大连赛区网络赛第二题)
    ACM HDU 4001 To Miss Our Children Time (2011ACM大连赛区网络赛)
  • 原文地址:https://www.cnblogs.com/Android-Alvin/p/12103080.html
Copyright © 2011-2022 走看看