前言
使用 ButterKnife 也有一段时间了,还记得我们从最开始的findViewById到现在使用注解来帮我们注入控件。其实这中间还有一个使用反射实现注入的一个阶段,真是因为在追求性能和今天,ButterKnife才会被广泛的使用.那么到底ButterKnife高效在哪里?今天博主带你全面了解ButterKnife
本文针对版本8.8.0讲解
首先在build.gradle中添加以下依赖
必要依赖
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.0'
compile 'com.jakewharton:butterknife-annotations:8.8.0'
compile 'com.jakewharton:butterknife:8.8.0'
第二个是使用ButterKnife的使用支持的注解,第三个是ButterKnife的核心实现,至于第一个是干嘛的下面说
支持的注解
可以看到现在的注解都不少呢,其实这里面的注解从名字上看,我们就可以很容易知道这些注解的用法,而我们最最常用的一个注解就是:@BindView 就是代替我们写 findViewById的.
每一个注解的用法,在这个注解的源代码中都会有一个例子,比如我们的@BindView
又比如我们注入多个控件到集合或者数组中的 @BindViews
所以这里关于注解的用法我就不陈述了,因为完全没必要了,每一个注解上的注释中都有示例代码
下面就开始讲解 ButterKnife的源码原理
ButterKnife的源码原理
简单的使用
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
tools:context="com.move.butterknife.MainAct">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="clickView"
android:text="click" />
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="Hello World!" />
</LinearLayout>
public class MainAct extends AppCompatActivity {
@BindView(R.id.tv)
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_main);
// 注入控件
ButterKnife.bind(this);
}
@OnTextChanged(R.id.tv)
public void checkChanged() {
System.out.println("checkChanged");
}
/**
* 按钮点击
*
* @param v
*/
public void clickView(View v) {
tv.setText("butterknife");
}
}
上面的代码很简单,我们通过注解注入 TextView 控件,然后绑定了一个 TextView 控件的文本改变事件,然后我们的按钮点击的时候会改变我们的TextView 控件的文本
点击了按钮以后,执行clickView(View v)方法,然后给文本设置了一个文本 “butterknife”,然后事件被监听到了,执行了 checkChanged() 方法,打印出 “checkChanged”
实现原理(1):生成代码
我们都知道,注解本身不具有任何的作用,比如我们的 @Override 等等,所以所有的奥秘都在下面的这句代码 和 编译时期生成代码上了.
// 注入控件
ButterKnife.bind(this);
我们在构建项目之后切换为Project视图,然后我们找到我们的Activity.class的目录
我们可以惊奇的发现多了一个MainAct_ViewBinding.class , 这个class 很明显不是我们写的java文件所编译过来的,肯定是 ButterKnife 生成的.那我们就点进去看看里面都有啥!
public class MainAct_ViewBinding implements Unbinder {
private MainAct target;
private View view2131427414;
private TextWatcher view2131427414TextWatcher;
@UiThread
public MainAct_ViewBinding(MainAct target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainAct_ViewBinding(final MainAct target, View source) {
this.target = target;
View view = Utils.findRequiredView(source, 2131427414, "field \'tv\' and method \'checkChanged\'");
target.tv = (TextView)Utils.castView(view, 2131427414, "field \'tv\'", TextView.class);
this.view2131427414 = view;
this.view2131427414TextWatcher = new TextWatcher() {
public void onTextChanged(CharSequence p0, int p1, int p2, int p3) {
target.checkChanged();
}
public void beforeTextChanged(CharSequence p0, int p1, int p2, int p3) {
}
public void afterTextChanged(Editable p0) {
}
};
((TextView)view).addTextChangedListener(this.view2131427414TextWatcher);
}
@CallSuper
public void unbind() {
MainAct target = this.target;
if(target == null) {
throw new IllegalStateException("Bindings already cleared.");
} else {
this.target = null;
target.tv = null;
((TextView)this.view2131427414).removeTextChangedListener(this.view2131427414TextWatcher);
this.view2131427414TextWatcher = null;
this.view2131427414 = null;
}
}
}
上述代码我们可以很清楚的看到,里面有findViewById的操作,而且有 addTextChangedListener 注册事件的过程,都是原生代码,不是反射的形式,我们对比我们MainAct中写的代码,可以很明显的发现这个类的代码就是我们在 MainAct 中写的注解解析之后的效果。
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.0'
所以这行代码的意思就是在编译的时候执行一个注解驱动器来生成上述的代码!
而生成的过程大家就可以忽略了,这可以说是另一块的知识,但是又和注解息息相关.其中的代码无非就是读取了有相应注解的类中的注解信息,然后生成字符串的过程,是一个枯燥无味的过程.但是确实ButterKnife 高效实现的关键之一.
但是这这些代码又是怎么样被执行的呢?
实现原理(2): 寻找实现类
上一步 ButterKnife 框架帮我们生成了我们应该写的代码,那么到底生成代码是怎么被执行的呢?
答案就在这里
// 注入控件
ButterKnife.bind(this);
我们点进去
重点的地方我都用红框框圈出来了,有一点反射的代码,相信你可以看懂.
总体就是,拿到需要注入的类 Class,拿到名称,我们的代码中就是
com.move.butterknife.MainAct
com.move.butterknife 是包名,然后在后面拼接上 _ViewBinding,然后根据这个名称寻找叫这个名称的 Class对象,也就是找到了这个类
com.move.butterknife.MainAct_ViewBinding
找到了以后获取构造方法,并创建实例对象,也就是创建了我们的 com.move.butterknife.MainAct_ViewBinding 对象,而你可以回头看生成的代码中,构造方法中就已经执行了控件注入和事件绑定的操作.所以一切都已经明了了.
所以对于ButterKnife框架的实现有两点是非常关键的:
- 编译时期根据注解中的信息,帮助我们生成一些必要的代码,这里就是会生成一个[你的类]_ViewBinding.java的类
- 在调用bind的时候,利用反射找到生成好的类来执行注入的过程.
总结
- 在ButterKnife中由于生成的代码是和你的待绑定的类是一个包的,所以你使用注解的成员属性必须至少是包访问权限的,你不能写一个private 修饰的!
- 在调用bind的时候,利用反射找到生成好的类来执行注入的过程.这个过程我们在上面可以看到是利用反射实现的,并不是很多人口口相传的一点反射代码都没有的!
- 在调用绑定方法之后,会返回一个 Unbinder 对象,很多人只管绑定,不管销毁,我们从生成的代码中可以看到,每一个生成的代码的类都实现了 Unbinder 接口,就是为了销毁注入的成员变量用的.所以正确的使用姿势应该是下面这样的
public class MainAct extends AppCompatActivity {
@BindView(R.id.tv)
TextView tv;
Unbinder unbinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_main);
// 注入控件
unbinder = ButterKnife.bind(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (unbinder != null) {
unbinder.unbind();
}
}
}
好了, ButterKnife 原理都介绍完毕了,有什么问题大家在评论区留言吧,博主会经历解答的!