ButterKnife源碼解析(1)

ButterKnife是經過註解進行動態注入的,若是你對註解還不太瞭解的話,能夠先看上篇有關注解的文章註解java

ButterKnife的工做原理通常分爲兩步:app

  • 經過@Bind(R.id.),@OnClick(R.id.)等註解在編譯的時候動態生成java文件,生成java文件編譯器會將它們編譯成對應的class文件
  • 經過ButterKnife.bind(this)等相似方法將ID與對應的上下文綁定在一塊兒。

首先來看最基本的註解類:ide

@Retention(CLASS) @Target(FIELD)
public @interface Bind {
 /** View ID to which the field will be bound. */
int[] value();
}

這個就是最經常使用的Bind註解,通常用它來標識要綁定的view,在這裏能夠清楚的看到,這個view是在編譯的時候起做用的,而且它只能用來標識成員變量。這就表示咱們能夠在編譯的時候解析它,從而生成java源代碼。函數

ButterKnife的註解解析器爲ButterknifeProcessor,它的最主要方法以下:oop

//初始化
  @Override public synchronized void init(ProcessingEnvironment env) {
    super.init(env);

    elementUtils = env.getElementUtils();
    typeUtils = env.getTypeUtils();
    filer = env.getFiler();
  }

//在這裏定義支持的註解類
  @Override public Set<String> getSupportedAnnotationTypes() {
    Set<String> types = new LinkedHashSet<String>();

    types.add(Bind.class.getCanonicalName());

    for (Class<? extends Annotation> listener : LISTENERS) {
      types.add(listener.getCanonicalName());
    }

    types.add(BindBool.class.getCanonicalName());
    types.add(BindColor.class.getCanonicalName());
    types.add(BindDimen.class.getCanonicalName());
    types.add(BindDrawable.class.getCanonicalName());
    types.add(BindInt.class.getCanonicalName());
    types.add(BindString.class.getCanonicalName());

    return types;
  }

//對註解進行解析
  @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);

    for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingClass bindingClass = entry.getValue();

      try {
        JavaFileObject jfo = filer.createSourceFile(bindingClass.getFqcn(), typeElement);
        Writer writer = jfo.openWriter();
        writer.write(bindingClass.brewJava());
        writer.flush();
        writer.close();
      } catch (IOException e) {
        error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
            e.getMessage());
      }
    }

    return true;
  }

全部經過ButterKnife註解的元素都會走到process方法,在process方法裏,首先經過findAndParseTargets(env)方法將全部具備註解的類的信息挨個放在BindingClass裏面,最後造成以TypeElement爲鍵,BindingClass爲值的鍵值對。ui

接下來,就只須要循環遍歷這個鍵值對,而後根據TypeElement和BindingClass裏面的信息生成對應的java類。例如MainActivity生成的類即爲MainActivity$$ViewBinder類。this

下面來看看findAndParseTargets(env)方法裏面:rest

private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<TypeElement, BindingClass>();
    Set<String> erasedTargetNames = new LinkedHashSet<String>();

    // Process each @Bind element.
    for (Element element : env.getElementsAnnotatedWith(Bind.class)) {
      try {
        parseBind(element, targetClassMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, Bind.class, e);
      }
    }

    // Process each annotation that corresponds to a listener.
    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()) {
      String parentClassFqcn = findParentFqcn(entry.getKey(), erasedTargetNames);
      if (parentClassFqcn != null) {
        entry.getValue().setParentViewBinder(parentClassFqcn + SUFFIX);
      }
    }

    return targetClassMap;
  }

這個方法作的主要工做就是將各註解進行分拆遍歷,而且進行解析,最後將解析的結果放入targetClassMap,這裏重點看Bind註解的解析:code

private void parseBind(Element element, Map<TypeElement, BindingClass> targetClassMap,
      Set<String> erasedTargetNames) {
    // Verify common generated code restrictions.
    if (isInaccessibleViaGeneratedCode(Bind.class, "fields", element)
        || isBindingInWrongPackage(Bind.class, element)) {
      return;
    }

    TypeMirror elementType = element.asType();
    if (elementType.getKind() == TypeKind.ARRAY) {
      parseBindMany(element, targetClassMap, erasedTargetNames);
    } else if (LIST_TYPE.equals(doubleErasure(elementType))) {
      parseBindMany(element, targetClassMap, erasedTargetNames);
    } else if (isSubtypeOfType(elementType, ITERABLE_TYPE)) {
      error(element, "@%s must be a List or array. (%s.%s)", Bind.class.getSimpleName(),
          ((TypeElement) element.getEnclosingElement()).getQualifiedName(),
          element.getSimpleName());
    } else {
      parseBindOne(element, targetClassMap, erasedTargetNames);
    }
  }

在這裏能夠看到ButterKnife會根據值的不一樣採起不一樣的解析方式,在這裏咱們看下最通用的解析方式,即parseBindOne(element,targetClassMap,erasedTargetNames),這也是解析器最核心的地方orm

