Github: butterknife 分析版本:fc10f16

ButterKnife 是一个 Android 视图快速注入库,它通过给 View 字段添加注解,可以让我们丢掉 findViewById() 来获取 View 的方法,从而简化了代码。

编译时注解

概述

编译时注解的核心依赖 APT ( Annotation Processing Tools ) 实现,原理是在某些代码元素上(如类型、函数、字段等)添加注解,在编译时编译器会检查 AbstractProcessor 的子类,并且调用该类型的 process 函数,然后将添加了注解的所有元素都传递到 process 函数中,使得开发人员可以在编译器进行相应的处理,例如,根据注解生成新的Java类,这也就是EventBus,Retrofit,Dragger等开源库的基本原理。

创建

创建一个 Java Library

创建一个 annotationcompiler 的 java module

配置 gradle

1
2
sourceCompatibility = "1.7"
targetCompatibility = "1.7"

同时需要在 app 的 module 中配置一下 Java7

1
2
3
4
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}

创建 Annotation

1
2
3
4
5
package com.yydcdut.annotation;

public @interface InjectView {
int value();
}

创建 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
36
37
38
39
40
41
42
43
44
45
46
47
package com.yydcdut.process;

@SupportedAnnotationTypes("com.yydcdut.annotation.InjectView")
public class ViewInjectProcessor extends AbstractProcessor {

/**
* 初始化
*
* @param processingEnvironment 提供了一些实用的工具类Elements, Types和Filer
*/

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

/**
* 指定使用的java版本
*
* @return
*/

@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}

/**
* 指定哪些注解应该被注解处理器注册
*
* @return
*/

@Override
public Set<String> getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}

/**
* 主要的逻辑处理
*
* @param set
* @param env
* @return
*/

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
return false;
}
}

注册处理器

在 java module 里的 main 目录中创建一个 resources 文件夹,然后下边在创建 META-INF/services,创建一个 javax.annotation.processing.Processor 文件,在此文件中写入注解处理器的类全称

1
com.yydcdut.process.ViewInjectProcessor

添加 android-apt

在 project 下的 build.gradle 中添加 apt 插件

1
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

然后在 app 中的 build.gradle 添加 apt 插件

1
2
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'

添加依赖

在 app 中的 build.gradle 添加依赖

1
2
3
dependencies {
compile project(':annotationcompiler')
}

Code

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
36
37
38
39
40
41
42
43
44
45
46
@SupportedAnnotationTypes("com.yydcdut.annotation.InjectView")
public class ViewInjectProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<String>();
types.add(InjectView.class.getCanonicalName());
return types;
}

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
generateCode(set, env);
return true;
}

private void generateCode(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
StringBuilder builder = new StringBuilder()
.append("package com.yydcdut.test.generated;\n\n")
.append("public class GeneratedClass {\n\n") // open class
.append("\tpublic String getMessage() {\n") // open method
.append("\t\treturn \"");
// for each javax.lang.model.element.Element annotated with the CustomAnnotation
for (Element element : roundEnv.getElementsAnnotatedWith(InjectView.class)) {
int id = element.getAnnotation(InjectView.class).value();
builder.append(id + "\n");
}
builder.append("\";\n") // end return
.append("\t}\n") // close method
.append("}\n"); // close class


try { // write the file
JavaFileObject source = processingEnv.getFiler().createSourceFile("com.yydcdut.test.generated.GeneratedClass");


Writer writer = source.openWriter();
writer.write(builder.toString());
writer.flush();
writer.close();
} catch (IOException e) {
// Note: calling e.printStackTrace() will print IO errors
// that occur from the file already existing after its first run, this is normal
e.printStackTrace();
}
}
}

被注解的部分代码:

1
2
3
4
5
6
package com.yydcdut.textdemo;

public class MainActivity extends Activity {
@InjectView(value = 123)
private EditText mEditText;
}

编译之后查看 app/build/generated/source/apt/debug/com/yydcdut/test/generated/GeneratedClass.java

GeneratedClass

源码解析

ButterKnifeProcessor

这里只分析 BindView 的过程

ButterKnifeProcessor

根据上述创建注解器的流程,那么我们分析源码也有了一个流程,就直接看 ButterKnife 的 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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
@AutoService(Processor.class)//自动注册处理器
public final class ButterKnifeProcessor extends AbstractProcessor {

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

private Elements elementUtils;//处理Element的工具类
private Types typeUtils;//处理TypeMirror的工具类
private Filer filer;//可以创建.java文件
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
//......
elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
filer = env.getFiler();
//......
}

