作为Android应用开发工程师估计没有人没听过Butterknife吧,我们可以借助它来简化view的查找过程以及通过注释将监听器绑定到事件响应方法上,这样可以减少很多代码量。对于Butterknife的用法在之前的博客中已经做了相应的介绍,这部分主要是分析Butterknife的源码,在之后的博文中还会对编译时注解解析器的实现做对应的介绍,如果想要对这部分内容有比较深入的了解,可以看下 《Java 进阶系列之注解》 以及《一步一步编写编译时注解框架》以及该篇博客。

好了言归正传,我们在介绍之前看下Butterknife 的工作原理:
在没有Butterknife的情况下我们用的是 Android 原生的API方法findViewById,而有了Butterknife后我们就可以直接使用@BindView(R.id.text) , 但是如果仔细看过源码的同学,会发现到最后还是调用findViewById,只不过这个是编译时注解框架帮我们生成了对应的代码,换句话说Butterknife以带有注解的源码文件作为javac编译器输入,在编译的时候会调用注解解析工具,对这些源码中的注解进行解析,解析后再通过代码生成工具来产生对应的源码文件,最后将我们自己的源码文件和注解解析工具产生的源码一同作为编译器输入进行编译。

ok 了解了整个原理我们就可以进行深入研究了。

我们将分成两大部分进行分析:

  • 绑定View以及事件
  • 注解解析器

[项目结构]:

[绑定过程]:

在绑定实用的是bind方法,bing方法实现如下,它的实现十分简单,但是在介绍bind时候,必须要先大概知道什么是target,什么是source,简单讲target就是一个实体,它承载着source,目前butterknife支持的target 包括activity,dialog,以及view。source就是我们实际要查找控件的地方。也就是findViewById 应用的对象。下面是以activity为target,所以对应的source就是对应activity的DecorView.

@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}

接着我们来看下createBinding方法,在方法开始的时候会使用target的class对象来获取对应的ViewBinding的构造方法,target的ViewBinding对象是由ButterknifeProcessor生成的,这个会在后面介绍ButterknifeProcessor的时候进行详细展开介绍。获取到对应的ViewBinding对象后调用该对象的构造方法并返回。


private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
//获取target的Class对象,比如 XXXActivity.class
Class<?> targetClass = target.getClass();
//获取targetClass对应ViewBinding的构造方法
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

if (constructor == null) {
return Unbinder.EMPTY;
}
//这样一来,ButterKnife.bind(this)传递进去的MainActivity会通过反射生成MainActivity_ViewBinding实例。
//在这个实例的构造函数内,进行findViewById、setOnclickListener等操作
try {

return constructor.newInstance(target, source);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to invoke " + constructor, e);
} catch (InstantiationException e) {
throw new RuntimeException("Unable to invoke " + constructor, e);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
if (cause instanceof Error) {
throw (Error) cause;
}
throw new RuntimeException("Unable to create binding instance.", cause);
}
}

findBindingConstructorForClass 的作用就是加载对应targetClass的ViewBinding对象的构造方法:

@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
//首先将传入XXXXActivity 类对象 作为参数尝试从缓存中获取通过JavaPoet产生的ViewBinding的构造方法
//内存缓存BINDINGS 的组成是以targetClass为key,targetClass ViewBinding 构造方法为value的一个Map
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
String clsName = cls.getName();
//由于Butterknife 不能用于注释android以及java这些标准sdk的类所以先进行这种类型的过滤
if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
try {
//使用类加载器加载在编译阶段由JavaPoet生成的XXXX_ViewBinding类对象
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//获取对应的构造方法类,并且会将得到的Constructor缓存起来,避免反射的性能问题。
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
} catch (ClassNotFoundException e) {
//如果没有找到那么找它对应的父类ViewBinding的构造方法
if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
} catch (NoSuchMethodException e) {
throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
}
//将其添加到缓存中,后续使用的时候就不需要进行再次使用反射了从而加快了效率
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}

为了提高整体性能,这里使用了BINDINGS这个Map作为内存缓存。BINDINGS的定义如下:

static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();

它的键值为我们的target 的类对象,我们以MainActivity为例子,那么key为MainActivity.class 对应的值呢?就是MainActivity_ViewBinding的构造方法,这个构造方法只有一个参数(View).
这个阶段的整个过程如下:
拿着target类对象到BINDINGS缓存中去匹配,看下是否有缓存,如果有缓存那么使用缓存,如果没有那么就获取JavaPoet产生的MainActivity_ViewBinding(View view)的构造方法,并添加到缓冲中,供后续使用,为啥要缓存?
因为这里获取MainActivity_ViewBinding的构造方法使用的是反射的方式,对性能有所影响,使用缓存可以减少反射的使用次数,但是这里有一个疑问就是会不会因为Butterknife的大量使用从而导致缓存占用很大的空间,这个问题又是如何克服的?我们带着这个问题继续。获取到构造方法后我们new出一个实例对象,这里可以看出 MainActivity_ViewBinding.java继承的是Unbinder,而Unbinder中的unbind方法有可能就是处理缓存清除的工作。
我们看下Butterknife sample例子中产生的一个代码:

