ButterKnife 是一个可以自动完成页面 findViewById 的工具,来自大名鼎鼎的 JakeWharton 之手。当然现在(2021.4.14)已经停止更新了,贴一下 github 上的说明:
Attention: This tool is now deprecated. Please switch to view binding. Existing versions will continue to work, obviously, but only critical bug fixes for integration with AGP will be considered. Feature development and general bug fixes have stopped.
那么这么好用的工具到底是怎么实现的呢?预知详情,让我们看看源码吧。
首先是简单使用,引入依赖后,通过在需要处理的 view 上添加 @BindView 注解,在 Activity onCreate 方法里面添加方法
ButterKnife.bind(this);
运行即可。
那么我们先看看这个 bind 方法做了什么?
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
// 1.首先获取了 decorView
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
// 2.获取 class ,找到对应的
Class<?> targetClass = target.getClass();
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
// 6.通过反射新建对象
try {
return constructor.newInstance(target, source);
} catch (IllegalAccessException e) {
// 删减了 catch 语句
}
}
@VisibleForTesting
static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
// 3.在构造方法缓存里面查找,找到了就直接返回
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null || BINDINGS.containsKey(cls)) {
return bindingCtor;
}
// 4.通过类前缀过滤掉系统类
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")
|| clsName.startsWith("androidx.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
// 5.通过反射加载 ButterKnife 帮自动生成的类,并获取构造方法
try {
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//noinspection unchecked
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
} // 省略 catch 语句
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
看一下 ButterKnife 自动生成的类长什么样
public class ButterKnifeTestActivity_ViewBinding implements Unbinder {
private ButterKnifeTestActivity target;
@UiThread
public ButterKnifeTestActivity_ViewBinding(ButterKnifeTestActivity target) {
this(target, target.getWindow().getDecorView());
}
// 7.最后执行到这里来,可见就会绑定上我们的 view 了
// target.btnFirst 这里也就说明了页面中我们定义的 view 属性不可以是 private 的
@UiThread
public ButterKnifeTestActivity_ViewBinding(ButterKnifeTestActivity target, View source) {
this.target = target;
target.btnFirst = Utils.findRequiredViewAsType(source, R.id.btnFirst, "field 'btnFirst'", Button.class);
}
@Override
@CallSuper
public void unbind() {
ButterKnifeTestActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.btnFirst = null;
}
}
基本流程就看完了,整体就是编译的时候根据我们的注解生成相关的类文件,然后通过反射调用,完成 findViewById 的操作。一套操作行云流水,真是不得不佩服大神的智慧啊。
这里面的 BINDINGS 这个 Map 我们可以注意下,通过 map 来缓存数据,避免多次通过反射来获取,提高效率。这种思想在我们平时写代码过程中也是可以经常使用的。学到了,有没有?