logo头像

我有一个梦想

Butterknife源码解析

本文于 1271 天之前发表,文中内容可能已经过时。

[TOC]

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@BindView(R.id.about_titleView)
TitleView aboutTitleView;
@BindView(R.id.view_about)
LinearLayout viewAbout;

Unbinder mBinder;

onCreate(){
mBinder = ButterKnife.bind(this);
}

onDestory(){
mBinder.unbind();
}

使用方法极其简单,减少findViewById的重复代码

BindView源码如下:

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

源码解析

1
ButterKnife.bind(this);

进入bind方法:

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

获取当前该activity的window上的decorView(获取最顶层view),创建绑定操作

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}
...
//
return constructor.newInstance(target, source);
...
}

进入findBindingConstructorForClass方法:

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
@VisibleForTesting
static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();

@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
//从缓存中获取绑定关系(key--class类,value--构造函数)
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();
//排除android系统类和java系统类
if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
try {
//加载一个className_ViewBinding类
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_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;
}

最终生成一个_ViewBinding类

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
public class AboutActivity_ViewBinding implements Unbinder {
private AboutActivity target;

@UiThread
public AboutActivity_ViewBinding(AboutActivity target) {
this(target, target.getWindow().getDecorView());
}

@UiThread
public AboutActivity_ViewBinding(AboutActivity target, View source) {
this.target = target;

target.aboutTitleView = Utils.findRequiredViewAsType(source, R.id.about_titleView, "field 'aboutTitleView'", TitleView.class);
target.viewAbout = Utils.findRequiredViewAsType(source, R.id.view_about, "field 'viewAbout'", LinearLayout.class);
}

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

target.aboutTitleView = null;
target.viewAbout = null;
}
}

总结:bind方法后,新建一个ViewBinding类,用来做findviewById,onClick等操作,将注解节省的步骤在该类的构造方法中执行,当然这里的构造函数都是注解自动生成的,所以,是怎么样的操作实现这个类?

_ViewBinding类是怎么生成的?

这里要介绍一个注解器,ButterKnifeProcessor,这个注解器是解析注解,生成ViewBinding类的具体代码。

init

初始化调用

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
@Override public synchronized void init(ProcessingEnvironment env) {
super.init(env);

//获取viewBinding类
String sdk = env.getOptions().get(OPTION_SDK_INT);
if (sdk != null) {
try {
this.sdk = Integer.parseInt(sdk);
} catch (NumberFormatException e) {
env.getMessager()
.printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '"
+ sdk
+ "'. Falling back to API 1 support.");
}
}

//是否是debug模式
debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));

typeUtils = env.getTypeUtils();
filer = env.getFiler();
try {
trees = Trees.instance(processingEnv);
} catch (IllegalArgumentException ignored) {
try {
// Get original ProcessingEnvironment from Gradle-wrapped one or KAPT-wrapped one.
for (Field field : processingEnv.getClass().getDeclaredFields()) {
if (field.getName().equals("delegate") || field.getName().equals("processingEnv")) {
field.setAccessible(true);
ProcessingEnvironment javacEnv = (ProcessingEnvironment) field.get(processingEnv);
trees = Trees.instance(javacEnv);
break;
}
}
} catch (Throwable ignored2) {
}
}
}

该方法主要是获取注解所要生成的viewBing类并进行绑定,遍历java源代码,获取所有的Element元素

process

实际处理方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
//获取注解类型Element和程序代码对应关系
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, debuggable);
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}

return false;
}

BindingSet:对应注解类型的绑定配置

  1. 通过findAndParseTargets获取注解类型Element和配置的相关信息
  2. 遍历Map,生成JavaFile
  3. 生成_ViewBinding文件,将javaFile写入

进入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
54
55
56
57
58
59
60
61
62
63
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();

scanForRClasses(env);

......

// Process each @BindView element.
//获取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);
}

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

//获取对应的key和value
TypeElement type = entry.getKey();
BindingSet.Builder builder = entry.getValue();

//查找当前元素的父类元素
TypeElement parentType = findParentType(type, erasedTargetNames);
if (parentType == null) {
//没找到父类,重新放入队列
bindingMap.put(type, builder.build());
} else {
//获取父类Element对应的BindingSet
BindingSet parentBinding = bindingMap.get(parentType);
if (parentBinding != null) {
//设置父类BindingSet并添加到Map中
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;
}

  1. 获取到所有的bind类注解的Element
  2. 并执行parseBindView()方法,绑定buildingSet和TypeElement的对应关系:
  3. 获取父类BindingSet对象,重新构建绑定关系,并返回该对应关系

进入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
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
//获取当前对象的父类,如果在Activity中使用,那么enclosingElement就是Activity类
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

// Start by verifying common generated code restrictions.
//校验对象,不能使用private和static修饰
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);

// Verify that the target type extends from 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 {
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.
//获取注解的value资源id(控件id值)
int id = element.getAnnotation(BindView.class).value();
//获取父类的BindingSet.Builder
BindingSet.Builder builder = builderMap.get(enclosingElement);
//将id和对象进行绑定
QualifiedId qualifiedId = elementToQualifiedId(element, 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 {
//将父类和BindingSet.Builder进行绑定并添加到builderMap中,并返回这个binder对象
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}

String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
//判断是否使用Nullable注解
boolean required = isFieldRequired(element);

//将资源id,(对象名,对象类型,是否使用Nullable添加到builder中
builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));

// Add the type-erased version to the valid binding targets set.
//将父元素添加到集合中用来查找父元素信息
erasedTargetNames.add(enclosingElement);
}
  1. 校验对象修饰不能是private和static
  2. 校验View是否为对象的超类
  3. 获取资源id并进行绑定,添加到集合中

在上述分析过程中,生成一个BindingSet类再通过JavaFile生成对应文件

1
2
3
4
5
6
JavaFile brewJava(int sdk, boolean debuggable) {
return JavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable))
//添加类头注释
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}

将java版本和是否为debug传入,传入包名+类名,根据类型创建不同代码,JavaFile属于javapoet包类,使用javapoet将实现类代码生成文件。

getSupportedAnnotationTypes

获得要处理的注解

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
@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(BindAnim.class);
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(BindFont.class);
annotations.add(BindInt.class);
annotations.add(BindString.class);
annotations.add(BindView.class);
annotations.add(BindViews.class);
annotations.addAll(LISTENERS);

return annotations;
}

ButterknifeProcessor支持的所有注解类型

getSupportedSourceVersion

指定java版本

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

这个是基本写法,一般都这样写

总结

Butterknife只是针对view的注入框架,在编译过程中,使用自定义注解器的方式解析注解,生成对应关系后,通过BindingSet+JavaFile的javapoet技术将程序类写入文件,生成Activity_ViewBinding文件,在bind方法中,反射获取Activity_ViewBinding类的构造函数,存入缓存,返回构造函数后实例化对象,完成视图的绑定