public class G_ViewBinding<T extends G> extends E_ViewBinding<T> {
private View view16908290;

@UiThread
public G_ViewBinding(final T target, View source) {
super(target, source);

View view;
target.button2 = Utils.findRequiredView(source, android.R.id.button2, "field 'button2'");
view = Utils.findRequiredView(source, android.R.id.content, "method 'onClick'");
view16908290 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.onClick();
}
});

Context context = source.getContext();
Resources res = context.getResources();
Resources.Theme theme = context.getTheme();
target.grayColor = Utils.getColor(res, theme, android.R.color.darker_gray);
}

@Override
public void unbind() {
T target = this.target;
super.unbind();
target.button2 = null;
view16908290.setOnClickListener(null);
view16908290 = null;
}
}

我们先不看上面的具体代码,只看unbind方法。它会递归地将对应的view对象设置成null,从而在gc的时候释放掉资源。

从上面代码看整个butterknife包的源码是十分简单的,最重要的在于xxxx_ViewBinding.class的生成,这就需要对butterknife-compiler包中的源码进行研究了,我们所有的注解都在butterknife-annotations这个包中,我们这里只以BindView注解作为分析对象:
具体的注解相关内容见《Java进阶之注解》 这篇博客。

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

从上面我们可以看出@BindView注解应用于属性定义中,并且只在源码级别以及class级别中存在,它的值为一个id类型的值。不允许其他类型的值作为参数。

[Butterknife注释解析器ButterknifeProcessor]:

从上面一小结介绍可以看出Butterknife.java 中的工作很简单就是拿着targetClass找到对应的ViewBinding,至于ViewBinding怎么来的则是这一小结的介绍重点,在介绍ButterKnife注解解析器之前我们再次回顾下注解解析器在整个编译过程中的的作用:
我们知道Java使用Javac处理编译过程的,在Java的编译时期,Javac会先调用java注解处理器来进行处理。因此我们可以自定义注解处理器来做一些处理。注解处理器通常以java源码或者已编译的字节码作为输入,然后生成一些源码文件作为输出。在这个过程可以在已有的代码上添加一些方法,来帮我们做一些有用的事情。这些生成的 java 文件跟其他手动编写的java源码一样,会被 javac 编译。编译时解析是注解强大的地方之一。我们可以利用它在编译时帮你生成一些代码逻辑,避免了运行时利用反射解析所带来的性能开销。

下面是ButterKnifeProcessor的大体结构:

@AutoService(Processor.class)
//要解析编译时注释,可以通过创建一个继承自AbstractProcessor的注解处理器,然后实现相关方法。
public final class ButterKnifeProcessor extends AbstractProcessor {
//指定支持的 java 版本,通常返回 SourceVersion.latestSupported()
@Override public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}

@Override public Set<String> getSupportedOptions() {
return Collections.singleton(OPTION_SDK_INT);
}

//init方法是在Processor创建时被javac调用并执行初始化操作。
//做一些初始化操作,可以在这里获取Filer,Elements等辅助类
//@param processingEnv 提供一系列的注解处理工具。
public synchronized void init(ProcessingEnvironment env) {
}

//注解处理器要处理的注解类型,值为完全限定名(就是带所在包名和路径的类全名) **/
@Override public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
//将支持的注释类型添加到types集合中
types.add(annotation.getCanonicalName());
}
return types;
}
//注解处理需要执行一次或者多次。每次执行时,处理器方法被调用,并且传入了当前要处理的注解类型。可以在这个方法中扫描和处理注解,并生成Java代码。
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
return true;
}

}

下面我们就顺着这个框架对ButterKnifeProcessor进行分析:
我们首先看下
@AutoService(Processor.class)的作用:一旦在Processor类上加上注解@AutoService(Processor.class),它自动帮你将这个类添加到META-INF/service/javax.naaotation.processing.Processor下Java的ServiceLoader会自己去找到这个类并进行编译处理。
接着是init方法:
init方法是在Processor创建时被javac调用并执行初始化操作。通过ProcessingEnvironment参数我们可以获得如下元素:
Messager :用于打印Log,在编译的时候会在Message窗口中输出。
Elements :用于处理编程元素的辅助类
Filer :用于注解处理器生成新文件的
Locale :用于获取时区信息的类
Types :用于操作类型的辅助方法
其中最常用的就是Elements以及Types和打印Log用到的Messager.

@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);

//获取sdk 版本
String sdk = env
.getOptions().get(OPTION_SDK_INT);
if (sdk != null) {

try {
this.sd
k = Integer.parseInt(sdk);
} catch (NumberFormatException e) {
env.getMessager().printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '" + sdk + "'. Falling back to API 1 support.");
}
}

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

try {
trees = Trees.instance(processingEnv);
} catch (IllegalArgumentException ignored) {
}
}