@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}

private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();

annotations.add(BindArray.class);
annotations.add(BindBitmap.class);
annotations.add(BindBool.class);
annotations.add(BindColor.class);
annotations.add(BindDimen.class);
annotations.add(BindDrawable.class);
annotations.add(BindFloat.class);
annotations.add(BindInt.class);
annotations.add(BindString.class);
annotations.add(BindView.class);
annotations.add(BindViews.class);
annotations.addAll(LISTENERS);

return annotations;
}

@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}

@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);////查找所有的被注解,封装成BindingSet保存到map中

//遍历步map的生成.java文件
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;
}
}

process 中主要做了两件事,第一是找出被注解的元素,封装成 Map<TypeElement, BindingSet> ,第一个参数可以想成类,第二个参数可以想成所有有关信息的封装;第二就是遍历这个 map ,然后针对每个类生成对应的 ViewBinder 类。

ButterKnifeProcessor#findAndParseTargets

那么主要的查找工作在 findAndParseTargets() 中进行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public final class ButterKnifeProcessor extends AbstractProcessor {

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

// Process each @BindView element.
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {//拿到被BindView注解的元素
// 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);
}
}
// .......

return bindingMap;
}
}

RoundEnvironment 可以理解成查询注解信息的类,而 Element 可以理解成程序中的元素,比如包、类、方法等等。

1
2
3
4
5
6
7
8
9
10
public class ClassA { // TypeElement
private int var; // VariableElement

public ClassA() {// ExecuteableElement
}

public void setA(int newA // TypeElement
)
{

}
}

TypeElement 代表源代码中元素类型,但是 TypeElement 并不包含类的相关信息,可以从 TypeElement 获取类的名称,但不能获取类的信息,比如说父类。这些信息可以通过 TypeMirror 获取。你可以通过调用 element.asType() 来获取一个 Element 的 TypeMirror 。

ButterKnifeProcessor#parseBindView

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
public final class ButterKnifeProcessor extends AbstractProcessor {
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();//类 com.yydcdut.textdemo.MainActivity

// Start by verifying common generated code restrictions.
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);//检查合法性

// Verify that the target type extends from View.
TypeMirror elementType = element.asType();//android.widget.EditText
if (elementType.getKind() == TypeKind.TYPEVAR) {//false (elementType.getKind()==DECLARED)
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
Name qualifiedName = enclosingElement.getQualifiedName();//com.yydcdut.textdemo.MainActivity
Name simpleName = element.getSimpleName();//mEditText
if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {//必须为view类型的子类或者是接口
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;
}

// Assemble information on the field.
int id = element.getAnnotation(BindView.class).value();//得到id

BindingSet.Builder builder = builderMap.get(enclosingElement);
QualifiedId qualifiedId = elementToQualifiedId(element, id);//封装成QualifiedId
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 {
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);//为这个类创建BindingSet.Builder
}

String name = simpleName.toString();//mEditText
TypeName type = TypeName.get(elementType);//android.widget.EditText
boolean required = isFieldRequired(element);

builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));//封装后放入builder中

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

/**
* 判断修饰符等
*
* @param annotationClass
* @param targetThing
* @param element
* @return
*/

private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass, String targetThing, Element element) {
boolean hasError = false;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

// Verify method modifiers.
Set<Modifier> modifiers = element.getModifiers();
if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) {//不能是private或者static的
error(element, "@%s %s must not be private or static. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName());
hasError = true;
}

// Verify containing type.
if (enclosingElement.getKind() != CLASS) {//不能是class
error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName());
hasError = true;
}

// Verify containing class visibility is not private.
if (enclosingElement.getModifiers().contains(PRIVATE)) {//不能是private
error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName());
hasError = true;
}

return hasError;
}

/**
* 判断是否弄错了包名
* 弄成android或者java的
*
* @param annotationClass
* @param element
* @return
*/

private boolean isBindingInWrongPackage(Class<? extends Annotation> annotationClass, Element element) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
String qualifiedName = enclosingElement.getQualifiedName().toString();//com.yydcdut.textdemo.MainActivity

if (qualifiedName.startsWith("android.")) {
error(element, "@%s-annotated class incorrectly in Android framework package. (%s)",
annotationClass.getSimpleName(), qualifiedName);
return true;
}
if (qualifiedName.startsWith("java.")) {
error(element, "@%s-annotated class incorrectly in Java framework package. (%s)",
annotationClass.getSimpleName(), qualifiedName);
return true;
}

