本博客介绍ButterKnife的使用及其源码解析。
ButterKnife的使用
ButterKnife简介
添加依赖
在Project级别的build.gradle
文件中添加为ButterKnife定制的Gradle插件:
1
2
3
4
5
6
7
8
9
|
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'com.jakewharton:butterknife-gradle-plugin:8.8.1'
}
}
|
在Application级别的build.gradle
文件中添加ButterKnife插件和依赖代码:
1
2
3
4
5
6
|
apply plugin: 'com.jakewharton.butterknife'
...
dependencies{
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
compile 'com.android.support:recyclerview-v7:26.0.0-alpha1'
}
|
使用ButterKnife
用注解@BindView()
绑定单个控件id,用注解@BindViews()
绑定多个控件id:
1
2
3
4
5
6
|
(R.id.et_input)
EditText etInput;
(R.id.btn_send)
Button btnSend;
(R.id.lv_main)
ListView lvMain;
|
在Activity中,需要在setContentView()
之后添加:
1
|
mUnbinder = ButterKnife.bind(this);
|
切记在Activity的onDestroy()
中进行解绑:
在Fragment中,需要在onCreateView()
的return之前添加:
1
|
mUnbinder = ButterKnife.bind(this, view);
|
在Fragment的onDestroyView()
中进行以下操作:
而在ListView或者RecyclerView的Adapter中也可以使用ButterKnife,需要做以下操作:
1
2
3
4
|
public (View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
|
ButterKnife提供了以下资源绑定方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@BindAnim()
//2、绑定数组资源id
@BindArray()
//3、绑定Bitmap相关的资源id
@BindBitmap()
//4、绑定boolean资源id
@BindBool()
//5、绑定color资源id
@BindColor()
//6、绑定尺寸资源id
@BindDimen()
//7、绑定drawable资源id
@BindDrawable()
//8、绑定float资源id
@BindFloat
//9、绑定字体资源id
@BindFont
//10、绑定int资源id
@BindInt
//11、绑定String资源id
@BindString
|
ButterKnife提供了以下监听绑定方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
//1、为添加`OnCheckedChangeListener`的View绑定资源id
@OnCheckedChanged()
//2、为添加`OnClickListener`的View绑定资源id
@OnClick()
//3、为添加`OnEditorActionListener`的View绑定资源id
@OnEditorAction()
//4、为添加`OnFocusChangeListener`的View绑定资源id
@OnFocusChange()
//5、为添加`OnItemClickListener`的View绑定资源id
@OnItemClick
//6、为添加`OnItemLongClickListener`的View绑定资源id
@OnItemLongClick()
//7、为添加`OnItemSelectedListener`的View绑定资源id
@OnItemSelected()
//8、为添加`OnLongClickListener`的View绑定资源id
@OnLongClick()
//9、为添加`OnPageChangeListener`的View绑定资源id
@OnPageChange()
//10、为添加`TextWatcher`的View绑定资源id
@OnTextChanged()
//11、为添加`OnTouchListener`的View绑定资源id
@OnTouch()
|
1
2
3
4
|
//1、防止空指针异常
@Nullable
//2、注入指定的视图不需要出现在该视图
@Optional
|
ButterKnife源码解析
ButterKnife采用的是编译时注解,自定义了很多常用注解,上文已经讲解在此不再赘述。以@BindView
注解为例:
1
2
3
4
|
@Retention(CLASS) @Target(FIELD)
public @interface BindView {
@IdRes int value();
}
|
其中@Retention(CLASS)
用来声明注解的保留策略,表明@BindView
注解是编译时注解,而@Target(FIELD)
则表明@BindView
注解作用于成员变量。
解析ButterKnife注解处理器ButterKnifeProcessor
要处理注解需要使用到注解处理器,ButterKnife的注解处理器是ButterKnifeProcessor
,继承自AbstractProcessor
。
1
2
3
4
|
@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
...
}
|
在注解处理器ButterKnifeProcessor
中注解处理的主要逻辑都在process()
中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
//1、查找和解析传入的目标`RoundEnvironment`对象并将其存入Map中
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
//2、遍历Map集合
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
//3、获取键TypeElement
TypeElement typeElement = entry.getKey();
//4、获取值BindingSet
BindingSet binding = entry.getValue();
//5、产生JavaFile
JavaFile javaFile = binding.brewJava(sdk, debuggable);
try {
//6、向JavaFile写入Filer
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
|
接下来查看在process()
中调用的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
|
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
//1、创建LinkedHashMap用于存储BindingBuilder
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
//2、创建Set用于存储builderMap键
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
scanForRClasses(env);
//3、解析每个@BindAnim注解
for (Element element : env.getElementsAnnotatedWith(BindAnim.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
//4、动画资源相应的解析方法
parseResourceAnimation(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindAnim.class, e);
}
}
...(此处省略n多个@BindXxx注解)
//5、创建ArrayDeque用于存储BindingSet.Builder对象
Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
new ArrayDeque<>(builderMap.entrySet());
//6、创建LinkedHashMap用于存储绑定中元素
Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
while (!entries.isEmpty()) {
//7、移除第一个元素(出队)
Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();
//8、获取移除元素的键
TypeElement type = entry.getKey();
//9、获取移除元素的值
BindingSet.Builder builder = entry.getValue();
//10、在提供的Set中找到父binder类型
TypeElement parentType = findParentType(type, erasedTargetNames);
if (parentType == null) {
bindingMap.put(type, builder.build());
} else {
BindingSet parentBinding = bindingMap.get(parentType);
if (parentBinding != null) {
builder.setParent(parentBinding);
bindingMap.put(type, builder.build());
} else {
//11、入队
entries.addLast(entry);
}
}
}
return bindingMap;
}
|
接下来查阅parseResourceAnimation()
代码:
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
|
private void parseResourceAnimation(Element element,
Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
boolean hasError = false;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
//1、验证目标类型是动画
if (!ANIMATION_TYPE.equals(element.asType().toString())) {
error(element, "@%s field type must be 'Animation'. (%s.%s)",
BindAnim.class.getSimpleName(), enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
//2、验证常见的生成代码限制
hasError |= isInaccessibleViaGeneratedCode(BindAnim.class, "fields", element);
hasError |= isBindingInWrongPackage(BindAnim.class, element);
if (hasError) {
return;
}
//3、收集信息在成员变量中
String name = element.getSimpleName().toString();
int id = element.getAnnotation(BindAnim.class).value();
QualifiedId qualifiedId = elementToQualifiedId(element, id);
BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
builder.addResource(new FieldAnimationBinding(getId(qualifiedId), name));
erasedTargetNames.add(enclosingElement);
}
|
其中,parseResourceAnimation()
中isInaccessibleViaGeneratedCode()
代码如下:
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
|
大专栏 ButterKnife的使用及其解析="code">
private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass,
String targetThing, Element element) {
boolean hasError = false;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
//1、方法修饰符不能为private和static
Set<Modifier> modifiers = element.getModifiers();
if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) {
error(element, "@%s %s must not be private or static. (%s.%s)",
annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
//2、包含类型不能为非class
if (enclosingElement.getKind() != CLASS) {
error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)",
annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
//3、包含类的修饰符不能为private
if (enclosingElement.getModifiers().contains(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;
}
|
其中,parseResourceAnimation()
中isBindingInWrongPackage()
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
private boolean isBindingInWrongPackage(Class<? extends Annotation> annotationClass,
Element element) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
String qualifiedName = enclosingElement.getQualifiedName().toString();
//1、判断类的包名不能以android开头
if (qualifiedName.startsWith("android.")) {
error(element, "@%s-annotated class incorrectly in Android framework package. (%s)",
annotationClass.getSimpleName(), qualifiedName);
return true;
}
//2、判断类的包名不能以java开头
if (qualifiedName.startsWith("java.")) {
error(element, "@%s-annotated class incorrectly in Java framework package. (%s)",
annotationClass.getSimpleName(), qualifiedName);
return true;
}
return false;
}
|
解析ButterKnife的bind()
ButterKnife的bind()运行在UI线程,在指定的Activity中注释变量和方法,当前的内容视图被用作根视图。
1
2
3
4
5
6
7
|
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
//1、获取根视图
View sourceView = target.getWindow().getDecorView();
//2、创建绑定
return createBinding(target, sourceView);
}
|
接下来查阅createBinding()
代码:
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
|
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
//1、获取目标Class
Class<?> targetClass = target.getClass();
if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
//2、为Class查找Binding构造方法
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
//3、返回构造方法实例对象
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()
代码:
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
|
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
//1、根据传入的Class从Map中获取Constructor对象
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();
//2、类名称如果以android或java开头返回null
if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
try {
//3、生成<类名>_ViewBinding的类
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
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);
}
//4、将Constructor放入Map并返回
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
|
而上段代码中多次出现的BINDINGS
是什么样的数据结构呢?
1
2
|
@VisibleForTesting
static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
|
解析ButterKnife生成辅助类
在上问分析中已经生成了<类名>_ViewBinding的类,下面分析<类名>_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
28
29
30
31
32
33
34
35
36
37
38
|
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
private View view2131427424;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
//1、构造方法分钟传入了MainActivity的DecorView
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
View view;
//2、根据类型查找到需要的View
target.etInput = Utils.findRequiredViewAsType(source, R.id.et_input, "field 'etInput'", EditText.class);
//3、查找需要的View
view = Utils.findRequiredView(source, R.id.btn_send, "field 'btnSend' and method 'OnClick'");
//4、View类型转换
target.btnSend = Utils.castView(view, R.id.btn_send, "field 'btnSend'", Button.class);
view2131427424 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.OnClick();
}
});
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.etInput = null;
target.btnSend = null;
view2131427424.setOnClickListener(null);
view2131427424 = null;
}
}
|
下面查阅Utils中的findRequiredViewAsType()
:
1
2
3
4
5
6
7
|
public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
Class<T> cls) {
//1、获取需要的View
View view = findRequiredView(source, id, who);
//2、View类型转换
return castView(view, id, who, cls);
}
|
下面查阅Utils中findRequiredView()
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public static View findRequiredView(View source, @IdRes int id, String who) {
//1、最终还是通过findViewById()
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.");
}
|
下面查阅Utils中castView()
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
try {
//1、类型转换类似于(TextView)findViewById()
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);
}
}
|
最新文章
- APUE 习题3-2 实现dup2,要求不使用fcntl函数。
- js中判断对象具体类型
- Win10 UWP 开发系列:支持异步的SQLite
- 快速掌握iOS API的一个小技巧
- sof文件和NIOS II的软件(elf)合并为jic文件以使用Quartus Programmer烧写
- error C2275: “XXX”: 将此类型用作表达式非法
- Reorder List [LeetCode]
- 【web】web欢迎页面执行servlet
- PHP 正则表达式语法
- BZOJ 3953 Self-Assembly 解题报告
- javascript控制图片等比例缩放
- 【Jsp】JSP自己定义标签与MODEL1、MODEL2标准
- Windows10系统故障检测你知道多少-上海IT33
- Android图片加载库Fresco
- 第一册:lesson109.
- python开发环境搭建及numpy基本属性-【老鱼学numpy】
- C#委托之我见
- Java设计模式—备忘录模式
- Stax解析XML示例代码
- centos 7.3systemctl工具
热门文章
- 微信小程序裁剪图片后上传
- DocCms_2016 代码审计
- 【Mongodb】mongoDB与mongoose---Scheme和Collections对应问题
- UI自动化(selenium+python)之元素定位的三种等待方式
- string.Format字符串格式化说明(转)
- springboot集成websocket实现大文件分块上传
- java 之断言
- 29)PHP,自动加载类
- VMware 安装 Centos7 后,没有ipv4的地址,或者地址显示127.0.0.1
- HTML颜色表