接着就是整个注解解析器的关键方法process,每次执行时,处理器方法被调用,并且传入了当前要处理的注解类型。可以在这个方法中扫描和处理注解,并生成Java代码。我们看下面的process方法,在这个方法中主要完成了两项任务,一个就是调用findAndParseTargets来获取bindingMap
bindingMap 是什么呢?它其实对应的是每个有调用bind绑定的target,举个例子来说,我们在MainActicity的onCreate方法调用了Butterknife的bind方法,那么ButterknifeProcessor就会产生一个对应的BindingSet,也就是说有多少个bind,bindingMap里面就有多少个BindingSet。然后再使用Javapost来为每个BingSet生成一个_ViewBinding.class.

@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);
try {
javaFile.writeTo(filer);
} catch (IOExceptio
n e) {
error(typeElement,
"Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}

我们先来看下findAndParseTarets,它的任务分成如下几个部分:

  1. 调用scanForRClasses 分别通过RClassScanner IdScanner VarScanner 解析R 文件并将解析结果存放到symbols 中,至于symbols的作用是什么我们后面介绍
  2. 调用一系列parseXXXX方法解析各种注解
  3. 调用findAndParseListener 来解析各种事件注解
  4. 整理BindingSet的继承关系
    private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    //建立view与R的id的关系
    //比如在Butterknife 编译出来的代码MainActivity_ViewBinding里持有一个全局变量view8131034512,
    // 这个其实就是MainActivity的tvTitle,后面的8131034512就是对应在R文件的id。

    //分别通过RClassScanner IdScanner VarScanner 解析R 文件并将解析结果存放到symbols 中
    scanForRClasses(env);

    // 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 annotation that corresponds to a listener.
    for (Class<? extends Annotation> listener : LISTENERS) {
    findAndParseListener(env, listener, builderMap, erasedTargetNames);
    }
    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) {
    bindin
    gMap.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;
    }

首先看下scanForRClasses:
它的作用是解析R文件将获取到的数据存入symbols,用于作为后续标识每个元素的key。R 元素中的每一个变量对应symols中的一个元素。

private void scanForRClasses(RoundEnvironment env) {
if (trees == null) return;
RClassScanner scanner = new RClassScanner();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
//遍历全部支持的注解,找出使用给定注释类型注释的元素 比如这里可以返回全部使用@BindView注解的元素
for (Element element : env.getElementsAnnotatedWith(annotation)) {
JCTree tree = (JCTree) trees.getTree(element, getMirror(element, annotation));
if (tree != null) {
//获取对应元素(使用BindView元素)的包名的完全限定名称
//首先根据element获取到包名,再利用RClassScanner寻找到R文件,在R文件里利用IdScanner寻找到内部类id,
//在id类里利用VarScanner寻找到tvTitle的id。最后就可以得到view2131034112。
String respectivePackageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
//扫描指定的包
scanner.setCurrentPackageName(respectivePackageName);
tree.accept(scanner);
}
}
}

//包名 + R Class
for (Map.Entry<String, Set<String>> packageNameToRClassSet : scanner.getRClasses().entrySet()) {
String respectivePackageName = packageNameToRClassSet.getKey();
//获取对应的R类
for (String rClass : packageNameToRClassSet.getValue()) {
//解析R类
//respectivePackageName 元素的包名
parseRClass(respectivePackageName/*key*/, rClass/*value*/);
}
}
}

查找R文件使用的是RClassScanner,它是一个TreeScanner,它会找到整个项目的R文件: 

private static class RClassScanner extends TreeScanner {
private final Map<String, Set<String>> rClasses = new LinkedHashMap<>();
private String currentPackageName;
//数据结构 包名 + RC 类名集合
//rClasses ---| String 包名(currentPackageName) + RC 类名集合(rClassSet) Set<String> -> symbol.getEnclosingElement().getEnclosingElement().enclClass().className()
// | String 包名(currentPackageName) + RC 类名集合(rClassSet) Set<String> -> symbol.getEnclosingElement().getEnclosingElement().enclClass().className()
// | String 包名(currentPackageName) + RC 类名集合(rClassSet) Set<String> -> symbol.getEnclosingElement().getEnclosingElement().enclClass().className()
// | String 包名(currentPackageName) + RC 类名集合(rClassSet) Set<String> -> symbol.getEnclosingElement().getEnclosingElement().enclClass().className()
@Override
public void visitSelect(JCTree.JCFieldAccess jcFieldAccess) {
Symbol symbol = jcFieldAccess.sym;
if (symbol != null
&& symbol.getEnclosingElement() != null
&& symbol.getEnclosingElement().getEnclosingElement() != null
&& symbol.getEnclosingElement().getEnclosingElement().enclClass() != null) {
//从缓存中获取对应的包名对应的 RClass 集合
Set<String> rClassSet = rClasses.get(currentPackageName);
if (rClassSet == null) {
rClassSet = new HashSet<>();
rClasses.put(currentPackageName, rClassSet);
}
// getEnclosingElement 返回封装此元素(非严格意义上)的最里层元素。这里是对应的R Class 文件
rClassSet.add(symbol.getEnclosingElement().getEnclosingElement().enclClass().className());
}
}
/**
* 获取当前包的rClasses对象
* @return
*/
Map<String, Set<String>> getRClasses() {
return rClasses;
}
/**
* 设置当前扫描的包名
* @param respectivePackageName
*/
void setCurrentPackageName(String respectivePackageName) {
this.currentPackageName = respectivePackageName;
}
}

