ButterKnife源码分析(二)

来源:互联网 发布:淘宝查询信誉网站 编辑:程序博客网 时间:2024/04/30 07:17

ButterKnife源码分析(二)

简介

上一篇文章 (ButterKnife源码分析(一))我们分析了ButterKnife是如何将@BindView转换成我们熟悉的findViewById的。
在源码分析的过程中我们提到了一个类名叫MainActivity_ViewBinding,他与MainActivity在同一个包下。是在编译的过程中由ButterKnife自动生成的。今天我们就来看看这个重要的MainActivity_ViewBinding类究竟是如何生成的。

源码网址:https://github.com/JakeWharton/butterknife
最新版本:8.5.1(最新版本的代码与老代码还是有很大变化的,不过好在基本思路没变)

目录

  • ButterKnife源码分析二
    • 简介
    • 目录
    • 举个栗子
    • 问题一 MainActivity_ViewBinding是如何生成的呢
      • findAndParseTargetsenv
        • 第一部分
        • 第二部分
      • bindingbrewJavasdk
      • javaFilewriteTofiler

举个栗子

我们还是举一个简单的例子:有一个MainActivity,他是这样的。

public class MainActivity extends AppCompatActivity {    @BindView(R.id.tv_content)    TextView tvContent;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ButterKnife.bind(this);    }}

编译之后,ButterKnife会生成一个这样的类:

// Generated code from Butter Knife. Do not modify!package com.example.admin.butterknifedemo;import android.support.annotation.CallSuper;import android.support.annotation.UiThread;import android.view.View;import android.widget.TextView;import butterknife.Unbinder;import butterknife.internal.Utils;import java.lang.IllegalStateException;import java.lang.Override;public class MainActivity_ViewBinding implements Unbinder {  private MainActivity target;  @UiThread  public MainActivity_ViewBinding(MainActivity target) {    this(target, target.getWindow().getDecorView());  }  @UiThread  public MainActivity_ViewBinding(MainActivity target, View source) {    this.target = target;    target.tvContent = Utils.findRequiredViewAsType(source, R.id.tv_content, "field 'tvContent'", TextView.class);  }  @Override  @CallSuper  public void unbind() {    MainActivity target = this.target;    if (target == null) throw new IllegalStateException("Bindings already cleared.");    this.target = null;    target.tvContent = null;  }}

那么第一个问题来了

问题一: MainActivity_ViewBinding是如何生成的呢

这是一个贯穿整篇文章的问题。首先介绍一下,ButterKnife所使用的注解是编译时注解。(这里不了解注解的,可以先学习下,这里就不再介绍了)顾名思义,编译时注解是在编译时进行解释的注解。想找编译时注解的解释代码很简单,首先找到一个继承AbstractProcessor 的类,然后找到这个类的process方法,入口就在这里了。

