ButterKnife源码解析

本文深入探讨ButterKnife的工作原理,包括注解处理工具APT的应用、注解生命周期、反射技术等,展示了如何实现视图绑定及事件绑定。

1.使用ButterKnife首先在项目的build添加

 implementation 'com.jakewharton:butterknife:8.4.0'

就ok了。
2.我们直接上代码吧

public class SideslipDeletActivity extends AppCompatActivity {
    @BindView(R.id.my_recyclerView)
    RecyclerView my_recyclerView;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sideslip_delet);
        ButterKnife.bind(this);
    }
    @OnClick(R.id.btn_tv)
    public void btn_tv() {
        Toast.makeText(this,"show",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);
  }

我们可以看到通过target.getWindow().getDecorView()根据activity得到DecorView,然后在传递给createBinding。

private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
      //拿到SideslipDeletActivity .class
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
      //通过targetClass获取一个构造器
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

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

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
    //使用构造器构造一个实例,参数是target(SideslipDeletActivity )和source(SideslipDeletActivity 对应的根视图)
      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) {
   //首先从BINDINGS获取Constructor,BINDINGS可以理解为缓存,已经用过的Constructor都会记录在BINDINGS中
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    //如果从BINDINGS找到了Constructor直接返回
    if (bindingCtor != null) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    String clsName = cls.getName();
    //通过类名如果发现是android类文件或者java类文件,则返回空。这里可能奇怪为什么会遇到android或者java文件?我们带着疑问继续看
    if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
    }
    try {
    //通过反射获取到名叫SideslipDeletActivity_ViewBinding的类的构造器
      Class<?> bindingClass = Class.forName(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());
      //如果SideslipDeletActivity_ViewBinding这个类没有找到就获取SideslipDeletActivity.class的父类,继续执行     findBindingConstructorForClass,这里是个递归,这个递归什么时候停止呢?直到遇到android或者java类文件。
      bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
    } catch (NoSuchMethodException e) {
      throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
    }
     //最后将这个Constructor放到缓存中,这样再次用到MainActivity_ViewBinding时就可以不用反射,可以提高运行效率
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
  }

findBindingConstructorForClass方法是一个比较核心的方法
这个方法是利用SideslipDeletActivity.class类名,找到名叫SideslipDeletActivity_ViewBinding的类并创建一个实例。
其实经过ButterKnife的努力将语句ButterKnife.bind(this)替换成了new SideslipDeletActivity_ViewBinding(this, getWindow().getDecorView())
现在我们来看一下SideslipDeletActivity_ViewBinding

public class SideslipDeletActivity_ViewBinding<T extends SideslipDeletActivity> implements Unbinder {
  protected T target;

  @UiThread
  public SideslipDeletActivity_ViewBinding(T target, View source) {
    this.target = target;

    target.my_recyclerView = Utils.findRequiredViewAsType(source, R.id.my_recyclerView, "field 'my_recyclerView'", RecyclerView.class);
  }

  @Override
  @CallSuper
  public void unbind() {
    T target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");

    target.my_recyclerView = null;

    this.target = null;
  }
}

看一下findRequiredViewAsType()方法

public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
      Class<T> cls) {
    View view = findRequiredView(source, id, who);
    return castView(view, id, who, cls);
  }

有两个方法,我们先看findRequiredView()方法

 public static View findRequiredView(View source, @IdRes int id, String who) {
    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.");
  }

我们看到了findViewById()函数的返回值是View,所以我们来看下castView()

public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
    try {
      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);
    }
  }

这个方法的作用是类型的强制转换。
经过ButterKnife再一次的不懈努力,我们成功的将

@BindView(R.id.my_recyclerView)
RecyclerView my_recyclerView;

转换成了:

my_recyclerView= (TextView) getWindow().getDecorView().findViewById(R.id.my_recyclerView;

@onClick是如何转换的

public class MainActivity extends AppCompatActivity {

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


    }

    /**
     * 侧滑删除
     */
    @OnClick(R.id.btn_sideslip)
    public void btn_sideslip() {
        Navigate.startSideslipDeletActivity(MainActivity.this);
    }

}

