Android 浅析 ButterKnife (二) 源码解析

前言

Linus Benedict Torvalds : RTFSC – Read The Funning Source Code

概括

这章将会根据由浅入深的过程浅析ButterKnife的注解方式。

工程结构

  • butterknife
  • butterknife-annotations
  • butterknife-compiler

可以看到工程的目录结构还是很清楚的
butterknife负责工程的主要逻辑调用入口。
butterknife-annotations负责所有注解的自定义文件。
butterknife-compiler负责在编译时解析Annotations。

butterknife-annotations

这里面的文件都是由注解组成。

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Bind a field to the view for the specified ID. The view will automatically be cast to the field
* type.
* <pre><code>
* {@literal @}Bind(R.id.title) TextView title;
* </code></pre>
*/
@Retention(CLASS) @Target(FIELD)
public @interface Bind {
/** View ID to which the field will be bound. */
@IdRes int[] value();
}

例如我们最经典的Bind方法的注解就是在这里定义的,这里通过注释我们可以很直接知道这些注解做的是什么就不再一一解析了。

butterknife-compiler

接下来我们看编译过程,这里是butterknife的主要部分。
所有的注解要在编译时进行解析,都需要自定义一个类继承于javax.annotation.processing.AbstractProcessor,通过复写其中的方法来实现。

先来看下支持注解的类类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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<>();
types.add(Bind.class.getCanonicalName());
for (Class<? extends Annotation> listener : LISTENERS) {
types.add(listener.getCanonicalName());
}
types.add(BindArray.class.getCanonicalName());
types.add(BindBitmap.class.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());
types.add(Unbinder.class.getCanonicalName());
return types;
}

这里我们可以看到基本都涵盖了我们在butterknife-annotations自定义的文件。

接着我们来看主要的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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();
//写进文件,生成辅助类
bindingClass.brewJava().writeTo(filer);
}
return true;
}

这里我们看到主要的process函数里实现的就三个功能
1:查找和解析注解;
2:循环遍历拿出注解中的键值;
3:写进文件,生成辅助类。

我们先来看第一个功能,查找和解析注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
// Process each @Bind element.
for (Element element : env.getElementsAnnotatedWith(Bind.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
parseBind(element, targetClassMap, erasedTargetNames);
}
// Process each annotation that corresponds to a listener.
for (Class<? extends Annotation> listener : LISTENERS) {
findAndParseListener(env, listener, targetClassMap, erasedTargetNames);
}
// Process each @BindInt element.
for (Element element : env.getElementsAnnotatedWith(BindInt.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceInt(element, targetClassMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindInt.class, e);
}
}
...
}

我们拿了一个比较简单的@BindInt来分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void parseResourceInt(Element element, Map<TypeElement, BindingClass> targetClassMap,
Set<TypeElement> erasedTargetNames) {
boolean hasError = false;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
...
// Verify common generated code restrictions.
hasError |= isInaccessibleViaGeneratedCode(BindInt.class, "fields", element);
hasError |= isBindingInWrongPackage(BindInt.class, element);
if (hasError) {return;}
// Assemble information on the field.
String name = element.getSimpleName().toString();
int id = element.getAnnotation(BindInt.class).value();
BindingClass bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
FieldResourceBinding binding = new FieldResourceBinding(id, name, "getInteger", false);
bindingClass.addResource(binding);
erasedTargetNames.add(enclosingElement);
}

这里我们首先看到isInaccessibleViaGeneratedCode(),它里面判断了三个点:

  1. 验证方法修饰符不能为privatestatic
  2. 验证包含类型不能为非Class
  3. 验证包含类的可见性并不是private

接着我们来看isBindingInWrongPackage,它判断了这个类的包名,包名不能以android.和java.开头,butterknife不可以在Android Framework和JDK框架内部使用。

最后就是调用getOrCreateTargetClass函数获取或者生成一个绑定类,并将之存入数组。

接下来我们肯定要分析最常用的注解@Bind了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void parseBind(Element element, Map<TypeElement, BindingClass> targetClassMap, Set<TypeElement> 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 {
parseBindOne(element, targetClassMap, erasedTargetNames);
}
}

看出这里第一步也是判断两个isInaccessibleViaGeneratedCode()isBindingInWrongPackage的条件,接着通过parseBindMany()来解析这个注解:

  1. 验证这个类型是一个List还是一个array
  2. 验证这个目标类型是否继承自View
  3. 在作用域里组装信息。
  4. 当然,最后将生成的BindingClass存入数组中。

分析完findAndParseTargets我们接着回到process往下看bindingClass.brewJava()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
JavaFile brewJava() {
TypeSpec.Builder result = TypeSpec.classBuilder(className)
.addModifiers(PUBLIC)
.addTypeVariable(TypeVariableName.get("T", ClassName.bestGuess(targetClass)));
if (parentViewBinder != null) {
result.superclass(ParameterizedTypeName.get(ClassName.bestGuess(parentViewBinder),
TypeVariableName.get("T")));
} else {
result.addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, TypeVariableName.get("T")));
}
if (hasUnbinder()) {
result.addType(createUnbinderClass());
}
result.addMethod(createBindMethod());
return JavaFile.builder(classPackage, result.build())
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}

这个函数通过将我们绑定类的信息写入到文件外还负责创建绑定和创建解除绑定。
在将绑定函数写入到文件后,整个编译器的注解方法就结束了。接下来就到运行时的注解过程了。

butterknife

Field and method binding for Android views. Use this class to simplify finding views and attaching listeners by binding them with annotations.
这是关于ButterKnife类的说明,关于ButterKnife的使用基本都是通过这个主类来实现的。
这里面最关键的函数就数bind()了:

1
2
3
public static void bind(@NonNull Activity target) {
bind(target, target, Finder.ACTIVITY);
}

每个bind函数实际上都是通过相同的函数不同参数实现。

1
2
3
4
5
6
static void bind(@NonNull Object target, @NonNull Object source, @NonNull Finder finder) {
Class<?> targetClass = target.getClass();
ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
viewBinder.bind(finder, target, source);
}

首先通过findViewBinderForClass生成每个类,然后再调用这些类的bind方法进行绑定,这些类的方法都是在编译期通过createBindMethod()方法一个个生成的。这些也就是辅助类的用处了。

总结

以上就是对ButterKnife的浅析,具体来说就是在你写下注解后,ButterKnife会帮你在编译期帮你把代码补全,然后在运行期来调用。