簡述
在Android開發(fā)中伟骨,很多時候可能都要寫一些模板代碼,比方說最常見的findViewById栈戳,而ButterKnife就是用于降低手寫的工作量的华糖。
通過編譯期的注解方式,生成新的文件瓢棒,然后在使用的時候從該文件中自動調(diào)用findViewById來實現(xiàn)手動的效果浴韭。
這樣的做法有好處當然也有壞處,壞處可能就是會增加方法數(shù)脯宿、使用的時候用到了反射構(gòu)建預先寫好的類念颈、降低編譯的速度,好處簡單說就是降低手動的工作量连霉,至于要不要使用榴芳,這個就看情況定了。
下面講一下ButterKnife的基本實現(xiàn)流程跺撼,因為篇幅有限窟感,只講findViewByID類型
BindView
用于綁定view的id的注解
@Retention(CLASS)//編譯期注解,不會保留到運行時
@Target(FIELD)//作用對象為參數(shù)
public @interface BindView {
//當前綁定對象的id
@IdRes int value();
}
這個其實就是說明了通過編譯期的注解歉井,預先定義好了一個視圖對象和id的關聯(lián)關系
@BindView(R.id.tv_text)
TextView tvText;
這個實際上就是說明tvText的id是R.id.tv_text肌括,而R.id.tv_text實際上就是一個int值,這個可以在R文件中看到
編譯期處理
因為代碼有點多,這里不細講代碼谍夭,主要講述一下流程:
1.通過實現(xiàn)AbstractProcessor來進行編譯期對于注解的處理
2.獲得當前ButterKnife所有支持的注解元素
3.檢查注解的作用域是否合理,通過該注解獲得當前參數(shù)憨募、注解的id等屬性
4.將每一個滿足條件的注解對象放入集合當中
5.為每一個有ButterKnife注解的類都重新寫一個java文件紧索,其中里面通過之前收集的集合中的數(shù)據(jù)進行寫入,主要是默認寫入class.view(注解的參數(shù)對象) = targetView.findViewById(注解的id值)菜谣,那么在后期進行bind的時候調(diào)用該新寫的類即可實現(xiàn)自動的效果
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");
這里可以看到實際上生成的輔助類就是在當前有ButterKnife注解的類_ViewBinding珠漂,比方說當前為AActivity,那么自動生成的類就是AActivity_ViewBinding
運行時處理
以一個例子說明尾膊,假設當前處理的類為MainActivity媳危,其中有一個
@BindView(R.id.tv_text)
TextView tvText;
在運行時,說明編譯已經(jīng)完成冈敛,這個時候輔助類已經(jīng)生成待笑,接下來要做的就是調(diào)用該輔助類
ButterKnife.bind(this);
通過默認提供的bind方法即可實現(xiàn)這個效果,具體看一下實現(xiàn)細節(jié)
public static Unbinder bind(@NonNull Activity target) {
//獲取當前Activity對應的Window的DecorView
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
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 {
//這里僅僅是調(diào)用構(gòu)造函數(shù)生成新的Unbinder對象
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);
}
}
可以看到這里是通過反射的方法創(chuàng)建了一個對象抓谴,當然到這里可以猜到其實就是反射構(gòu)建以前編譯期生成的輔助類暮蹂。
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
//嘗試從緩存中獲取對應的構(gòu)造函數(shù),這里只有解析過某一個cls的情況下才有值
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
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.")) {//過濾Java和Android的內(nèi)部類
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
try {
//通過ClassLoader讀取名稱為MainActivity_ViewBinding的類(一個例子)
//其中這個文件是在編譯期間的ButterKnifeProcessor中生成的
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//noinspection unchecked
//獲取指定的構(gòu)造函數(shù)癌压,其中有兩個Class參數(shù)(target,view)
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);
}
//放入緩存當中,再次拉起當前頁面的時候滩届,可以直接從緩存中獲取構(gòu)造函數(shù)集侯,從而節(jié)省反射的開支
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
這里有一些優(yōu)化手段先不提,主要是看到通過ClassLoader來加載clsName + "_ViewBinding"的類帜消,這里其實就是MainActivity_ViewBinding棠枉,這個就是編譯期生成的類。
然后最終在MainActivity_ViewBinding(Activity a, View v)這個方法中完成了預定義的一系列findViewById操作
插件
如果說已經(jīng)決定使用ButterKnife的話券犁,那么添加頁面化插件的話在使用上就更加方便术健,它可以自動生成注解、id和view的代碼
然后在對應的Activity代碼中粘衬,alt+insert(我用的快捷鍵荞估,這個其實就是AS導航欄上面Code里面的Generate)中打開選項,里面就有ButterKnife的選項稚新,后面就簡單了勘伺。
總結(jié)
從使用的層面來說,ButterKnife確實比較方便褂删,特別是有的頁面view特別多的情況下飞醉,不過從代碼的角度上面來說,如果更加考慮編譯的速度和方法數(shù)的話,也許自己封裝findViewByID是一種更加合適的方案缅帘,這個就根據(jù)自己的需求來決定轴术。