在找到全部的R文件后在scanForRClass中会调用parseRClass对扫描到的R文件进行解析:

private void parseRClass(String respectivePackageName, String rClass) {
Element element;
try {
//对应的R元素
element = elementUtils.getTypeElement(rClass);
} catch (MirroredTypeException mte) {
element = typeUtils.asElement(mte.getTypeMirror());
}
//对应的R文件
JCTree tree = (JCTree) trees.getTree(element);
if (tree != null) {
IdScanner idScanner = new IdScanner(symbols,
elementUtils.getPackageOf(element).getQualifiedName().toString()/*R class 全路径*/,
respectivePackageName/*目标元素的包名*/);
tree.accept(idScanner);
} else {
parseCompiledR(respectivePackageName, (TypeElement) element);
}
}

我们从上面代码中可以看到在parseRClass中会使用IdScanner 对当前扫描的R文件进行扫描:

private static class IdScanner extends TreeScanner {
private final Map<QualifiedId, Id> ids;
private final String rPackageName;
private final String respectivePackageName;

IdScanner(Map<QualifiedId, Id> ids, String rPackageName/*R class 全路径*/, String respectivePackageName/*对应的包名*/) {
this.ids = ids;
this.rPackageName = rPackageName;
this.respectivePackageName = respectivePackageName;
}

@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
for (JCTree tree : jcClassDecl.defs) {
//每个指的是R 文件中的一个类 比如anim
if (tree instanceof ClassTree) {
ClassTree classTree = (ClassTree) tree;
//返回对应的类名比如 public static final class anim 返回的是 anim
String className = classTree.getSimpleName().toString();
//看下当前的类型是否在支持的范围内
if (SUPPORTED_TYPES.contains(className)) {
//获取到资源类型的全限定名 比如com.demo.example.R.anim
ClassName rClassName = ClassName.get(rPackageName, "R", className);
VarScanner scanner = new VarScanner(ids, rClassName /* 这里是某个资源类的全限定名*/, respectivePackageName);
((JCTree) classTree).accept(scanner);
}
}
}
}
}

在IdScanner 中会对R文件中的每个内部类进行扫描,判断当前的内部类是否是所支持的,如果是那么就调用VarScanner进行进一步扫描。

private static class VarScanner extends TreeScanner {
private final Map<QualifiedId, Id> ids;
private final ClassName className;
private final String respectivePackageName;

private VarScanner(Map<QualifiedId, Id> ids, ClassName className/*com.demo.example.R.anim*/, String respectivePackageName) {
this.ids = ids;
this.className = className;
this.respectivePackageName = respectivePackageName;
}

@Override public void visitVarDef(JCTree.JCVariableDecl jcVariableDecl) {
if ("int".equals(jcVariableDecl.getType().toString())) {
//资源id
int id = Integer.valueOf(jcVariableDecl.getInitializer().toString());
//资源名
String resourceName = jcVariableDecl.getName().toString();
// 元素所在包名,资源id
QualifiedId qualifiedId = new QualifiedId(respectivePackageName, id);
//[获取到资源类型的全限定名比如com.demo.example.R.anim] [资源名abc_fade_in] [0x7f040000]
ids.put(qualifiedId, new Id(id, className, resourceName));
}
}
}

VarScanner 也是一个TreeScanner,它的所用就是在R 文件内部类中扫描,并将扫描到的id 添加到symbos中。

介绍到这里我们做下简要,首先我们会通过RCScanner找到项目的R文件,再使用IdScanner对R文件中的内部类进行扫描,每个内部类对应一类资源,最后通过VarScanner对id进行扫描然后存入sybols这个map.

symbols定义如下:

private final Map<QualifiedId, Id> symbols = new LinkedHashMap<>();

Key:

QualifiedId ---|--- respectivePackageName 当前资源所在对包
|--- id 当前资源id

Value ---|--- id 当前资源id 比如 0x7f040000
|--- className 获取到资源类型的全限定名 比如com.demo.example.R.anim
|--- resourceName 资源名 比如 abc_fade_in

介绍完scanForRClass我们继续了解下注解的解析过程:
在进行解析之前会先对注解所应用的对象做初步检查:

  • 验证对应的修饰符是否是static 或者 private ,注解是否正在修饰局部变量,是否处于私有类中,以及检查包名是否是在android以及java标准API包内,如果有上述的情况就返回false
  • 获取使用该注解的元素对应类型是否继承自View

