Butterknife 源码剖析(二)

作者: spiritTalk | 来源:发表于2016-03-30 02:03 被阅读757次

源码剖析——编译期解析注解、生成java代码流程

在上一篇 ButterKnife的工作流程 中我们分析了ButterKnife.bind()的流程,那么编译期是如何生成MainActivity$$ViewBinder.java呢?答案将在本篇揭晓。

编译时 Annotation 指 @Retention 为 CLASS 的 Annotation,甴 apt(Annotation Processing Tool) 解析自动解析。需要做的:
1、自定义类继承自 AbstractProcessor
2、重写其中的 process 函数
其实就是 apt(Annotation Processing Tool) 在编译时自动查找所有继承自 AbstractProcessor 的类,然后调用他们的 process 方法去处理。

ButterKnife中继承AbstractProcessor的是ButterKnifeProcessor类:

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

 @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 {
        /** 通过bindingClass写进文件,从而生成辅助类。**/
        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;
  }

来看findAndParseTargets()方法:

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.
    /** 解析每个@Bind元素 **/
    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);
    }

    // Process each @BindBool element.
    /** 解析每个@BindBool元素 **/
    for (Element element : env.getElementsAnnotatedWith(BindBool.class)) {
      try {
        parseResourceBool(element, targetClassMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindBool.class, e);
      }
    }
   /** 解析@BindColor **/
   /** 解析@BindDimen **/
   // ……
}

我们来看parseBind()方法:

private void parseBind(Element element, Map<TypeElement, BindingClass> targetClassMap,
      Set<String> erasedTargetNames) {
    // Verify common generated code restrictions.
    /**
      * isInaccessibleViaGeneratedCode()中验证了:
      * 1、修饰符不能为private或static;2、不能用于非Class类;3、当前类修饰符不能为private
      *
      * isBindingInWrongPackage()验证了,注解Class不能位于Android framework package或Java framework package。
      */
    if (isInaccessibleViaGeneratedCode(Bind.class, "fields", element)
        || isBindingInWrongPackage(Bind.class, element)) {
      return;
    }

    TypeMirror elementType = element.asType();
    if (elementType.getKind() == TypeKind.ARRAY) {
      /** Array类型 **/
      parseBindMany(element, targetClassMap, erasedTargetNames);
    } else if (LIST_TYPE.equals(doubleErasure(elementType))) {
      /** //list类型,@Bind({ R.id.consume_checkbox, R.id.expired_checkbox, R.id.latest_push_checkbox}) List<CheckedTextView> checkedTextViews; **/
      parseBindMany(element, targetClassMap, erasedTargetNames);
    } else if (isSubtypeOfType(elementType, ITERABLE_TYPE)) {
      /** java.lang.Iterable<?>的子类型 **/
      error(element, "@%s must be a List or array. (%s.%s)", Bind.class.getSimpleName(),
          ((TypeElement) element.getEnclosingElement()).getQualifiedName(),
          element.getSimpleName());
    } else {
      /** 解析单个@Bind **/
      parseBindOne(element, targetClassMap, erasedTargetNames);
    }
  }

我们先来看parseBindOne()方法:

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();
    }
    /** 必须为view类型的子类或者是接口 **/
    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. 
    /** 只能有一个资源id。**/
    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()) {/** 当前资源id已经绑定过 **/
          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 **/
      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());
  }

然后进入getOrCreateTargetClass()方法:

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);
      /** 生成的辅助类名为:className + "$$ViewBinder" **/
      String className = getClassName(enclosingElement, classPackage) + SUFFIX;

      bindingClass = new BindingClass(classPackage, className, targetType);
      /** 放入缓存 **/
      targetClassMap.put(enclosingElement, bindingClass);
    }
    return bindingClass;
  }

上面主要是解析了注解中的@Bind(其他流程类似),然后放到targetClassMap中。

一开始我们分析知道最终是通过BindingClass写入文件,生成辅助类的,那么我们接着来看BindingClass:

String brewJava() {
    StringBuilder builder = new StringBuilder();
    builder.append("// Generated code from Butter Knife. Do not modify!\n");
    builder.append("package ").append(classPackage).append(";\n\n");

    if (!resourceBindings.isEmpty()) {
      builder.append("import android.content.res.Resources;\n");
    }
    if (!viewIdMap.isEmpty() || !collectionBindings.isEmpty()) {
      builder.append("import android.view.View;\n");
    }
    builder.append("import butterknife.ButterKnife.Finder;\n");
    if (parentViewBinder == null) {
      builder.append("import butterknife.ButterKnife.ViewBinder;\n");
    }
    builder.append('\n');

    builder.append("public class ").append(className);
    builder.append("<T extends ").append(targetClass).append(">");

    /** parentViewBinder不为空则继承parentViewBinder,为空则实现ViewBinder **/
    if (parentViewBinder != null) {
      builder.append(" extends ").append(parentViewBinder).append("<T>");
    } else {
      builder.append(" implements ViewBinder<T>");
    }
    builder.append(" {\n");
    /** 生成绑定方法 **/
    emitBindMethod(builder);
    builder.append('\n');
    /** 生成解绑方法 **/
    emitUnbindMethod(builder);

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

这样就生成了我们在第一篇中所看到的MainActivity$$ViewBinder.java类:

import android.view.View;
import butterknife.ButterKnife.Finder;
import butterknife.ButterKnife.ViewBinder;

public class MainActivity$$ViewBinder<T extends com.spirittalk.rxjavatraining.MainActivity> implements ViewBinder<T> {
  @Override public void bind(final Finder finder, final T target, Object source) {
    View view;
    view = finder.findRequiredView(source, 2131492970, "field 'mButton' and method 'clickButton'");
    target.mButton = finder.castView(view, 2131492970, "field 'mButton'");
    view.setOnClickListener(
      new butterknife.internal.DebouncingOnClickListener() {
        @Override public void doClick(
          android.view.View p0
        ) {
          target.clickButton();
        }
      });
  }

  @Override public void unbind(T target) {
    target.mButton = null;
  }
}

转载请标明出处:http://www.jianshu.com/p/4c38616af3a5

相关文章

网友评论

    本文标题:Butterknife 源码剖析(二)

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