return false;
}

/**
* 是不是otherType的子类型
*
* @param typeMirror
* @param otherType
* @return
*/

static boolean isSubtypeOfType(TypeMirror typeMirror, String otherType) {
if (isTypeEqual(typeMirror, otherType)) {
return true;
}
if (typeMirror.getKind() != TypeKind.DECLARED) {
return false;
}
DeclaredType declaredType = (DeclaredType) typeMirror;
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
if (typeArguments.size() > 0) {
StringBuilder typeString = new StringBuilder(declaredType.asElement().toString());
typeString.append('<');
for (int i = 0; i < typeArguments.size(); i++) {
if (i > 0) {
typeString.append(',');
}
typeString.append('?');
}
typeString.append('>');
if (typeString.toString().equals(otherType)) {
return true;
}
}
Element element = declaredType.asElement();
if (!(element instanceof TypeElement)) {
return false;
}
TypeElement typeElement = (TypeElement) element;
TypeMirror superType = typeElement.getSuperclass();
if (isSubtypeOfType(superType, otherType)) {
return true;
}
for (TypeMirror interfaceType : typeElement.getInterfaces()) {
if (isSubtypeOfType(interfaceType, otherType)) {
return true;
}
}
return false;
}

/**
* 是不是接口
*
* @param typeMirror
* @return
*/

private boolean isInterface(TypeMirror typeMirror) {
return typeMirror instanceof DeclaredType
&& ((DeclaredType) typeMirror).asElement().getKind() == INTERFACE;
}

private QualifiedId elementToQualifiedId(Element element, int id) {
return new QualifiedId(elementUtils.getPackageOf(element).getQualifiedName().toString(), id);
}

/**
* 判断有没有被Nullable修饰
*
* @param element
* @return
*/

private static boolean isFieldRequired(Element element) {
return !hasAnnotationWithName(element, NULLABLE_ANNOTATION_NAME);
}

private BindingSet.Builder getOrCreateBindingBuilder(
Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement)
{

BindingSet.Builder builder = builderMap.get(enclosingElement);
if (builder == null) {
builder = BindingSet.newBuilder(enclosingElement);
builderMap.put(enclosingElement, builder);
}
return builder;
}
}

这里是解析被 BindView 修饰的元素,将信息都封装成 BindingSet.Builder

BindingSet#newBuilder

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
final class BindingSet {

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 final ImmutableList<ViewBinding> viewBindings;
private final ImmutableList<FieldCollectionViewBinding> collectionBindings;
private final ImmutableList<ResourceBinding> resourceBindings;
private final BindingSet parentBinding;

private BindingSet(TypeName targetTypeName, ClassName bindingClassName, boolean isFinal,
boolean isView, boolean isActivity, boolean isDialog, ImmutableList<ViewBinding> viewBindings,
ImmutableList<FieldCollectionViewBinding> collectionBindings,
ImmutableList<ResourceBinding> resourceBindings, BindingSet parentBinding)
{

this.isFinal = isFinal;
this.targetTypeName = targetTypeName;
this.bindingClassName = bindingClassName;
this.isView = isView;
this.isActivity = isActivity;
this.isDialog = isDialog;
this.viewBindings = viewBindings;
this.collectionBindings = collectionBindings;
this.resourceBindings = resourceBindings;
this.parentBinding = parentBinding;
}

static Builder newBuilder(TypeElement enclosingElement) {
TypeMirror typeMirror = enclosingElement.asType();//android.widget.EditText

boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);//是不是View
boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);//是不是Activity
boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);//是不是Dialog

TypeName targetType = TypeName.get(typeMirror);
if (targetType instanceof ParameterizedTypeName) {
targetType = ((ParameterizedTypeName) targetType).rawType;
}

String packageName = getPackage(enclosingElement).getQualifiedName().toString();//com.yydcdut.textdemo
String className = enclosingElement.getQualifiedName().toString().substring(
packageName.length() + 1).replace('.', '$');//MainActivity
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");//com.yydcdut.textdemo.MainActivity_ViewBinding

boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);//是不是final修饰的
return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
}

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 final Map<Id, ViewBinding.Builder> viewIdMap = new LinkedHashMap<>();

