ButterKnife 从入门到精通 - 源码级分析(二)

914 阅读5分钟

上篇文章说到,ButterKnife是如何通过bind()方法来实现绑定试图、设置监听的。这一节给大家继续说说ButterKnife是如何生成ViewBinding的。

APT

类似于ButterKnife这种注入框架以前也有很多的,像xutils、afinal,包括Dagger(Dagger2重构前的版本),但是这些框架都是依赖于运行时注解和反射,严重影响效率。后来的框架(ButterKnife、Dagger2等)都是依赖于编译时注解,被称为APT(Android Processing Tool)技术。
APT的实现原理是这样的:定义需要的注解类的@Retention为CLASS,定义自己的Procesor继承自AbstractProcessor类,那么在代码编译时,编译器会自动调用Processor类的process方法。

Element

在正式分析之前,先简单了解一下Element类。
Elemet被称为元素类,它有以下几个常用的子类。

  • VariableElement //一般代表成员变量
  • ExecutableElement //一般代表类中的方法
  • TypeElement //一般代表代表类
  • PackageElement //一般代表Package

ButterKnifeProcessor

在ButterKnife中,类ButterKnifeProcessor继承自AbstractProcessor,提供给我们使用的注解的@Retention都为CLASS。
找到ButterKnifeProcessor类,代码较多,我们需要关注的只有4个方法

  • init() //初始化需要的数据
  • getSupportedAnnotationTypes() //返回支持的注解类型
  • getSupportedOptions //返回支持的源码版本
  • process() //逻辑处理,重点关注

先来看看init()

@Override 
public synchronized void init(ProcessingEnvironment env) {
    super.init(env);
    //获取sdk版本
    String sdk = env.getOptions().get(OPTION_SDK_INT);
    this.sdk = Integer.parseInt(sdk);
    //操作Element的工具类
    elementUtils = env.getElementUtils();
    //操作TypeMirror的工具类
    typeUtils = env.getTypeUtils();
    //用来创建辅助文件的
    filer = env.getFiler();
}

注释已经解释的很详细了,init()方法就是用来创建一些辅助类。getSupportedAnnotationTypes() 和 getSupportedOptions中的代码也很简单,这里就不细看了。

接着就看看它的process方法。

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

    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
        TypeElement typeElement = entry.getKey();
        BindingSet binding = entry.getValue();

        JavaFile javaFile = binding.brewJava(sdk);
        javaFile.writeTo(filer);
}
    return false;
}

首先一进这个函数就执行findAndParseTargets(env),来看看都做了那些工作:

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();

    scanForRClasses(env);

    ...

    // Process each @BindView element.
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
        parseBindView(element, builderMap, erasedTargetNames);
    }

     ...
    // Associate superclass binders with their subclass binders. This is a queue-based tree walk
    // which starts at the roots (superclasses) and walks to the leafs (subclasses).
    Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
        new ArrayDeque<>(builderMap.entrySet());
    Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
    while (!entries.isEmpty()) {
        Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();

        TypeElement type = entry.getKey();
        BindingSet.Builder builder = entry.getValue();

        TypeElement parentType = findParentType(type, erasedTargetNames);
        if (parentType == null) {
            bindingMap.put(type, builder.build());
        } else {
            BindingSet parentBinding = bindingMap.get(parentType);
        if (parentBinding != null) {
            builder.setParent(parentBinding);
            bindingMap.put(type, builder.build());
        } else {
            // Has a superclass binding but we haven't built it yet. Re-enqueue for later.
            entries.addLast(entry);
        }
      }
    }
    return bindingMap;
}

这里处理各个注解的代码较多,不一一贴出了,就以我们的例子中的BindView注解为例,其他的注解也是类似的。
上来先创建了两个容器,builderMap和erasedTargetNames,然后开始遍历所有被BindView注解的Element。(BindingSet类后文会有介绍)
在循环中,只调用了一个方法,parseBindView(element, builderMap, erasedTargetNames),注意这个方法并没有返回值。

private void parseBindView(Element element,Map<TypeElement,BindingSet.Builder> builderMap,Set<TypeElement> erasedTargetNames){
    //获取外围类元素
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    //获取视图的ID
    int id = element.getAnnotation(BindView.class).value();
    //在builderMap中查找BindingSet.Builder
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    if (builder != null) {
        //查找此ID是否有对应的数据
        String existingBindingName = builder.findExistingBindingName(getId(id));
        //如果有就返回
        if (existingBindingName != null) {
            return;
        }
    }else{//创建一个BindingSet.Builder,并保存到builderMap中
        builder = getOrCreateBindingBuilder(builderMap,enclosingElement);
    }
    String name = element.getSimpleName().toString();
    TypeName type = TypeName.get(elementType);
    boolean required = isFieldRequired(element);

    builder.addField(getId(id),new FieldViewBinding(name,type,required));

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

先获取外围类元素,再获取需要绑定的View的ID,接着是一些防止重复的判断,这里我们是第一次进入,肯定不会重复了。通过getOrCreateBindingBuilder(builderMap,enclosingElement)方法创建一个BindingSet.Builder,并保存到builderMap中。最后再将一些信息保存到builder中去。
回过头来接着看findAndParseTargets()方法,

Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries = new ArrayDeque<>(builderMap.entrySet());
Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();

将builder的entrySet()保存到一个队列中。接着又创建一个Map容器。

//遍历队列
while (!entries.isEmpty()) {
    Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();
    //
    TypeElement type = entry.getKey();
    BindingSet.Builder builder = entry.getValue();

    TypeElement parentType = findParentType(type, erasedTargetNames);
    if (parentType == null) {
        //调用builder.build();
        bindingMap.put(type, builder.build());
    } else {
        BindingSet parentBinding = bindingMap.get(parentType);
        if (parentBinding != null) {
            builder.setParent(parentBinding);
            //调用builder.build();
            bindingMap.put(type, builder.build());
        } else {
            // Has a superclass binding but we haven't built it yet. Re-enqueue for later.
            entries.addLast(entry);
        }
    }
}

这一步主要是调用了builder.build()方法将builderMap数据保存到bindingMap中,if-else判断主要是为了提高健壮性,就不带大家细看了。最后将bindingMap返回到process()方法中。

for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
    TypeElement typeElement = entry.getKey();
    BindingSet binding = entry.getValue();

    JavaFile javaFile = binding.brewJava(sdk);
    javaFile.writeTo(filer);

这一步,遍历取出相关信息,并生成文件(MainActivity_ViewBinding类),这里使用的是JavaPoet库去生成Java源文件的,你当然也可以使用别的方式去生成。

关于BindingSet

有的小伙伴可能会纠结于BindingSet类到底是什么,其实BindingSet类的作用很简单,一是通过BindingSet.builder收集信息,二是封装JavaPoet的代码。这种收集信息和生成Java源代码的方式,在ButterKnife不同的版本中也是不一样的,我们了解一下就可以了。

流程图

通过下面的流程图,加深理解一下生成MainActivity_ViewBinding的过程

流程图

ButterKnife运行的整个流程

到这里,我们就可以很清楚的了解到ButterKnife实现的整个流程了。

  1. 在MainActivity中通过注解,标记button和onClick方法
  2. 编译时执行ButterKnifePrcessor的Process方法,生成MainActivity_ViewBinding类
  3. 运行时,执行Bind()方法完成试图和监听的绑定

理清楚了,是不是很简单。