ButterKnife的使用及其解析

本博客介绍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

  • 使用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()中进行解绑:

1
mUnbinder.unbind();

在Fragment中,需要在onCreateView()的return之前添加:

1
mUnbinder = ButterKnife.bind(this, view);

在Fragment的onDestroyView()中进行以下操作:

1
mUnbinder.unbind();

而在ListView或者RecyclerView的Adapter中也可以使用ButterKnife,需要做以下操作:

1
2
3
4
public (View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}

  • 使用ButterKnife绑定资源

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绑定监听

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

  • 使用ButterKnife可选绑定
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);
}
}

原文地址:https://www.cnblogs.com/lijianming180/p/12268257.html