这里我们搜索ButterKnife的源码,可以找到一个叫ButterKnifeProcessor的类,这个类继承了AbstractProcessor ,不错这个类中的processor方法,就是我们的着手点。

  @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {    //对所有的元素进行解析,生成一个bindingMap    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);    //遍历整个map    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {      TypeElement typeElement = entry.getKey();      BindingSet binding = entry.getValue();      //BindingSet 生成一个 javaFile      JavaFile javaFile = binding.brewJava(sdk);      try {        //将javafile转换成真正的java文件        javaFile.writeTo(filer);      } catch (IOException e) {        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());      }    }    return false;  }

整个process方法只有三个主要方法 分别是
findAndParseTargets(env)
binding.brewJava(sdk)
javaFile.writeTo(filer)

我们依次看三个方法的作用

findAndParseTargets(env)

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {    //创建了两个变量    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();    scanForRClasses(env);    //第一部分:很多for循环,分别取出ButterKnife对应的注解    // Process each @BindArray element.    for (Element element : env.getElementsAnnotatedWith(BindArray.class)) {      if (!SuperficialValidation.validateElement(element)) continue;      try {        parseResourceArray(element, builderMap, erasedTargetNames);      } catch (Exception e) {        logParsingError(element, BindArray.class, e);      }    }    // Process each @BindBitmap element.    for (Element element : env.getElementsAnnotatedWith(BindBitmap.class)) {      if (!SuperficialValidation.validateElement(element)) continue;      try {        parseResourceBitmap(element, builderMap, erasedTargetNames);      } catch (Exception e) {        logParsingError(element, BindBitmap.class, e);      }    }    ........    // Process each @BindView element.    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {      // we don't SuperficialValidation.validateElement(element)      // so that an unresolved View type can be generated by later processing rounds      try {        parseBindView(element, builderMap, erasedTargetNames);      } catch (Exception e) {        logParsingError(element, BindView.class, e);      }    }    // Process each @BindViews element.    for (Element element : env.getElementsAnnotatedWith(BindViews.class)) {      // we don't SuperficialValidation.validateElement(element)      // so that an unresolved View type can be generated by later processing rounds      try {        parseBindViews(element, builderMap, erasedTargetNames);      } catch (Exception e) {        logParsingError(element, BindViews.class, e);      }    }    // Process each annotation that corresponds to a listener.    for (Class<? extends Annotation> listener : LISTENERS) {      findAndParseListener(env, listener, builderMap, erasedTargetNames);    }    //第二部分:对builderMap进行处理,生成了最终的 bindingMap    // 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;  }

这个方法很长(省略号的地方省略了一些)但结构比较清晰,正如我上面的注释所写,整个函数分为两个部分,下面将一一进行讲解,在讲解前我们先看一下process方法的参数RoundEnvironment env,这里我们可以简单的理解为,这个参数记录了整个工程中所有的注解。process方法,在最开始创建了两个变量。

Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();

这两个变量是干嘛的呢,我们带着这个问题,来看一下第一部分。

第一部分

我们看到第一部分,由十几个for循环构成。这些for循环分别取出了所有的
@BindArray
@BindBitmap
@BindBool
@BindView
@BindViews
………
取出来做了什么,又放到哪里了呢?

// Process each @BindView element.for (Element element : env.getElementsAnnotatedWith(BindView.class)) {  // we don't SuperficialValidation.validateElement(element)  // so that an unresolved View type can be generated by later processing rounds  try {    parseBindView(element, builderMap, erasedTargetNames);  } catch (Exception e) {    logParsingError(element, BindView.class, e);  }}

这里我们我们以我们最关心的@BindView为例,看完了这个for循环我们就能推出其他for循环的作用了。
函数首先执行了parseBindView(element, builderMap, erasedTargetNames)方法。注释中我们假设当前解析的元素,就是例子中的

@BindView(R.id.tv_content)TextView tvContent;

下面我们开始分析 parseBindView:

private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,      Set<TypeElement> erasedTargetNames) {    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();    //这个方法是对当前元素和元素所在的类进行检查,检查什么呢:    //1、检查这个元素是不是private或者static的,如果是直接报错!!看看看,如果元素是private的错误就是在这里报出来的    //2、检查private是不是在一个class里(不能在interface、enum等等中),如果不在class中,报错!!    //3、检查所在的class是不是private的,如果是private,报错!!    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)        || isBindingInWrongPackage(BindView.class, element);    // 检查@BindView注解修饰的变量,必须是一个view或者interface。如果不是,报错,报错!!    TypeMirror elementType = element.asType();    if (elementType.getKind() == TypeKind.TYPEVAR) {      TypeVariable typeVariable = (TypeVariable) elementType;      elementType = typeVariable.getUpperBound();    }    Name qualifiedName = enclosingElement.getQualifiedName();    Name simpleName = element.getSimpleName();    if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {      if (elementType.getKind() == TypeKind.ERROR) {        note(element, "@%s field with unresolved type (%s) "                + "must elsewhere be generated as a View or interface. (%s.%s)",            BindView.class.getSimpleName(), elementType, qualifiedName, simpleName);      } else {        error(element, "@%s fields must extend from View or be an interface. (%s.%s)",            BindView.class.getSimpleName(), qualifiedName, simpleName);        hasError = true;      }    }    //如果经过上面的错误检查,发现有错,直接返回,什么也不做了。    if (hasError) {      return;    }    // 拿到@BindView(id)中的id。    //这里拿到的是R.id.tv_content    int id = element.getAnnotation(BindView.class).value();    //从builderMap中取出builder,这里每一个一个builder都对应工程中的一个类。    //也就是说这里拿到的是MainActivity对应的builder。    BindingSet.Builder builder = builderMap.get(enclosingElement);    QualifiedId qualifiedId = elementToQualifiedId(element, id);    if (builder != null) {      //如果发现这个builder中已经存在一个变量,id也是R.id.tv_content ,报错!!!      //从这里我们可以看出来,一个id只能对应一个变量。      String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));      if (existingBindingName != null) {        error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",            BindView.class.getSimpleName(), id, existingBindingName,            enclosingElement.getQualifiedName(), element.getSimpleName());        return;      }    } else {      //如果builder还是空,就创建一个新的。      //这里生成builder后,就直接把builder放到builderMap中了。      builder = getOrCreateBindingBuilder(builderMap, enclosingElement);    }    String name = simpleName.toString();    TypeName type = TypeName.get(elementType);    boolean required = isFieldRequired(element);    //把当前元素tvContent,加入到 MainActivity对应的builder中    builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));    // Add the type-erased version to the valid binding targets set.    erasedTargetNames.add(enclosingElement);  }

嚯,这个方法也够长的。不过上面的注释已经非常清楚了。大家可以带着这些注释去看源码。
看到这里是不是有点晕,下面我画一个图,帮大家整理下思路:

这里写图片描述

首先程序会生成一个名叫builderMap的Map。每一个使用butterKnife的类都会生成一个对应BindingClass。并存放在Map中,bindingClass中用又一个叫viewIdMap的Map,用于存储所有的@bindView。这样我们用于生成MainActivity_ViewBinding类的所有关键信息都被存储到了builderMap中。
这里有个地方要注意,无论是builderMap中的BindingSet.Builder还是viewIdMap中的ViewBinding.builder都是是用来构建BindingSet和ViewBinding的中间产物。

第二部分

第二部分主要的工作就是将builderMap转换成了bindingMap。这两个map名字很像,其实内容也很像。

这里有一点需要注意,无论builderMap还是bindingMap都是有顺序的LinkedHashMap,这两个Map唯一的不同时元素顺序不同,bindingMap会保证父类在子类的前面。以前面的图为例。假设BaseFragment是MainFragment的父类,结果如图:

这里写图片描述

在遍历之后如果发现MainFragment的父类BaseFragment,且发现BaseFragment还没有添加到buildingMap中,MainFragment就会被放置到队列的尾端。最后添加,以确保,父类一定在子类之前。
其实这里也不用看的这么仔细,只要知道:bindingMap中父类一定在子类之前就可以了。
另外还有一个不同:binderMap中的BindingSet.Builder变成了BindingSet。

binding.brewJava(sdk)

在取得了bindingMap之后,程序会遍历bindingMap中的所用binding,并调用binding.brewJava(sdk),接下来我们就来看一下这个函数做了什么。

  JavaFile brewJava(int sdk) {    return JavaFile.builder(bindingClassName.packageName(), createType(sdk))        .addFileComment("Generated code from Butter Knife. Do not modify!")        .build();  }

这个函数利用binding中的数据生成了一个JavaFile对象,看这一句:

addFileComment("Generated code from Butter Knife. Do not modify!")

是不是很熟悉,对的这就是MainActivity_ViewBinding中的第一句话。接下来我们看一下JavaFile文件:

  public final CodeBlock fileComment;  public final String packageName;  public final TypeSpec typeSpec;  public final boolean skipJavaLangImports;  private final Set<String> staticImports;  private final String indent;

里面存储着:文件注释、包名、变量信息、import等内容。有了这个对象,我们就可以去创造MainActivity_ViewBinding了。

javaFile.writeTo(filer)

接下来是最后一个方法就是真正的写文件,怎么写呢,当然是一行一行的写。哈哈

  • 先写注释
  • 再写包名
  • 再写import

跟我们写代码一样,只不过是用代码写代码…….这里就不再做详细介绍了,有兴趣大家可以看源码。(不影响大局嘛,哈哈)

最后给大家留两个问题。

  1. MainActivity_ViewBinding文件写完了,但文件的文件名是在哪里确定的呢?
  2. Set erasedTargetNames这个参数又是做什么用的呢?

butterKnife源码分析,终于写完了。butterKnife是一个简单的工具。源码也并不复杂,还是希望有兴趣的同学可以去看看代码。看源码的效果要比看什么源码解析有用的多。只希望这篇文章,可以给大家一个思路,让大家在源码阅读的路上更加顺畅。

1 0
原创粉丝点击