文章目录
一. 概述
ButterKnife 是一个依赖注入框架,主要用于绑定View、一些View的事件等等,可以大大减少findViewById以及设置View事件监听器的代码,并且框架的性能相比于传统写法也没有什么太大的损耗。
二. 简单使用
ButterKnife的用法十分简单
导入依赖
android {
...
// Butterknife requires Java 8.
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'com.jakewharton:butterknife:10.2.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.0'
}
在代码中使用
在需要注入值的属性或者方法上使用对应的注解修饰,然后在onCreate中启动注解处理工具,然后在onDestroy中移除对属性的绑定。
public class MainActivity extends AppCompatActivity {
@BindView(R.id.cl)
ConstraintLayout cl;
@BindView(R.id.bt)
Button bt;
@BindString(R.string.app_name)
String appName;
@BindDrawable(R.drawable.ic_launcher_background)
Drawable icon;
@BindColor(R.color.colorAccent)
int color;
private Unbinder mUnbinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mUnbinder = ButterKnife.bind(this);
}
@OnClick(R.id.bt)
void onClick() {
}
@Override
protected void onDestroy() {
super.onDestroy();
mUnbinder.unbind();
}
}
三. 源码分析
我们将使用上方的例子来讲解ButterKnife,我们先来看看我们在例子中做了哪些操作
- 通过BindView将布局中对应id的布局绑定到对应的View
- 通过BindString将一个资源字符串绑定到一个String类型的属性
- 通过BindDrawable将一个资源类型的图片绑定到一个Drawable的属性
- 通过BindColor将一个颜色绑定到一个int类型的属性
- 通过OnClick为R.id.bt对用的控件设置了点击事件的方法回调
public class MainActivity extends AppCompatActivity {
@BindView(R.id.cl)
ConstraintLayout cl;
@BindView(R.id.bt)
Button bt;
@BindString(R.string.app_name)
String appName;
@BindDrawable(R.drawable.ic_launcher_background)
Drawable icon;
@BindColor(R.color.colorAccent)
int color;
private Unbinder mUnbinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mUnbinder = ButterKnife.bind(this);
}
@OnClick(R.id.bt)
void onClick() {
}
@Override
protected void onDestroy() {
super.onDestroy();
mUnbinder.unbind();
}
}
3.1 ViewBinding 类
上述代码使用了ButterKnife注解,在编译时就会被系统扫描到,然后在同级的包名目录下会生成一个[类名]_ViewBinding的一个类。
现在我们来看生成的MainActivity_ViewBinding类,这个类代码十分简单。
- 属性: 有一个MainActivity的属性,用于持有对MainActivity对象的引用。
- 构造方法:两个构造方法,在构造方法内对MainActivity中使用注解修饰的属性进行赋值或为指定的控件设置监听器
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
private View view7f070042;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
View view;
// 找到id为R.id.cl的控件,然后将其赋值给MainActivity中的名为cl的属性,也就是使用BindView修饰,注解值为R.id.cl的控件
target.cl = Utils.findRequiredViewAsType(source, R.id.cl, "field 'cl'", ConstraintLayout.class);
// 同理 找到id为.id.bt的控件,将其赋值给MainActivity中的名为bt的属性
view = Utils.findRequiredView(source, R.id.bt, "field 'bt' and method 'onClick'");
target.bt = Utils.castView(view, R.id.bt, "field 'bt'", Button.class);
view7f070042 = view;
//为R.id.bt的控件设置监听器
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.onClick();
}
});
Context context = source.getContext();
Resources res = context.getResources();
// 将R.color.colorAccent对应的颜色赋值给MainActivity中名为color的属性
target.color = ContextCompat.getColor(context, R.color.colorAccent);
// 将R.drawable.ic_launcher_background对应的图片赋值给MainActivity中名为icon的属性
target.icon = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background);
// 将R.string.app_name 对应的字符串值赋值给MainActivity中名为appName的属性
target.appName = res.getString(R.string.app_name);
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
// 移除被BindView修饰的View控件的引用
target.cl = null;
target.bt = null;
// 移除监听器
view7f070042.setOnClickListener(null);
view7f070042 = null;
}
}
这里以id为R.id.bt的控件为例进行讲解:
可以看到这边通过Utils这个工具类去获取这个id的控件,findRequiredView有三个参数,第一个source为DecorView对象,是MainActivity的顶级容器,第二个参数是需要获取控件的id,第三个参数为描述信息
public MainActivity_ViewBinding(final MainActivity target, View source) {
...
view = Utils.findRequiredView(source, R.id.bt, "field 'bt' and method 'onClick'");
}
我们来看看Utils工具类,不难发现,最终还是通过findViewById从source(这里是MainActivity的顶层View)获取到了控件,然后将其返回。
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);
}
public static View findRequiredView(View source, @IdRes int id, String who) {
//可以看到最终还是通过findViewById从source中找到View
View view = source.findViewById(id);
if (view != null) {
return view;
}
...
}
我们再回到MainActivity_ViewBinding的构造方法中,由于在例子中我们在属性名为bt的按钮设置了BindView的注解,所以这里将找到的控件赋值给MainActivity的bt属性,这里由于是直接赋值,因此被注解修饰的属性访问权限不能为private。
由于在例子中我们还给R.id.bt的控件设置了点击事件的回调方法,所以这边找到R.id.bt的控件之后,还需为其设置点击事件,并在回调方法中回调MainActivity的onClick方法。因此bt对应的控件被点击,MainActivity的onClick会被回调。
public MainActivity_ViewBinding(final MainActivity target, View source) {
...
//获取id为R.id.bt的View
view = Utils.findRequiredView(source, R.id.bt, "field 'bt' and method 'onClick'");
target.bt = Utils.castView(view, R.id.bt, "field 'bt'", Button.class);
view7f070042 = view;
//为R.id.bt的控件设置监听器
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.onClick();
}
});
}
最后 再讲下unbind这个方法,这个方法主要是用于移除之前绑定控件或者监听器的引用,在Activity销毁前调用,用于及时回收不必要的内存。
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
// 移除被BindView修饰的View控件的引用
target.cl = null;
target.bt = null;
// 移除监听器
view7f070042.setOnClickListener(null);
view7f070042 = null;
}
好了,MainActivity_ViewBinding类算是讲解完了,这里做一个小结
- Activity或者Fragment中有使用BindView等注解,在项目编译时,ButterKnife会在同级包目录下生成[类型]_ViewBinding类(一个类对应一个ViewBinding类)
- 在生成的类的构造方法中会对目标类中所有被注解修饰的属性进行值的注入,若存在监听器相关的注解,则同时为控件创建事件监听器,并回调目标类对应的方法
3.2 ButterKnife.bind
看到这,相信大家心里都有疑问,虽然生成了这样一个类,它在构造方法中完成了值的注入,但是也没看见ButterKnife调用啊。
我们再回到例子,看看是不是还漏了什么东西。对的,想必你也发现了,我们在onCreate方法中将MainActivity对象注入到了ButterKnife中,我们来看看这里做了什么操作。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mUnbinder = ButterKnife.bind(this);
}
}
ButterKnife这个类的代码也比较少
public final class ButterKnife {
private ButterKnife() {
throw new AssertionError("No instances.");
}
private static final String TAG = "ButterKnife";
private static boolean debug = false;
// 用于缓存_ViewBinding类的构造方法,避免每次通过反射去获取
@VisibleForTesting
static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
/** Control whether debug logging is enabled. */
public static void setDebug(boolean debug) {
ButterKnife.debug = debug;
}
// Activity注入
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
// View注入,例如自定义View中使用注解
@NonNull @UiThread
public static Unbinder bind(@NonNull View target) {
return bind(target, target);
}
// 对话框注入
@NonNull @UiThread
public static Unbinder bind(@NonNull Dialog target) {
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
// Activity注入
@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull Activity source) {
View sourceView = source.getWindow().getDecorView();
return bind(target, sourceView);
}
// 对话框注入
@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull Dialog source) {
View sourceView = source.getWindow().getDecorView();
return bind(target, sourceView);
}
@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
Constructor<?