众里寻它千百度——ButterKnife源码完全解析

一、概述

在很久很久之前,自从朋友推荐我用butterknife后, 从此的项目再也离不开butterknife了。问butterknife的原理,估计很多人都会回答注解加反射。我一开始也是以为是注解加反射,然而看了源码之后发现不单单用的注解加反射。那么下面我们就来分析一下ButterKnife的实现原理吧。本文基于ButterKnife8.6.0.

二、ButterKnife用法

做android开发应该大部分都用过ButterKnife(官方文档),没用过可以点击链接进去看看。就算没用过应该也听说过,毕竟是Jake Wharton出品的东西(话说现在Jake Wharton大神现在已经入职谷歌了)。要是没用过的话可以看看这篇文章,这是我自己用注解加反射实现的类似ButterKnife功能的代码。

三、ButterKnife原理

Butterknife用的APT(Annotation Processing Tool)编译时解析技术(现在已经改成了谷歌的更强大的annotationProcessor,APT已经停止更新了)。大概原理就是你声明的注解的生命周期为CLASS,然后继承AbstractProcessor类。继承这个类后,在编译的时候,编译器会扫描所有带有你要处理的注解的类,然后再调用AbstractProcessor的process方法,对注解进行处理,那么我们就可以在处理的时候,动态生成绑定事件或者控件的java代码,然后在运行的时候,直接调用方法完成绑定。

四、ButterKnife源码解析

说了这么多,我们还是直接从源码入手吧。

public class MainActivity extends AppCompatActivity {
   

    @BindView(R.id.tv)
    TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        tv.setText("点击");
    }

    @OnClick(R.id.tv)
    public void onViewClicked() {
        Toast.makeText(this,"111",Toast.LENGTH_SHORT).show();
    }
}

上边是我们在使用时候的代码,我们直接就从 ButterKnife.bind(this)入手吧,点进来看看:

  @NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return createBinding(target, sourceView);
  }

再点到createBinding(target, sourceView)里面看看:

private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

    if (constructor == null) {
      return Unbinder.EMPTY;
    }

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      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);
    }
  }

createBinding()方法主要就是拿到我们绑定的Activity的Class,然后通过Constructor构造器获取一个Unbinder子类的构造方法,然后在调用newInstance(target, source)通过构造方法获取到Unbinder子类的一个实例,这里传入两个参数,说明构造方法里需要两个参数。我们打开findBindingConstructorForClass()方法。

@Nullable @CheckResult @UiThread
  private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    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();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
    }
    try {
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //noinspection unchecked
      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);
    }
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
  }
@VisibleForTesting
  static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();

上面的BINDINGS是一个保存了Class为key,Class_ViewBinding为Value的一个LinkedHashMap,主要是做一下缓存,提高下次再来bind的性能。
第14行clsName 是我们传入要绑定的Activity类名,第16行通过反射调用构造方法,这里相当于拿到了Activity_ViewBinding这个类的实例。其实从类名可以看出来,相当于Activity的一个辅助类,这时候我们就要问了,我们在用的时候没有声明这个类啊?从哪里来的? 不要方,其实它就是我们在之前讲原理的时候说到的AbstractProcessor在编译的时候生成的一个类,我们后面再来看它,现在我们继续往下面分析。
前面我们说到,这个方法里面用linkhashMap做了下缓存,所以在下边,就把刚刚反射的bindingCtor作为value,Class作为key加入这个LinkedHashMap,下次再绑定这个类的时候,就直接在方法的开始的时候取出来用。

下边我们就来找找Activity_ViewBinding这个类。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值