ButterKnife 源码解析 (一)

1. ButterKnife 的优势

  • 强大的 View 绑定和 Click 事件处理功能,简化代码,提升开发效率
  • 方便的处理 Adapter 里的 ViewHolder 绑定问题
  • 运行时不影响 App 效率,使用配置方便
    • 不通过反射技术实现,而是通过预编译生成的 Class 类文件
  • 代码清晰,可读性强

2. APT 和 IoC 架构的区别

APT 简介

APT 即为 Annotation Processing Tool,它是 javac 的一个工具,中文意思为编译时注解处理器APT 可以用来在编译时扫描和处理注解。通过 APT 可以获取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来自动的生成一些代码,省去了手动编写。注意,获取注解及生成代码都是在代码编译时候完成的,相比反射在运行时处理注解大大提高了程序性能。

新版的 ButterKnife 使用的技术就是 APT

DayIoCAPT
共同特点都实现了解耦~
核心技术运行时通过反射技术注解处理器技术
开发使用两者几乎一致,不好分清楚~
代码难易IoC 编程更具挑战性~
程序稳定两者暂未发现致命缺陷~
两者缺陷reflect会消耗一定性能,运行时消耗内存APT会增加apk大小,预编译期生成额外的Java
开发追求更偏向编译器的 APT 技术

3. ButterKnife 用法

  • https://github.com/JakeWharton/butterknife

4. ButterKnife 原理

4.1 代码中简单使用示例

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.activity_main_button)
    Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ButterKnife.bind(this);
    }

    @OnClick(R.id.activity_main_button)
    public void click(View view){
        Toast.makeText(this, "hi", Toast.LENGTH_SHORT).show();
    }
}

4.2 源码分析

ButterKnife.bind(this) 入手吧,点进来看看:

/**
   * BindView annotated fields and methods in the specified {@link Activity}. The current content
   * view is used as the view root.
   *
   * @param target Target activity for view binding.
   */
  @NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return bind(target, sourceView);
  }

再点:

/**
   * BindView annotated fields and methods in the specified {@code target} using the {@code source}
   * {@link View} as the view root.
   *
   * @param target Target class for view binding.
   * @param source View root on which IDs will be looked up.
   */
  @NonNull @UiThread
  public static Unbinder bind(@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 (..Exception e) {
      ...
    } 
  }
@Nullable @CheckResult @UiThread
  private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null || BINDINGS.containsKey(cls)) {
      ..
      return bindingCtor;
    }
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")
        || clsName.startsWith("androidx.")) {
      ..
      return null;
    }
    try {
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //noinspection unchecked
      bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
      ..
    } catch (ClassNotFoundException e) {
      ..
      bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
    } catch (NoSuchMethodException e) {
      ..
    }
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
  }

bind()方法的流程:

  1. 首先获取当前 ActivityDecorViewDecorView 是整个 ViewTree 的最顶层 View,包含标题 view 和内容 view 这两个子元素。我们一直调用的 setContentView() 方法其实就是往内容 view 中添加 view 元素。
  2. 然后调用createBinding(target, sourceView) --> findBindingConstructorForClass(targetClass) --> constructor.newInstance(target, source)

上面代码所做的就是发现 specified {@link Activity} 的构造方法,构造方法不为空,就 new 出来这个构造方法:

Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass)//问题关键在这, 再看:
|
|
//cls 即 targetClass
//在这先加载APT生成的这个类
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
|
|
//再去获取它的构造方法,即先加载这个类,再启动它的构造方法
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
|
|
BINDINGS.put(cls, bindingCtor);
|
|
return constructor.newInstance(target, source);

这里会加载一个 clsName_ViewBinding 类,然后获取这个类里面的双参数 (Activity, View) 构造方法,最后放在BINDINGS 里面,它是一个 map,主要作用是缓存。在下次使用的时候,就可以从缓存中获取到:

Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null || BINDINGS.containsKey(cls)) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }

4.3 再从生成的 MainActivity_ViewBinding 类分析

4.2 分析到 constructor.newInstance(target, source), 可以看到它调用了 MainActivity_ViewBinding的构造方法,即:

这个编译时生成的类位于:ButterKnifeDemo\app\build\generated\ap_generated_sources\debug\out\packagename\MainActivity_ViewBinding.java

@UiThread
  public MainActivity_ViewBinding(final MainActivity target, View source) {
    this.target = target;

    View view;
    view = Utils.findRequiredView(source, R.id.activity_main_button, "field 'button' and method 'click'");
    target.button = Utils.castView(view, R.id.activity_main_button, "field 'button'", Button.class);
    view7f070019 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.click(p0);
      }
    });
  }

Utils 类:

public static View findRequiredView(View source, @IdRes int id, String who) {
    View view = source.findViewById(id);//在这
    if (view != null) {
      return view;
    }
    ..
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值