private Builder(TypeName targetTypeName, ClassName bindingClassName, boolean isFinal,
boolean isView, boolean isActivity, boolean isDialog)
{

this.targetTypeName = targetTypeName;
this.bindingClassName = bindingClassName;
this.isFinal = isFinal;
this.isView = isView;
this.isActivity = isActivity;
this.isDialog = isDialog;
}

void addField(Id id, FieldViewBinding binding) {
getOrCreateViewBindings(id).setFieldBinding(binding);
}

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());
}
return new BindingSet(targetTypeName, bindingClassName, isFinal, isView, isActivity, isDialog, viewBindings.build(), collectionBindings.build(), resourceBindings.build(), parentBinding);
}
}
}

ButterKnifeProcessor#findAndParseTargets

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public final class ButterKnifeProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
//....

//....
// 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();//com.yydcdut.textdemo.MainActivity
BindingSet.Builder builder = entry.getValue();

TypeElement parentType = findParentType(type, erasedTargetNames);//父类
if (parentType == null) {//自己就是了
bindingMap.put(type, builder.build());
} else {
BindingSet parentBinding = bindingMap.get(parentType);//拿到父类的BindingSet
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;
}

/**
* Finds the parent binder type in the supplied set, if any.
*/

private TypeElement findParentType(TypeElement typeElement, Set<TypeElement> parents) {
TypeMirror type;
while (true) {
type = typeElement.getSuperclass();
if (type.getKind() == TypeKind.NONE) {
return null;
}
typeElement = (TypeElement) ((DeclaredType) type).asElement();
if (parents.contains(typeElement)) {
return typeElement;
}
}
}
}

ButterKnifeProcessor#process

findAndParseTargets 算是解析完了,再回过头来看第二步,生成文件的步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public final class ButterKnifeProcessor extends AbstractProcessor {
@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
try {
javaFile.writeTo(filer);//生成文件
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}

return false;
}
}

将信息转变成 JavaFile ,通过 filer 完成。

write过程

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
final class BindingSet {
JavaFile brewJava(int sdk) {
return JavaFile.builder(bindingClassName.packageName(), createType(sdk))//packageName-->com.yydcdut.textdemo
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}

private TypeSpec createType(int sdk) {
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(PUBLIC);//申明类
if (isFinal) {
result.addModifiers(FINAL);//添加final关键字
}

if (parentBinding != null) {
result.superclass(parentBinding.bindingClassName);//继承父类
} else {
result.addSuperinterface(UNBINDER);//实现Unbinder接口
}

if (hasTargetField()) {
result.addField(targetTypeName, "target", PRIVATE);//设置private的target变量
}

if (isView) {
result.addMethod(createBindingConstructorForView());
} else if (isActivity) {
result.addMethod(createBindingConstructorForActivity());
} else if (isDialog) {
result.addMethod(createBindingConstructorForDialog());
}
if (!constructorNeedsView()) {
// Add a delegating constructor with a target type + view signature for reflective use.
result.addMethod(createBindingViewDelegateConstructor());
}
result.addMethod(createBindingConstructor(sdk));

if (hasViewBindings() || parentBinding == null) {
result.addMethod(createBindingUnbindMethod(result));
}

return result.build();
}

private MethodSpec createBindingConstructorForView() {
MethodSpec.Builder builder = MethodSpec.constructorBuilder()//创建构造器
.addAnnotation(UI_THREAD)//添加UIThread的注解
.addModifiers(PUBLIC)//为public
.addParameter(targetTypeName, "target");//构造函数中得有target参数
if (constructorNeedsView()) {//构造函数中的方法
builder.addStatement("this(target, target)");
} else {
builder.addStatement("this(target, target.getContext())");
}
return builder.build();
}

/**
* True if this binding requires a view. Otherwise only a context is needed.
*/

private boolean constructorNeedsView() {
return hasViewBindings() //
|| parentBinding != null && parentBinding.constructorNeedsView();
}

private MethodSpec createBindingViewDelegateConstructor() {
return MethodSpec.constructorBuilder()
.addJavadoc("@deprecated Use {@link #$T($T, $T)} for direct creation.\n "
+ "Only present for runtime invocation through {@code ButterKnife.bind()}.\n",
bindingClassName, targetTypeName, CONTEXT)
.addAnnotation(Deprecated.class)
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC)
.addParameter(targetTypeName, "target")
.addParameter(VIEW, "source")
.addStatement(("this(target, source.getContext())"))
.build();
}

