如何使用
添加依賴
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
給你對應的view
@BindView(R.id.tv_date)
TextView tvDate;
當你view
較多的時候需要你多次編寫類似的代碼,比較耗時,此時可以使用Android ButterKnife Zelezny
插件。
如何添加插件?
添加插件
點擊File
打開菜單
點擊
Settings...
打開設置頁面點擊
Plugins
打開插件設置頁面,選擇Marketplace
標簽頁在搜索欄中輸入
ButterKnife
后,按回車確認,點擊第一個插件下的INSTALL
安裝,安裝完成后重啟AndroidStudio
使用插件
重啟完成后打開你需要使用@BindView
的Activity
頁面,在布局文件的id
上單擊右鍵,然后選擇Generate...
菜單
在彈出的菜單中選擇
Generate Butterknife Injections
在之后的菜單中,勾選你需要在
Activity
中創建的view
,然后點擊CONFIRM
,就會自動生成對應的@BindView
代碼除了這些代碼,還會額外在
onDestory
方法中生成mUnbinder
的解綁代碼,是我們使用ButterKnife
必要的代碼
if (mUnbinder != null) {
mUnbinder.unbind();
}
以上就是在Activity
中使用時的簡單步驟
學習源碼
查看學習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
中執行的unbind()
方法,以及一個初始化好的EMPTY
的Unbinder
實例。
接下來查看學習ButterKnife.bind(this)
的bind
方法。
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
這段代碼獲取了Activity
的頂層視圖,并作為參數傳入了createBinding
方法中,我們繼續查看該方法
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>
實例,余下的都是一些異常處理,那么我們就需要繼續深入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
,用來緩存已經匹配到過的bindingCtor
以節省開銷。
可以看到上面的代碼中倒數第二行,將匹配到的bindingCtor
放入了BINDINGS
中。
@VisibleForTesting
static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
那么對我們來說有意義的代碼就是try catch
代碼塊中的內容了
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//noinspection unchecked
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
clsName
是你傳進來的Activity
的名字,以我傳入的為例與后面的拼接之后就是MainActivity_ViewBinding
。我們全局搜索一下這個類名。
這是我們編譯代碼之后生成的輔助文件。那么
findBindingConstructorForClass
這個方法返回的就是通過反射得到的MainActivity_ViewBinding
的構造方法。然后在createBinding
方法中使用constructor.newInstance(target, source)
得到了MainActivity_viewBinding
的實例。至此,我們已經了解了
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
類源碼之后,看到,給對應的view
賦值的方法有這三個。接下來我們繼續查看這三個方法。
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
的流程走完了,我們發現最重要的步驟其實應該是MainActivity_ViewBinding
這個類的生成,它代替我們做了一系列findViewById
的工作。
那我們會有一個疑問MainActivity_ViewBinding
這個類是怎么生成的呢?
我們打開這個文件查看路徑
當看到
apt
時,我們搜一下apt
是什么:APT(Annotation Processing Tool)
即注解處理器,是一種處理注解的工具,確切的說它是javac
的一個工具,它用來在編譯時掃描和處理注解。注解處理器以Java
代碼(或者編譯過的字節碼)作為輸入,生成.java
文件作為輸出。簡單來說就是在編譯期,通過注解生成
.java
文件。
我們在添加ButterKnife
依賴的時候還添加了這樣一行代碼annotationProcessor "com.jakewharton:butterknife-compiler:$rootProject.butterKnifeVersion"
接下來我們通過github
下載得到butterknife
的源碼。
我們看到了我們所引用的butterknife-compiler
項目,我們在下一篇來一探究竟。