学习Butterknife的一点心得(系列)二

Android Butterknife 是一款不错的注解框架,和咱们在运行期处理注解的思想不一样,它是在编译期的时候收集注解信息。在系列一中咱们讲解了编译注解的原理,ButterKnife的原理和那个例子差很少。java

理解这个ButterKnife先从应用讲起。先在项目中添加butterKnife.jar,这个能够从github上找到。git

通常在activity声明一个View:github

第一步:就能够这么写框架

@Bind(R.id.iv_title_left)maven

ImageView iv_title_left;ide

这样省去了findViewById方法。ui

第二步:在onCreate()方法中,添加这行代码:ButterKnife.bind(this);记得要在setContentView(R.layout.activity_main);以后写。。通过以上两步配置好了。那么有两个问题,在ButterKnife.bind()干了什么,框架是怎么处理这个注解的。this

那咱们带着问题来看他的源码,spa


一 编译期rest

咱们先找框架的注解(咱们用了Bind类型的注解,就找它的源码):

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

这个定义就符合咱们在用的Bind注解。

而后咱们找它的注解处理器:

接着看它process()方法:

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
1  Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);

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

    try {
3      bindingClass.brewJava().writeTo(filer);
    } catch (IOException e) {
      error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
          e.getMessage());
    }
  }

  return true;

targetClassMap的key为一个类元素,value为类中的注解元素信息。进入1中的findAndParseTarget()方法,由于方法中代码比较多,只贴一部分,:

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

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

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

根据几个for循环,咱们看到了

env.getElementsAnnotatedWith(Bind.class)

的相似写法,说明是在收集项目中被注解元素的信息。

而后咱们进入paseBind()看具体是怎么解析的:

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

咱们在应用Bind注解的时候,一般只用了一个ID,因此咱们进入paserBindOne():

方法代码比较多,就贴核心代码:

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();
TypeName type = TypeName.get(elementType);
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());


能够看到若是若是类对应的BindClass不存在的话,就建立一个。

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

能够看到根据注解元素的名字,类型(获取这些值调用的方法的含义,我在例子中解释了),建立了一个注解字段对应的FieldViewBinding,而后添加到bindingClass中。

如今切回到findPaseTargets()方法,其余对不一样注解类型的解析基本和这个上述对注解Bind解析的流程同样。执行完以后就收集到了全部的注解信息,暂存到targetClassMap中。


接着退回到process()方法,3行代码的意思为每个有注解的类调用brewJava()方法,接着调用writeTo(),查看源代码writeTo()发现是其javapoet的代码,而javapoet是一个生成java源文件的框架。(javapoet是butterKnife经过maven管理引用的一个jar包)。而后进入brewJava()方法看一下:

JavaFile brewJava() {
1  TypeSpec.Builder result = TypeSpec.classBuilder(className)
2      .addModifiers(PUBLIC)
3      .addTypeVariable(TypeVariableName.get("T", ClassName.bestGuess(targetClass)));

4  if (parentViewBinder != null) {
5    result.superclass(ParameterizedTypeName.get(ClassName.bestGuess(parentViewBinder),
6        TypeVariableName.get("T")));
7  } else {
8    result.addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, TypeVariableName.get("T")));
  }

9  result.addMethod(createBindMethod());
10  result.addMethod(createUnbindMethod());

  return JavaFile.builder(classPackage, result.build())
      .addFileComment("Generated code from Butter Knife. Do not modify!")
      .build();
}

看生成了JavaFile对象,先不分析它的源码,看看它生成的文件,文件路径为:

Weather\.apt_generated\com\hsj\weather\ui\activity\MainActivity$$ViewBinder.java,Weather是个人项目名,

文件内容是:

public class MainActivity$$ViewBinder<T extends com.hsj.weather.ui.activity.MainActivity> implements ViewBinder<T> {
  @Override public void bind(final Finder finder, final T target, Object source) {
    View view;
    view = finder.findRequiredView(source, 2131361899, "field 'iv_title_left'");
    target.iv_title_left = finder.castView(view, 2131361899, "field 'iv_title_left'");
    view = finder.findRequiredView(source, 2131361901, "field 'iv_title_right'");
    target.iv_title_right = finder.castView(view, 2131361901, "field 'iv_title_right'");
    view = finder.findRequiredView(source, 2131361900, "field 'tv_title_center'");
    target.tv_title_center = finder.castView(view, 2131361900, "field 'tv_title_center'");
  }

  @Override public void unbind(T target) {
    target.iv_title_left = null;
    target.iv_title_right = null;
    target.tv_title_center = null;
  }
}

再回头看brewJava()方法,1-8行生成了这个源文件的类头,9行对应生成了bind()方法(根createBindMethod()这个方法名字能够猜出来)10行对应生成unBind()方法。具体怎么生成的能够查看源代码,挺简单的,这里不分析了。至此框架在编译期的处理结束了。

相关文章
相关标签/搜索