private MethodSpec createBindingConstructor(int sdk) {
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()//构造方法
.addAnnotation(UI_THREAD)//UIThread注解
.addModifiers(PUBLIC);//public

if (hasMethodBindings()) {
constructor.addParameter(targetTypeName, "target", FINAL);//final的target变量
} else {
constructor.addParameter(targetTypeName, "target");
}

if (constructorNeedsView()) {
constructor.addParameter(VIEW, "source");//变量名为source的View
} else {
constructor.addParameter(CONTEXT, "context");//变量名为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());
}

if (hasOnTouchMethodBindings()) {
constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
.addMember("value", "$S", "ClickableViewAccessibility")
.build());
}

if (parentBinding != null) {//如果有父类,则调用super
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");
}
if (hasTargetField()) {
constructor.addStatement("this.target = target");//赋值
constructor.addCode("\n");
}

if (hasViewBindings()) {
if (hasViewLocal()) {
// Local variable in which all views will be temporarily stored.
constructor.addStatement("$T view", VIEW);
}
for (ViewBinding binding : viewBindings) {
addViewBinding(constructor, binding);//View的初始化
}
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));//通过不同的SDK版本实现Resource相关(Drawable,Color等)的初始化
}
}

return constructor.build();
}

private void addViewBinding(MethodSpec.Builder result, ViewBinding binding) {
if (binding.isSingleFieldBinding()) {//只有findView的情况,没有监听器
// Optimize the common case where there's a single binding directly to a field.
FieldViewBinding fieldBinding = binding.getFieldBinding();
CodeBlock.Builder builder = CodeBlock.builder()
.add("target.$L = ", fieldBinding.getName());//(target-->MainActivity) ($L-->mEditText)

boolean requiresCast = requiresCast(fieldBinding.getType());//是否需要强转
if (!requiresCast && !fieldBinding.isRequired()) {
builder.add("source.findViewById($L)", binding.getId().code);//findViewById
} else {
builder.add("$T.find", UTILS);
builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");
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(")");//target.mEditText = Utils.findRequiredViewAsType(source, R.id.edit, "field 'mEditText'", EditText.class);
}
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));//view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
}

addFieldBinding(result, binding);//强转
addMethodBindings(result, binding);//各种监听器
}

private void addFieldBinding(MethodSpec.Builder result, ViewBinding binding) {
FieldViewBinding fieldBinding = binding.getFieldBinding();
if (fieldBinding != null) {
if (requiresCast(fieldBinding.getType())) {
result.addStatement("target.$L = $T.castView(view, $L, $S, $T.class)",
fieldBinding.getName(), UTILS, binding.getId().code,
asHumanDescription(singletonList(fieldBinding)), fieldBinding.getRawType());// target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
} else {
result.addStatement("target.$L = view", fieldBinding.getName());
}
}
}
}

最后文件的生成是通过 com.squareup.javapoet.JavaFile 生成的。

官方 demo 的栗子

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.example.butterknife.library;

import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.util.List;

import butterknife.BindView;
import butterknife.BindViews;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.OnItemClick;
import butterknife.OnLongClick;

import static android.widget.Toast.LENGTH_SHORT;

public class SimpleActivity extends Activity {
private static final ButterKnife.Action<View> ALPHA_FADE = new ButterKnife.Action<View>() {
@Override
public void apply(@NonNull View view, int index) {
AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
alphaAnimation.setFillBefore(true);
alphaAnimation.setDuration(500);
alphaAnimation.setStartOffset(index * 100);
view.startAnimation(alphaAnimation);
}
};

@BindView(R2.id.title)
TextView title;
@BindView(R2.id.subtitle)
TextView subtitle;
@BindView(R2.id.hello)
Button hello;
@BindView(R2.id.list_of_things)
ListView listOfThings;
@BindView(R2.id.footer)
TextView footer;

@BindViews({R2.id.title, R2.id.subtitle, R2.id.hello})
List<View> headerViews;

private SimpleAdapter adapter;

@OnClick(R2.id.hello)
void sayHello() {
Toast.makeText(this, "Hello, views!", LENGTH_SHORT).show();
ButterKnife.apply(headerViews, ALPHA_FADE);
}

@OnLongClick(R2.id.hello)
boolean sayGetOffMe() {
Toast.makeText(this, "Let go of me!", LENGTH_SHORT).show();
return true;
}

@OnItemClick(R2.id.list_of_things)
void onItemClick(int position) {
Toast.makeText(this, "You clicked: " + adapter.getItem(position), LENGTH_SHORT).show();
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);

// Contrived code to use the bound fields.
title.setText("Butter Knife");
subtitle.setText("Field and method binding for Android views.");
footer.setText("by Jake Wharton");
hello.setText("Say Hello");

adapter = new SimpleAdapter(this);
listOfThings.setAdapter(adapter);
}
}