private void parseBindOne(Element element, Map<TypeElement, BindingClass> targetClassMap,
      Set<String> erasedTargetNames) {
    boolean hasError = false;
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Verify that the target type extends from View.
    TypeMirror elementType = element.asType();
    if (elementType.getKind() == TypeKind.TYPEVAR) {
      TypeVariable typeVariable = (TypeVariable) elementType;
      elementType = typeVariable.getUpperBound();
    }
    if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
      error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
          Bind.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName());
      hasError = true;
    }

    // Assemble information on the field.
    int[] ids = element.getAnnotation(Bind.class).value();
    if (ids.length != 1) {
      error(element, "@%s for a view must only specify one ID. Found: %s. (%s.%s)",
          Bind.class.getSimpleName(), Arrays.toString(ids), enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    if (hasError) {
      return;
    }

    int id = ids[0];
    BindingClass bindingClass = targetClassMap.get(enclosingElement);
    if (bindingClass != null) {
      ViewBindings viewBindings = bindingClass.getViewBinding(id);
      if (viewBindings != null) {
        Iterator<FieldViewBinding> iterator = viewBindings.getFieldBindings().iterator();
        if (iterator.hasNext()) {
          FieldViewBinding existingBinding = iterator.next();
          error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
              Bind.class.getSimpleName(), id, existingBinding.getName(),
              enclosingElement.getQualifiedName(), element.getSimpleName());
          return;
        }
      }
    } else {
      bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
    }

    String name = element.getSimpleName().toString();
    String type = elementType.toString();
    boolean required = isRequiredBinding(element);

    FieldViewBinding binding = new FieldViewBinding(name, type, required);
    bindingClass.addField(id, binding);

    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement.toString());
  }

在這個方法裏面,enclosingElement包含使用Bind註解類的信息,經過下面的方法獲取註解標註的值。

int[] ids = element.getAnnotation(Bind.class).value();

接下來的任務就是若是沒有建立BindingClass了(若是沒有的話),建立BindingClass類也很簡單,它的構造函數也只須要包名,類名等基本信息:

private BindingClass getOrCreateTargetClass(Map<TypeElement, BindingClass> targetClassMap,
      TypeElement enclosingElement) {
    BindingClass bindingClass = targetClassMap.get(enclosingElement);
    if (bindingClass == null) {
      String targetType = enclosingElement.getQualifiedName().toString();
      String classPackage = getPackageName(enclosingElement);
      String className = getClassName(enclosingElement, classPackage) + SUFFIX;

      bindingClass = new BindingClass(classPackage, className, targetType);
      targetClassMap.put(enclosingElement, bindingClass);
    }
    return bindingClass;
  }

以後,就將view的信息綁定在FieldViewBinding類的實例中,最後添加到對應的BindingClass實例中,這樣使用註解的實例中全部有關注解的信息及這個實體類自己的信息都會在這個BindingClass類的實體類中,ButterKnife接着會利用這個類來生成對應的java文件。

接下來綁定的步驟與上文講解的相似,基本上都是這個原理,在這裏不作贅述,當解析完成以後,全部使用註解的實例的相關信息都會存儲在BindingClass的集合內(targetClassMap)。接下來作的工做就是循環遍歷這個集合生成文件。

這裏包含敏感詞,點此查看原文.

這個方法首先生成包名,而後引入各個類,接下來生成類的主題,經過emitBindMethod(builder)方法生成類的bind(builder)方法,經過emitUnbindMethod(builder)方法生成類的unbind(builder)方法。

private void emitBindMethod(StringBuilder builder) {
  builder.append("  @Override ")
      .append("public void bind(final Finder finder, final T target, Object source) {\n");

  // Emit a call to the superclass binder, if any.
  if (parentViewBinder != null) {
    builder.append("    super.bind(finder, target, source);\n\n");
  }

  if (!viewIdMap.isEmpty() || !collectionBindings.isEmpty()) {
    // Local variable in which all views will be temporarily stored.
    builder.append("    View view;\n");

    // Loop over each view bindings and emit it.
    for (ViewBindings bindings : viewIdMap.values()) {
      emitViewBindings(builder, bindings);
    }

    // Loop over each collection binding and emit it.
    for (Map.Entry<FieldCollectionViewBinding, int[]> entry : collectionBindings.entrySet()) {
      emitCollectionBinding(builder, entry.getKey(), entry.getValue());
    }
  }

  if (!resourceBindings.isEmpty()) {
    builder.append("    Resources res = finder.getContext(source).getResources();\n");

    for (FieldResourceBinding binding : resourceBindings) {
      builder.append("    target.")
          .append(binding.getName())
          .append(" = res.")
          .append(binding.getMethod())
          .append('(')
          .append(binding.getId())
          .append(");\n");
    }
  }

  builder.append("  }\n");
}


 private void emitViewBindings(StringBuilder builder, ViewBindings bindings) {
  builder.append("    view = ");

  List<ViewBinding> requiredViewBindings = bindings.getRequiredBindings();
  if (requiredViewBindings.isEmpty()) {
    builder.append("finder.findOptionalView(source, ")
        .append(bindings.getId())
        .append(", null);\n");
  } else {
    if (bindings.getId() == View.NO_ID) {
      builder.append("target;\n");
    } else {
      builder.append("finder.findRequiredView(source, ")
          .append(bindings.getId())
          .append(", \"");
      emitHumanDescription(builder, requiredViewBindings);
      builder.append("\");\n");
    }
  }

  emitFieldBindings(builder, bindings);
  emitMethodBindings(builder, bindings);
}

能夠看到這裏先讀取每一個綁定view的配置(ViewBindings),接下來綁定view,首先找到activity裏面對應的view,而後經過emitFieldBindings(builder,bindings)將成員變量的view注入到activity中,emitMethodBindings(builder.bindings)則用來綁定事件。這裏會在下一篇文章詳細解說。

當這一切都結束以後,就能夠生成文件了,在編譯階段生成的java文件也會生成class文件,在這裏爲了方便讀者理解,給出一個最基本的MainActivity類和生成的MainActivity&&ViewBindler類的源代碼:

這裏包含敏感詞,點此查看原文.

到這裏,咱們就將ButterKnife編譯器生成文件部分的代碼分析完畢了,更多的細節還請讀者本身閱讀ButterKnife源碼,若是有什麼疏漏,還望指出,下一次咱們來分析ButterKnife與上下文綁定。

相關文章
相關標籤/搜索