美文网首页技术收藏android技术收藏开源框架源码解析
深入理解ButterKnife源码并掌握原理(二)

深入理解ButterKnife源码并掌握原理(二)

作者: 后厂村追寻 | 来源:发表于2016-09-10 23:20 被阅读472次

    好,我们接着parseBindView的步骤3 ,忘记了在哪里了,咦。


    不知所措

    可以看下上一篇,哈哈。
    步骤3

       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的步骤3就完了。
    步骤4:parseBindView步骤4

    
        //@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);
        
    

    步骤5.

    添加到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.\n\n%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("\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实体,形成方法的实体
    
       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方法算是走完了。里面有很多细节。
    为什么要分析有关细节呢?可以学习下大神的方法和理解有关处理的细节啊,哈哈。
    深入理解ButterKnife源码并掌握原理(三)

    相关文章

      网友评论

        本文标题:深入理解ButterKnife源码并掌握原理(二)

        本文链接:https://www.haomeiwen.com/subject/epquettx.html