官方 demo 的 apt 生成类的栗子

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// Generated code from Butter Knife. Do not modify!
package com.example.butterknife.library;

import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import butterknife.Unbinder;
import butterknife.internal.DebouncingOnClickListener;
import butterknife.internal.Utils;
import java.lang.IllegalStateException;
import java.lang.Override;

public class SimpleActivity_ViewBinding<T extends SimpleActivity> implements Unbinder {
protected T target;

private View view2130968578;

private View view2130968579;

@UiThread
public SimpleActivity_ViewBinding(final T target, View source) {
this.target = target;

View view;
target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class);
target.subtitle = Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class);
view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
view2130968578 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.sayHello();
}
});
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View p0) {
return target.sayGetOffMe();
}
});
view = Utils.findRequiredView(source, R.id.list_of_things, "field 'listOfThings' and method 'onItemClick'");
target.listOfThings = Utils.castView(view, R.id.list_of_things, "field 'listOfThings'", ListView.class);
view2130968579 = view;
((AdapterView<?>) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> p0, View p1, int p2, long p3) {
target.onItemClick(p2);
}
});
target.footer = Utils.findRequiredViewAsType(source, R.id.footer, "field 'footer'", TextView.class);
target.headerViews = Utils.listOf(
Utils.findRequiredView(source, R.id.title, "field 'headerViews'"),
Utils.findRequiredView(source, R.id.subtitle, "field 'headerViews'"),
Utils.findRequiredView(source, R.id.hello, "field 'headerViews'"));
}

@Override
@CallSuper
public void unbind() {
T target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");

target.title = null;
target.subtitle = null;
target.hello = null;
target.listOfThings = null;
target.footer = null;
target.headerViews = null;

view2130968578.setOnClickListener(null);
view2130968578.setOnLongClickListener(null);
view2130968578 = null;
((AdapterView<?>) view2130968579).setOnItemClickListener(null);
view2130968579 = null;

this.target = null;
}
}

ButterKnife

当要进行 bind 的时候,都需要在 Activity 或 View 等的初始化函数中进行绑定,这里就拿 Activity 为栗子进行分析:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public final class ButterKnife {
/**
* BindView annotated fields and methods in the specified {@link Activity}. The current content
* view is used as the view root.
*
* @param target Target activity for view binding.
*/

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

private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
if (debug) {
Log.d(TAG, "Looking up binding for " + targetClass.getName());
}
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

if (constructor == null) {
return Unbinder.EMPTY;
}

//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
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);
}
}

@Nullable
@CheckResult
@UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
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();
if (clsName.startsWith("android.") || clsName.startsWith("java.")) {//排除android和java的包
if (debug) {
Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
}
return null;
}
try {
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");//加载后面加"_ViewBinding"的累
//noinspection unchecked
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);//初始化
if (debug) {
Log.d(TAG, "HIT: Loaded binding class and constructor.");
}
} catch (ClassNotFoundException e) {
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;
}
}

最终通过 ClassLoader 的方式将类加载出来,最后 constructor.newInstance 方法来调用该类的构造函数。

总结

平时了解到更多的是运行时注解,即声明注解的生命周期为 RUNTIME ,然后在运行的时候通过反射完成注入,这种方式虽然简单,但是设计到比较多的反射,必然多多少少会有性能的损耗。而 ButterKnife 用的 APT 编译时解析技术,比较好的解决了反射这些问题。

APT 大概就是声明的注解的生命周期为 CLASS ,然后继承 AbstractProcessor 类。继承这个类后,在编译的时候,编译器会扫描所有带有你要处理的注解的类,然后再调用 AbstractProcessor 的 process 方法,对注解进行处理,那么就可以在处理的时候,动态生成绑定事件或者控件的 java 代码,然后在运行的时候,直接调用 bind 方法完成绑定。
其实这种方式的好处是我们不用再一遍一遍地写 findViewById 和 onClick 等代码了,这个框架在编译的时候帮我们自动生成了这些代码,然后在运行的时候调用就行了。