最新版本使用方法
首先需要配置的环境,包括依赖的插件和版本库
配置环境
根目录builld.gradle
buildscript { repositories { mavenCentral() google() } dependencies { classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3' } }
app.gradle中添加依赖
android {
...
// Butterknife requires Java 8.
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}dependencies {
implementation 'com.jakewharton:butterknife:10.2.3'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'
}
modual中的配置
apply plugin: 'com.android.library'
apply plugin: 'com.jakewharton.butterknife'
简单使用
public class MainActivity extends AppCompatActivity {
@BindView(R.id.textVew)
TextView username;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
username.setText("hi nick to meet you");
}
}
源码目录
源码包括了butterknife、butterknife-annotations、butterknife-compiler、butterknife-gradle-plugin、butterknife-lint、butterknife-reflect、butterknife-runtime这几个模块
butterknife模块提供了ButterKnife类,作为应用层的唯一封装,通过bind方法实现了View绑定功能(不仅仅是绑定功能,还有更多其他)
butterknife-annotaions提供了绑定view的注解,比如@BindView、@BindClick等
butterknife-compiler动态生成一些代码,比如MainActivity中使用了Butterknife,就会生成一个MainActivity_ViewBinding的类,ButterKnife类通过MainActivity_ViewBinding实现了View的绑定
butterknife-gradle-plugin 生成一个gradle插件,实现R文件拷贝成R2。不过最新版本里面已经不是R2了,是另一个包名的R文件。
源码分析
前面,我们提到,通过@BindView可以关联Id和View(当然还有更多其他的用途),接下来,我们以BindView注解为例,分析如果将View绑定到对应的Id。
假如,我们在MainActivity中使用了ButterKnife,该框架的注解处理器会帮我们自动生成一个名为MainActivity_ViewBinding的类。MainActivity_ViewBinding类最终帮我们关联Id和View,或者设定View的点击事件等等。对于MainActivity_ViewBinding这个类,我们先有个概念,知道他是干嘛的就可以了,后面再来具体分析MainActivity_ViewBinding的机制。
接下来,我们以ButterKnife类的bind方法为入口,分析ButterKnife的总体流程。
ButterKnife.bind(this);
/**
* ButterKnife.java
*/
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
ButterKnife.bind方法调用了另一个bind(Object target, View source)方法,方法如下:
ButterKnife.bind(Object target, View source)
bind(Object target, View source)返回了MainActivity_ViewBinding对象。首先,通过findBindingConstructorForClass方法,返回了MainActivity_ViewBinding的构造方法。然后通过newIntance构造来MainActivity_ViewBinding对象。
/**
* ButterKnife.java
*/
// target = activity, source = decorView
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
// MainActivity.class
Class<?> targetClass = target.getClass();
/**
* 这里说多两句,假如我们在MainActivity中,使用了ButterKnife,ButterKnife的注解处理器会自
动生成一个MainActivity_ViewBinding.java 文件。通过MainActivity_ViewBinding这个类实现
TextView等View绑定到对应Id、实现View点击事件等等。
findBindingConstructorForClass就是通过反射拿到MainActivity_ViewBinding这个类的构造方
法
*/
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
// 上面的MainActivity_ViewBinding实现来UnBind接口,这里通过构造方法实现
// MainActivity_ViewBinding.
return constructor.newInstance(target, source);
}
......
}
findBindingConstructorForClass(Class<?> cls)
findBindingConstructorForClass方法缓存并返回了MainActivity_ViewBinding类的构造方法。
@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;
}
// 反射构造 MainActivity_ViewBinding这种注解处理器生成的类
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;
}
MainActivity_ViewBinding.java
接下来,我们继续分析,注解处理器自动生成的MainActivity_ViewBinding类是如何关联View和Id的。第二个构造函数,调用Utils.findRequiredViewAsType方法,实现View和资源Id对应视图的绑定。根据前面的入参,我们知道target和source,userName都是什么了。
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(MainActivity target, View source) {
this.target = target;
target.username = Utils.findRequiredViewAsType(source, R.id.textVew, "field 'username'", TextView.class);
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.username = null;
}
}
// target = activity // source = decorView // userName = TextView
target.username = Utils.findRequiredViewAsType(source, R.id.textVew, "field 'username'", TextView.class);
findRequiredViewAsType(View source, @IdRes int id, String who,Class<T> cls)
findRequiredViewAsType调用了findRequiedView方法,并传入来decvor,@BindView中的资源id(上面demo是R.id.textView)
/**
* butterknife-runtie:10.2.3.aar
* Utils.java
*/
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(View source, @IdRes int id, String who)
调用source.findViewById = decorView.findViewById(R.id.textView), 对应demo中,返回TextView对象
/**
* View = DecorView
* id = R.id.textView (上面demo中TextView的资源Id)
*/
public static View findRequiredView(View source, @IdRes int id, String who) {
// 有没有很熟悉啊!
View view = source.findViewById(id);
if (view != null) {
return view;
}
......
}
至此,我们撸完来ButterKnife实现findViewById的全过程。总结一下,通过ButterKnife.bind方法,构造了MainActivity_ViewBinding类。MainActivity_ViewBinding类的构造方法通过调用Utils.findRequredView实现来findViewById。