Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 深入理解ButterKnife源碼並掌握原理(二)

深入理解ButterKnife源碼並掌握原理(二)

編輯:關於Android編程

好,我們接著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 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 listener : LISTENERS) {
      findAndParseListener(env, listener, targetClassMap, erasedTargetNames);
    }

    // Try to find a parent binder for each.
    for (Map.Entry 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> 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 annotationClass, Map targetClassMap,
      Set 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 annotationClass, Element element,
      Map targetClassMap, Set 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 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> 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方法算是走完了。裡面有很多細節。
為什麼要分析有關細節呢?可以學習下大神的方法和理解有關處理的細節啊,哈哈。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved