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

好,我们接着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源码并掌握原理(三)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容