如果校验无误,那么会先查询bingMap中是否已经有对应的BindingSet了,如果没有就创建一个并添加到bingMap中。如果有的话就完对应的BindingSet添加Field这些Field会在JavaPost创建对应源码文件的时候用于生成对应的成员变量。这个后面会进行详细介绍。
我们下面深入看两点:

  1. BindingSet是怎么创建的
  2. addFeild是怎么添加的
    private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    //校验一:解析前初步校验
    //验证对应的修饰符是否是static 或者 private ,注解是否正在修饰局部变量,是否处于私有类中,以及检查包名是否是在android以及java标准API包内,如果有上述的情况就返回false
    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || isBindingInWrongPackage(BindView.class, element);

    //校验二:获取使用该注解的元素对应类型是否继承自View
    TypeMirror elementType = element.asType();
    // 是否是一个变量类型
    if (elementType.getKind() == TypeKind.TYPEVAR) {
    TypeVariable typeVariable = (TypeVariable) elementType;
    //获取它的父类
    elementType = typeVariable.getUpperBound();
    }
    Name qualifiedName = enclosingElement.getQualifiedName();
    Name simpleName = element.getSimpleName();
    //如果不是View类型并且不是接口那么就返回类型失败的错误
    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 {
    //不是View的子类或者不是接口
    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值
    int id = element.getAnnotation(BindView.class).value();
    //用包名和id生成一个QualifiedId:symble中存放的内容是以QualifiedId为键值
    QualifiedId qualifiedId = elementToQualifiedId(element, id);

    //从缓存中获取对应元素所在类的BindingSet.Builder,每个注解元素所在类都只有一个BindingSet.Builder
    //比如我们当前的元素位于MainActivity中,那么解析的时候就获取MainActivity对应的BindingSet.Builder
    BindingSet.Builder builder = builderMap.get(enclosingElement/*MainActivity*/);

    //判断是否存在同一个id重复绑定的问题
    if (builder != null) {
    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 {
    //元素所处类对应包名/*MainActivity*/ + BindingSet.Builder
    builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    }

    //控件名 比如mTextView
    String name = simpleName.toString();
    //获取对应的类型 TextView
    TypeName type = TypeName.get(elementType);
    //当前元素是否是必须的
    boolean required = isFieldRequired(element);
    //将当前的成员变量添加到BuildSet中 比如上述的例子: id / TextView mTextView false
    builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));
    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement);
    }

BindingSet的创建是通过调用getOrCreateBindingBuilder方法来完成的:
它会先判断当前target类型是activity,dialog,还是一般的view,以及生成target对应的ViewBinding文件名。

//builderMap 中存放的形式  key: 对应的包名 + 对应的BindingSet.Builder
private BindingSet.Builder getOrCreateBindingBuilder(Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
BindingSet.Builder builder = builderMap.get(enclosingElement);
if (builder == null) {
builder = BindingSet.newBuilder(enclosingElement/*MainActivity*/);
builderMap.put(enclosingElement, builder);
}
return builder;
}
/**
* 创建一个Builder,每个文件对应一个Builder,这里主要判断当前是否是View,是否是Activity,是否是Dialog,是否是Final,以及对应的报名
* 参数为注解所处上层元素
*/
static Builder newBuilder(TypeElement enclosingElement) {
TypeMirror typeMirror = enclosingElement.asType();
//判断当前是否View
boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);
//判断当前是否Activity
boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);
//判断当前是否Dialog
boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);
//获取上层类的类型
TypeName targetType = TypeName.get(typeMirror);
if (targetType instanceof ParameterizedTypeName) {
targetType = ((ParameterizedTypeName) targetType).rawType;
}
//获取包名
String packageName = getPackage(enclosingElement).getQualifiedName().toString();
String className = enclosingElement.getQualifiedName().toString().substring(packageName.length() + 1).replace('.', '$');
//生成源码的文件名
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");
boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
}

那BindingSet的结构是怎样的呢?我们继续往下看:

static final class Builder {
private final TypeName targetTypeName;
private final ClassName bindingClassName;
private final boolean isFinal;
private final boolean isView;
private final boolean isActivity;
private final boolean isDialog;
private BindingSet parentBinding;
private final Map<Id, ViewBinding.Builder> viewIdMap = new LinkedHashMap<>();
private final ImmutableList.Builder<FieldCollectionViewBinding> collectionBindings = ImmutableList.builder();
private final ImmutableList.Builder<ResourceBinding> resourceBindings = ImmutableList.builder();

/**
* 添加Field
*/
void addField(Id id, FieldViewBinding binding) {
ViewBinding.Builder viewBinding = getOrCreateViewBindings(id);
viewBinding.setFieldBinding(binding);
}

/**
* 添加方法
*/
boolean addMethod(Id id, ListenerClass listener, ListenerMethod method, MethodViewBinding binding) {
ViewBinding.Builder viewBinding = getOrCreateViewBindings(id);
if (viewBinding.hasMethodBinding(listener, method) && !"void".equals(method.returnType())) {
return false;
}
viewBinding.addMethodBinding(listener, method, binding);
return true;
}

/**
* 添加Field集合
*/
void addFieldCollection(FieldCollectionViewBinding binding) {
collectionBindings.add(binding);
}

/**
* 添加资源
*/
void addResource(ResourceBinding binding) {
resourceBindings.add(binding);
}
void setParent(BindingSet parent) {
this.parentBinding = parent;
}
/**
* 当前id是否已经被绑定了
* @param id
* @return
*/
String findExistingBindingName(Id id) {
ViewBinding.Builder builder = viewIdMap.get(id);
if (builder == null) {
return null;
}
FieldViewBinding fieldBinding = builder.fieldBinding;
if (fieldBinding == null) {
return null;
}
return fieldBinding.getName();
}
/**
* 往viewIdMap中获取ViewBinding.Builder
*/
private ViewBinding.Builder getOrCreateViewBindings(Id id) {
ViewBinding.Builder viewId = viewIdMap.get(id);
if (viewId == null) {
viewId = new ViewBinding.Builder(id);
viewIdMap.put(id, viewId);
}
return viewId;
}

BindingSet build() {
ImmutableList.Builder<ViewBinding> viewBindings = ImmutableList.builder();
for (ViewBinding.Builder builder : viewIdMap.values()) {
viewBindings.add(builder.build());
}
//将当前的绑定数据传到BindingSet
return new BindingSet(targetTypeName, bindingClassName, isFinal, isView, isActivity, isDialog,
viewBindings.build(), collectionBindings.build(), resourceBindings.build(), parentBinding);
}
}

