不是大牛,也能分析 ButterKnife 源码流程

ButterKnife是一个自动完成findViewById的工具,现已停止更新,但其通过注解和反射实现了便捷的视图绑定。源码分析显示,它在运行时通过反射创建自动生成的绑定类实例,将视图与目标对象关联。利用BINDINGS Map缓存构造方法以提高效率。绑定过程包括获取视图、找到对应绑定构造方法、实例化绑定类并执行绑定操作。
摘要由CSDN通过智能技术生成

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 来缓存数据,避免多次通过反射来获取,提高效率。这种思想在我们平时写代码过程中也是可以经常使用的。学到了,有没有?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值