Butterknife源码解析
本文于 1271 天之前发表,文中内容可能已经过时。
[TOC]
使用
1 |
|
使用方法极其简单,减少findViewById的重复代码
BindView源码如下:
1 |
|
源码解析
1 | ButterKnife.bind(this); |
进入bind方法:
1 |
|
获取当前该activity的window上的decorView(获取最顶层view),创建绑定操作
1 | private static Unbinder createBinding( Object target, View source){ |
进入findBindingConstructorForClass方法:
1 |
|
最终生成一个_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
27public class AboutActivity_ViewBinding implements Unbinder {
private AboutActivity target;
public AboutActivity_ViewBinding(AboutActivity target) {
this(target, target.getWindow().getDecorView());
}
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);
}
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 | public synchronized void init(ProcessingEnvironment env) { |
该方法主要是获取注解所要生成的viewBing类并进行绑定,遍历java源代码,获取所有的Element元素
process
实际处理方法
1 | public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { |
BindingSet:对应注解类型的绑定配置
- 通过findAndParseTargets获取注解类型Element和配置的相关信息
- 遍历Map,生成JavaFile
- 生成_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
63private 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;
}
- 获取到所有的bind类注解的Element
- 并执行parseBindView()方法,绑定buildingSet和TypeElement的对应关系:
- 获取父类BindingSet对象,重新构建绑定关系,并返回该对应关系
进入parseBindView中:
1 | private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap, |
- 校验对象修饰不能是private和static
- 校验View是否为对象的超类
- 获取资源id并进行绑定,添加到集合中
在上述分析过程中,生成一个BindingSet类再通过JavaFile生成对应文件1
2
3
4
5
6JavaFile 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 | public Set<String> getSupportedAnnotationTypes() { |
ButterknifeProcessor支持的所有注解类型
getSupportedSourceVersion
指定java版本1
2
3public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
这个是基本写法,一般都这样写
总结
Butterknife只是针对view的注入框架,在编译过程中,使用自定义注解器的方式解析注解,生成对应关系后,通过BindingSet+JavaFile的javapoet技术将程序类写入文件,生成Activity_ViewBinding文件,在bind方法中,反射获取Activity_ViewBinding类的构造函数,存入缓存,返回构造函数后实例化对象,完成视图的绑定