我们从BindingSet的Builder可以看出它有如下的成员变量:

parentBinding       指向父类的BindingSet。
viewIdMap 与View相关的绑定信息,包括view对象以及事件方法。
collectionBindings 多个view集体绑定的情况
resourceBindings 资源绑定

它有如下方法:

void addField(Id id, FieldViewBinding binding)  添加Field
boolean addMethod(Id id, ListenerClass listener, ListenerMethod method, MethodViewBinding binding) 添加事件方法
void addFieldCollection(FieldCollectionViewBinding binding) 添加Field集合
void addResource(ResourceBinding binding) 添加资源

介绍完BindView 我也把事件绑定的方法贴出来了,下面有较为详细的注释。

private void findAndParseListener(RoundEnvironment env, Class<? extends Annotation> annotationClass, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
for (Element element : env.getElementsAnnotatedWith(annotationClass)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseListenerAnnotation(annotationClass, element, builderMap, erasedTargetNames);
} catch (Exception e) {
StringWriter stackTrace = new StringWriter();
e.printStackTrace(new PrintWriter(stackTrace));
error(element, "Unable to generate view binder for @%s.\n\n%s",
annotationClass.getSimpleName(), stackTrace.toString());
}
}
}

private void parseListenerAnnotation(Class<? extends Annotation> annotationClass, Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) throws Exception {
// This should be guarded by the annotation's @Target but it's worth a check for safe casting.
//首先检查当前元素是否是方法类型
if (!(element instanceof ExecutableElement) || element.getKind() != METHOD) {
throw new IllegalStateException(String.format("@%s annotation must be on a method.", annotationClass.getSimpleName()));
}

ExecutableElement executableElement = (ExecutableElement) element;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
//获取注解中的id数组
Annotation annotation = element.getAnnotation(annotationClass);
Method annotationValue = annotationClass.getDeclaredMethod("value");
if (annotationValue.getReturnType() != int[].class) {
throw new IllegalStateException(String.format("@%s annotation value() type not int[].", annotationClass));
}
int[] ids = (int[]) annotationValue.invoke(annotation);
//方法名字
String name = executableElement.getSimpleName().toString();
//是否必须
boolean required = isListenerRequired(executableElement);

//校验是否错误
boolean hasError = isInaccessibleViaGeneratedCode(annotationClass, "methods", element);
hasError |= isBindingInWrongPackage(annotationClass, element);

//查找是否有重复的id
Integer duplicateId = findDuplicate(ids);
if (duplicateId != null) {
error(element, "@%s annotation for method contains duplicate ID %d. (%s.%s)",
annotationClass.getSimpleName(), duplicateId, enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}

//获取到对应ListenerClass
ListenerClass listener = annotationClass.getAnnotation(ListenerClass.class);
if (listener == null) {
throw new IllegalStateException(
String.format("No @%s defined on @%s.", ListenerClass.class.getSimpleName(),
annotationClass.getSimpleName()));
}
/*@ListenerClass(
targetType = "android.view.View",
setter = "setOnClickListener",
type = "butterknife.internal.DebouncingOnClickListener",
method = @ListenerMethod(
name = "doClick",
parameters = "android.view.View"
)
)*/
ListenerMethod method;
ListenerMethod[] methods = listener.method();
//获取onClick注解中@ListenerClass中的method
if (methods.length > 1) {
//不允许有两个以上的method
throw new IllegalStateException(String.format("Multiple listener methods specified on @%s.", annotationClass.getSimpleName()));
} else if (methods.length == 1) {
//method 不允许和callbacks共存
if (listener.callbacks() != ListenerClass.NONE.class) {
throw new IllegalStateException(String.format("Both method() and callback() defined on @%s.", annotationClass.getSimpleName()));
}
//获取到methods[0]
method = methods[0];
} else {
//如果没有method就获取callback
Method annotationCallback = annotationClass.getDeclaredMethod("callback");
Enum<?> callback = (Enum<?>) annotationCallback.invoke(annotation);
Field callbackField = callback.getDeclaringClass().getField(callback.name());
method = callbackField.getAnnotation(ListenerMethod.class);
if (method == null) {
throw new IllegalStateException(
String.format("No @%s defined on @%s's %s.%s.", ListenerMethod.class.getSimpleName(),
annotationClass.getSimpleName(), callback.getDeclaringClass().getSimpleName(),
callback.name()));
}
}

//验证回调方法的参数(实际被注释的方法参数不能大于注解中提供的参数个数)
// Verify that the method has equal to or less than the number of parameters as the listener.
//获取实际的方法参数
List<? extends VariableElement> methodParameters = executableElement.getParameters();
//如果注解目标参数大于ListenerMethod注解指定的参数个数就报错
if (methodParameters.size() > method.parameters().length) {
error(element, "@%s methods can have at most %s parameter(s). (%s.%s)",
annotationClass.getSimpleName(), method.parameters().length,
enclosingElement.getQualifiedName(), element.getSimpleName());
hasError = true;
}

// 验证返回类型(实际被注释的方法参数需要等于注解中提供的参数类型)
// Verify method return type matches the listener.
TypeMirror returnType = executableElement.getReturnType();
if (returnType instanceof TypeVariable) {
TypeVariable typeVariable = (TypeVariable) returnType;
returnType = typeVariable.getUpperBound();
}
if (!returnType.toString().equals(method.returnType())) {
error(element, "@%s methods must have a '%s' return type. (%s.%s)",
annotationClass.getSimpleName(), method.returnType(),
enclosingElement.getQualifiedName(), element.getSimpleName());
hasError = true;
}

//校验结束,如果有问题就不在继续
if (hasError) {
return;
}

Parameter[] parameters = Parameter.NONE;
//省略参数的创建过程

//name 名字 parameters 参数 required 是否必须
MethodViewBinding binding = new MethodViewBinding(name, Arrays.asList(parameters), required);
//创建一个BindingSet.Builder
BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
for (int id : ids) {
//为每个id绑定一个方法
QualifiedId qualifiedId = elementToQualifiedId(element, id);
//ListenerClass listener = annotationClass.getAnnotation(ListenerClass.class);
if (!builder.addMethod(getId(qualifiedId), listener, method, binding)) {
error(element, "Multiple listener methods with return value specified for ID %d. (%s.%s)", id, enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
}
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}

使用JavaPoest为每个BindingSet创建对应的_ViewBinding文件:
我们在此回到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);
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}

我们上面了解了如何从R文件中获取对应的信息构成后续作为Id使用的symbols。再对代码中使用到的Butterknife注解进行解析,获取到对应的View 绑定,事件方法绑定,资源绑定信息,添加到对应BingSet中,最后我们要了解的是最后一步 – 使用JavaPost来将BindingSet中收集到的View 绑定,事件方法绑定,资源绑定信息生成对应的源码:
我们先来看下brewJava 方法:

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

brewJava 方法中最重要的就是createType,它是生成_ViewBinding的核心代码,下面给出了详细的注释:

private TypeSpec createType(int sdk) {

//使用JavaPost 生成 "public final class MainActivity_BindingView"
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName()).addModifiers(PUBLIC);
if (isFinal) {
result.addModifiers(FINAL);
}

//生成 public final class MainActivity_BindingView extends Unbinder
//或者 public final class MainActivity_BindingView extends XXXXActivity_BindingView
//查看是否父类有绑定,添加绑定类的父类
if (parentBinding != null) {
result.superclass(parentBinding.bindingClassName);
} else {
result.addSuperinterface(UNBINDER);
}

//是否有方法或者值绑定,如果有那么添加target成员,在只有资源绑定的情况下就没有target成员变量
if (hasTargetField()) {
result.addField(targetTypeName, "target", PRIVATE);
}

//创建对应的构造方法
if (isView) {
result.addMethod(createBindingConstructorForView());
} else if (isActivity) {
result.addMethod(createBindingConstructorForActivity());
} else if (isDialog) {
result.addMethod(createBindingConstructorForDialog());
}

//添加一个废弃的方法用于在Butterknife.bind方法反射时候使用
if (!constructorNeedsView()) {
result.addMethod(createBindingViewDelegateConstructor());
}
result.addMethod(createBindingConstructor(sdk));

//添加unbind方法
if (hasViewBindings() || parentBinding == null) {
result.addMethod(createBindingUnbindMethod(result));
}
return result.build();
}