我们来看一下对应MainActivity_ViewBinding

public class MainActivity_ViewBinding<T extends MainActivity> implements Unbinder {
  protected T target;

  private View view2131165218;

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

    View view;
    view = Utils.findRequiredView(source, R.id.btn_sideslip, "method 'btn_sideslip'");
    view2131165218 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.btn_sideslip();
      }
    });
  }

  @Override
  @CallSuper
  public void unbind() {
    if (this.target == null) throw new IllegalStateException("Bindings already cleared.");

    view2131165218.setOnClickListener(null);
    view2131165218 = null;

    this.target = null;
  }
}

从上面代码我们发现是DebouncingOnClickListener而不是View.OnClickListener我们点进去看一下

public abstract class DebouncingOnClickListener implements View.OnClickListener {
  static boolean enabled = true;

  private static final Runnable ENABLE_AGAIN = new Runnable() {
    @Override public void run() {
      enabled = true;
    }
  };

  @Override public final void onClick(View v) {
    if (enabled) {
      enabled = false;
      v.post(ENABLE_AGAIN);
      doClick(v);
    }
  }

  public abstract void doClick(View v);
}

作用是当一个Button被按下是,其他的Button都disable。
我们在这里看下们要看下ButterKnife.bind()方法的全家福。

//适用于activity,view注入
@NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return createBinding(target, sourceView);
  }

 //在view中绑定子view使用
  @NonNull @UiThread
  public static Unbinder bind(@NonNull View target) {
    return createBinding(target, target);
  }

 //适用于Dialog,view注入
  @NonNull @UiThread
  public static Unbinder bind(@NonNull Dialog target) {
    View sourceView = target.getWindow().getDecorView();
    return createBinding(target, sourceView);
  }

  //适用于在Activity外部使用Activity的view
  @NonNull @UiThread
  public static Unbinder bind(@NonNull Object target, @NonNull Activity source) {
    View sourceView = source.getWindow().getDecorView();
    return createBinding(target, sourceView);
  }

//适用于Fragment中,view注入
  @NonNull @UiThread
  public static Unbinder bind(@NonNull Object target, @NonNull View source) {
    return createBinding(target, source);
  }

//在dialog外部使用Dialog的view
  @NonNull @UiThread
  public static Unbinder bind(@NonNull Object target, @NonNull Dialog source) {
    View sourceView = source.getWindow().getDecorView();
    return createBinding(target, sourceView);
  }

用注解修饰的变量,和方法都不能试private,这是为什么呢
我们可以看一下,MainActivity_ViewBinding的包名,与MainActivity是相同的。而 MainActivity_ViewBinding使用了MainActivity中的btn_sideslip变量和btn_sideslip方法,如果设置成private会导致编译出错。所以不能试private的。在生成MainActivity_ViewBinding时会逐一检查注解修饰的所有变量和方法是否是private,如果是直接报错。
总结一下原理:
ButterKnife所注入注解框架是运行时注解,即声明注解的生命周期为RUNTIME,然后在运行的时候通过反射完成注入,这种方式虽然简单,但是这种方式多多少少会有性能的损耗。那么有没有一种方法能解决这种性能的损耗呢? 没错,答案肯定是有的,那就是Butterknife用的APT(Annotation Processing Tool)编译时解析技术。

APT大概就是你声明的注解的生命周期为CLASS,然后继承AbstractProcessor类。继承这个类后,在编译的时候,编译器会扫描所有带有你要处理的注解的类,然后再调用AbstractProcessor的process方法,对注解进行处理,那么我们就可以在处理的时候,动态生成绑定事件或者控件的java代码,然后在运行的时候,直接调用bind方法完成绑定。
其实这种方式的好处是我们不用再一遍一遍地写findViewById和onClick了,这个框架在编译的时候帮我们自动生成了这些代码,然后在运行的时候调用就行了。
基本流程图:
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值