如何使用
添加依賴
implementation "com.jakewharton:butterknife:$butterKnifeVersion"
annotationProcessor "com.jakewharton:butterknife-compiler:$butterKnifeVersion"
在Activity中使用
聲明Unbinder對象為局部變量
private Unbinder mUnbinder;
在Activity的onCreate生命周期中初始化mUnbinder
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
......
setContentView(layoutResId);
......
mUnbinder=ButterKnife.bind(this);
......
}
用@BindView注解綁定view_id給你對應(yīng)的view
@BindView(R.id.tv_date)
TextView tvDate;
當(dāng)你view較多的時候需要你多次編寫類似的代碼,比較耗時,此時可以使用Android ButterKnife Zelezny插件。
如何添加插件?
添加插件
點擊File打開菜單

點擊
Settings...打開設(shè)置頁面
點擊
Plugins打開插件設(shè)置頁面,選擇Marketplace標(biāo)簽頁
在搜索欄中輸入
ButterKnife后,按回車確認(rèn),點擊第一個插件下的INSTALL安裝,安裝完成后重啟AndroidStudio
使用插件
重啟完成后打開你需要使用@BindView的Activity頁面,在布局文件的id上單擊右鍵,然后選擇Generate...菜單

在彈出的菜單中選擇
Generate Butterknife Injections
在之后的菜單中,勾選你需要在
Activity中創(chuàng)建的view,然后點擊CONFIRM,就會自動生成對應(yīng)的@BindView代碼
除了這些代碼,還會額外在
onDestory方法中生成mUnbinder的解綁代碼,是我們使用ButterKnife必要的代碼
if (mUnbinder != null) {
mUnbinder.unbind();
}
以上就是在Activity中使用時的簡單步驟
學(xué)習(xí)源碼
查看學(xué)習(xí)Unbinder對象源碼
import android.support.annotation.UiThread;
/** An unbinder contract that will unbind views when called. */
public interface Unbinder {
@UiThread void unbind();
Unbinder EMPTY = new Unbinder() {
@Override public void unbind() { }
};
}
其中包含了一個在UiThread中執(zhí)行的unbind()方法,以及一個初始化好的EMPTY的Unbinder實例。
接下來查看學(xué)習(xí)ButterKnife.bind(this)的bind方法。
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
這段代碼獲取了Activity的頂層視圖,并作為參數(shù)傳入了createBinding方法中,我們繼續(xù)查看該方法
private static Unbinder createBinding(@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);
}
}
這個方法再第四行代碼中通過findBindingConstructorForClass(targetClass)方法獲取到一個Constructor<? extends Unbinder>實例,余下的都是一些異常處理,那么我們就需要繼續(xù)深入findBindingConstructorForClass(targetClass)一探究竟。
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
//BINDINGS
if (bindingCtor != null) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
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;
}
這里是實例化BINDINGS的代碼,它是一個LinkedHashMap,用來緩存已經(jīng)匹配到過的bindingCtor以節(jié)省開銷。
可以看到上面的代碼中倒數(shù)第二行,將匹配到的bindingCtor放入了BINDINGS中。
@VisibleForTesting
static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
那么對我們來說有意義的代碼就是try catch代碼塊中的內(nèi)容了
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//noinspection unchecked
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
clsName是你傳進來的Activity的名字,以我傳入的為例與后面的拼接之后就是MainActivity_ViewBinding。我們?nèi)炙阉饕幌逻@個類名。

這是我們編譯代碼之后生成的輔助文件。那么
findBindingConstructorForClass這個方法返回的就是通過反射得到的MainActivity_ViewBinding的構(gòu)造方法。然后在createBinding方法中使用constructor.newInstance(target, source)得到了MainActivity_viewBinding的實例。至此,我們已經(jīng)了解了
ButterKnife.bind(this)這個方法所做的工作。
接下來我們仔細查看這個生成的類幫我們做了什么。
target.vStatusBg = Utils.findRequiredView(source, R.id.v_status_bg, "field 'vStatusBg'");
target.tvDate = Utils.findRequiredViewAsType(source, R.id.tv_date, "field 'tvDate'", TextView.class);
target.tvMenuBuyCarService = Utils.castView(view, R.id.tv_menu_buy_car_service, "field 'tvMenuBuyCarService'", TextView.class);
我們查看MainActivity_ViewBinding類源碼之后,看到,給對應(yīng)的view賦值的方法有這三個。接下來我們繼續(xù)查看這三個方法。
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.");
}
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 <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);
}
}
我們可以看到最終還是通過findViewById給view賦值
到這里我們將簡單的BindView的流程走完了,我們發(fā)現(xiàn)最重要的步驟其實應(yīng)該是MainActivity_ViewBinding這個類的生成,它代替我們做了一系列findViewById的工作。
那我們會有一個疑問MainActivity_ViewBinding這個類是怎么生成的呢?
我們打開這個文件查看路徑

當(dāng)看到
apt時,我們搜一下apt是什么:APT(Annotation Processing Tool)即注解處理器,是一種處理注解的工具,確切的說它是javac的一個工具,它用來在編譯時掃描和處理注解。注解處理器以Java代碼(或者編譯過的字節(jié)碼)作為輸入,生成.java文件作為輸出。簡單來說就是在編譯期,通過注解生成
.java文件。
我們在添加ButterKnife依賴的時候還添加了這樣一行代碼annotationProcessor "com.jakewharton:butterknife-compiler:$rootProject.butterKnifeVersion"
接下來我們通過github下載得到butterknife的源碼。

我們看到了我們所引用的butterknife-compiler項目,我們在下一篇來一探究竟。