那么我们在Butterknife bind方法中调用的构造函数是哪个呢:就是createBindingConstructor

private MethodSpec createBindingConstructor(int sdk) {
//添加一个无参数的构造方法
//@UiThread
//public class MainActivity_BindingView {
// MainActivity_BindingView(Activity target,View source) {
// super(target, source);
// this.target = target
// }
//}
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC);

if (hasMethodBindings()) {
constructor.addParameter(targetTypeName, "target", FINAL);
} else {
constructor.addParameter(targetTypeName, "target");
}

//如果跟View相关那么添加成员变量source 否则添加成员变量context
if (constructorNeedsView()) {
constructor.addParameter(VIEW, "source");
} else {
constructor.addParameter(CONTEXT, "context");
}

if (hasUnqualifiedResourceBindings()) {
// Aapt can change IDs out from underneath us, just suppress since all will work at runtime.
constructor.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
.addMember("value", "$S", "ResourceType")
.build());
}

//是否有onTouch事件绑定
if (hasOnTouchMethodBindings()) {
constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
.addMember("value", "$S", "ClickableViewAccessibility")
.build());
}

//添加对父类构造方法的调用
if (parentBinding != null) {
if (parentBinding.constructorNeedsView()) {
constructor.addStatement("super(target, source)");
} else if (constructorNeedsView()) {
constructor.addStatement("super(target, source.getContext())");
} else {
constructor.addStatement("super(target, context)");
}
constructor.addCode("\n");
}

