一、简介
Butterknife是一款andoid平台应用广泛的依赖注入框架,使用Butterknife可以避免大量的findViewById,setOnClickListener等查找View、绑定事件的代码,同时对应用性能也基本没有影响。
二、使用
2.1 添加依赖
- 在Project的build.gradle中添加如下代码
buildscript {
repositories {
...
}
dependencies {
...
classpath 'com.jakewharton:butterknife-gradle-plugin:10.1.0' //添加这一行代码
}
}
- 在App的build.gradle中添加如下代码
//ButterKnife
implementation 'com.jakewharton:butterknife:10.1.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
2.2 绑定和注解
- 在Activity中使用ButterKnife
使用ButterKnife.bind(this)进行绑定,注意必需在setContentView方法之后调用。使用@BindView(@ResId)绑定View控件,使用@OnClick(@ResId)绑定点击事件。
public class ButterKnifeActivity extends AppCompatActivity {
@BindView(R.id.text_view01)
TextView mTextView01;
@BindView(R.id.button01)
Button mButton01;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_butter_knife);
ButterKnife.bind(this);
}
@OnClick(R.id.button01)
public void onViewClicked() {
}
}
- 在Fragment中使用ButterKnife
使用ButterKnife.bind(this, view)在onCreateView中进行绑定,并且需要在onDestroyView中进行解绑,绑定View控件和事件的方式同Activity。
public class ButterKnifeFragment extends Fragment {
@BindView(R.id.text_view01)
TextView mTextView01;
@BindView(R.id.button01)
Button mButton01;
private Unbinder mUnbinder;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.activity_butter_knife,container,false);
mUnbinder = ButterKnife.bind(this,view);
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
mUnbinder.unbind();
}
@OnClick(R.id.button01)
public void onViewClicked() {
}
}
- 在Adapter中使用ButterKnife
在adapter定义内部类ViewHolder,并在ViewHolder中绑定ButterKnife。
public class ViewHolder {
@BindView(R.id.text_view01)
TextView mTextView01;
public ViewHolder(View view) {
ButterKnife.bind(this, view);
}
}
- View、资源、事件绑定
/************* 绑定View *************/
//单个View绑定
@BindView(R.id.text_view)
public TextView mTextView;
//多个View绑定
@BindViews({R.id.text_view01,R.id.text_view02})
public TextView mTextView01,mTextView02;
@BindViews({ R.id.button1, R.id.button2, R.id.button3})
public List<Button> buttonList ;
/************* 绑定资源 *************/
//String字符串
@BindString(R.string.app_name)
String app_name;
//string里面array数组
@BindArray(R.array.name)
String [] names ;
//mipmap里面的Bitmap资源
@BindBitmap(R.mipmap.ic_launcher)
Bitmap mBitmap;
//具体色值在color文件中
@BindColor( R.color.colorAccent )
int black ;
/************* 绑定事件 *************/
//onClick事件
@OnClick(R.id.button01)
public void onClick(View view){
}
//多个onClick事件绑定
@OnClick({R.id.button01,R.id.button02})
public void onViewsClick(View view){
}
//onLongClick事件
@OnLongClick(R.id.button01)
public void onLongClick(View view){
}
以上是常用的绑定方式,还有一些其他不常用的View、资源、事件绑定这里就不一一例举了。
三、分析
ButterKnife使用起来很方便,那么源码怎么样呢?下面来分析分析:
ButterKnife.bind方法是一系列的重载方法,根据不同的场景调用不同的bind,最终都调用统一方法。
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
...
@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);
}
...
@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();
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);
}
}
我们直接分析最终的bind方法就行,该方法里首先获取目标类,接着通过findBindingConstructorForClass方法获取一个Constructor,最终调用Constructor.newInstance()返回继承Unbinder泛型。
findBindingConstructorForClass方法如下:
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
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;
}
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")
|| clsName.startsWith("androidx.")) {
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;
}
先在缓存中查找是否存在对应模版类,如果不存在则通过反射加载新的模版类(模版类命名方式为:类名+_viewBinding),然后获取它的构造方法,最后存入缓存中并返回。
通过上面分析,我们知道每一个bind了ButterKnife都会生成一个对应的模版类,那么这个类在什么地方呢?当编译文件时,对应模版类就生成在app>build>generated>source>apt中。
我们看下生成的模版类:
public class ButterKnifeActivity_ViewBinding implements Unbinder {
private ButterKnifeActivity target;
private View view7f070022;
@UiThread
public ButterKnifeActivity_ViewBinding(ButterKnifeActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public ButterKnifeActivity_ViewBinding(final ButterKnifeActivity target, View source) {
this.target = target;
View view;
target.mTextView01 = Utils.findRequiredViewAsType(source, R.id.text_view01, "field 'mTextView01'", TextView.class);
view = Utils.findRequiredView(source, R.id.button01, "field 'mButton01' and method 'onViewClicked'");
target.mButton01 = Utils.castView(view, R.id.button01, "field 'mButton01'", Button.class);
view7f070022 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.onViewClicked();
}
});
}
@Override
@CallSuper
public void unbind() {
ButterKnifeActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.mTextView01 = null;
target.mButton01 = null;
view7f070022.setOnClickListener(null);
view7f070022 = null;
}
}
在构造函数中目标类中可以看到View是通过findRequiredViewAsType获取的,在findRequiredViewAsType中又是调用了findRequiredView方法。
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) {
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 。而绑定事件则是直接在构造函数中调用findRequiredView方法获取View,然后直接在构造函数中绑定对应事件并回调目标类中对应的注解方法。
@UiThread
public ButterKnifeActivity_ViewBinding(final ButterKnifeActivity target, View source) {
this.target = target;
View view;
view = Utils.findRequiredView(source, R.id.button01, "field 'mButton01' and method 'onViewClicked'");
target.mButton01 = Utils.castView(view, R.id.button01, "field 'mButton01'", Button.class);
view7f070022 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.onViewClicked();
}
});
}