//给target赋值
if (hasTargetField()) {
constructor.addStatement("this.target = target");
constructor.addCode("\n");
}

//添加View 绑定
if (hasViewBindings()) {
if (hasViewLocal()) {
// Local variable in which all views will be temporarily stored.
constructor.addStatement("$T view", VIEW);
}
//添加绑定的View,这里是重点
for (ViewBinding binding : viewBindings) {
addViewBinding(constructor, binding);
}
for (FieldCollectionViewBinding binding : collectionBindings) {
constructor.addStatement("$L", binding.render());
}
if (!resourceBindings.isEmpty()) {
constructor.addCode("\n");
}
}

//添加其他绑定
if (!resourceBindings.isEmpty()) {
if (constructorNeedsView()) {
constructor.addStatement("$T context = source.getContext()", CONTEXT);
}
if (hasResourceBindingsNeedingResource(sdk)) {
constructor.addStatement("$T res = context.getResources()", RESOURCES);
}
for (ResourceBinding binding : resourceBindings) {
constructor.addStatement("$L", binding.render(sdk));
}
}
return constructor.build();
}

上面的代码已经注释很清楚了,我们直接讲最重要的:addViewBinding,在调用addViewBinding之前会调用hasViewBindings来判断当前是否有view绑定以及事件方法绑定,如果有才会调用addViewBinding.
在addViewBinding方法中会判断是否isRequired来决定是调用butterknife.internal.Utils的findRequiredViewAsType 还是findOptionalView

private void addViewBinding(MethodSpec.Builder result, ViewBinding binding) {

if (binding.isSingleFieldBinding()) {
FieldViewBinding fieldBinding = binding.getFieldBinding();
CodeBlock.Builder builder = CodeBlock.builder().add("target.$L = ", fieldBinding.getName());
//生成target.mTextBtn =

//是否需要强制转换
boolean requiresCast = requiresCast(fieldBinding.getType());

if (!requiresCast && !fieldBinding.isRequired()) {
//生成 target.mTextBtn = source.findViewById(id)
builder.add("source.findViewById($L)", binding.getId().code);
} else {
builder.add("$T.find", UTILS);
builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");
//调用butterknife.internal.Utils的findRequiredViewAsType 或者findOptionalView
if (requiresCast) {
builder.add("AsType");
}
builder.add("(source, $L", binding.getId().code);
if (fieldBinding.isRequired() || requiresCast) {
builder.add(", $S", asHumanDescription(singletonList(fieldBinding)));
}
if (requiresCast) {
builder.add(", $T.class", fieldBinding.getRawType());
}
builder.add(")");
}
result.addStatement("$L", builder.build());
return;
}
List<MemberViewBinding> requiredBindings = binding.getRequiredBindings();
if (requiredBindings.isEmpty()) {
result.addStatement("view = source.findViewById($L)", binding.getId().code);
} else if (!binding.isBoundToRoot()) {
result.addStatement("view = $T.findRequiredView(source, $L, $S)", UTILS,
binding.getId().code, asHumanDescription(requiredBindings));
}
//添加成员变量绑定
addFieldBinding(result, binding);
//添加方法绑定
addMethodBindings(result, binding);
}

findOptionalViewAsType 和 findRequiredViewAsType 有什么区别呢?我们从下面代码可以看出findRequiredViewAsType会在source中获取对应的控件,后判断是否获取到,如果没有获取到会抛出如下异常:

throw new IllegalStateException("Required view '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
+ " (methods) annotation.");

而最终的类型转换是通过cls.cast 方法来完成的。

public static <T> T findOptionalViewAsType(View source, @IdRes int id, String who,
Class<T> cls) {
View view = source.findViewById(id);
return castView(view, id, who, cls);
}

public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
Class<T> cls) {
View view = findRequiredView(source, id, who);
return castView(view, id, who, cls);
}

public static View findRequiredView(View source, @IdRes int id, String who) {
View view = source.findViewById(id);
if (view != null) {
return view;
}
String name = getResourceEntryName(source, id);
throw new IllegalStateException("Required view '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
+ " (methods) annotation.");
}

public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
try {
return cls.cast(view);
} catch (ClassCastException e) {
String name = getResourceEntryName(view, id);
throw new IllegalStateException("View '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was of the wrong type. See cause for more info.", e);
}
}

到此为止整个Butterknife源码分析完毕。老规矩上图:

Contents
  1. 1. [项目结构]:
  2. 2. [绑定过程]:
  3. 3. [Butterknife注释解析器ButterknifeProcessor]: