源碼剖析——ButterKnife的工作流程
butterknife是一個android視圖快速注入庫,它通過給view字段添加java注解捏题,可以讓我們丟掉findViewById()來獲取view的方法,從而簡化了代碼罩旋。本文是基于7.0.1版本的剖析菱阵。
首先來看下注解方式:
1、標(biāo)準(zhǔn)Annotation
標(biāo)準(zhǔn)的Annotation咪橙,我們經(jīng)常用的@Override、@Deprecated虚倒、@SuppressWarnings美侦,這些是java自帶的幾個Annotation,分別表示重寫函數(shù)魂奥、不鼓勵使用菠剩、忽略某項(xiàng)Warning。
2耻煤、元Annotation
元Annotation是指用來定義Annotation的Annotation具壮,一般我們自定義Annotation時就會用到准颓。主要包括以下幾個:
- @Documented是否會保存到Javadoc文檔中
- @Retention保留時間,可選值SOURCE(源碼時)棺妓,CLASS(編譯時)攘已,RUNTIME(運(yùn)行時),默認(rèn)為CLASS怜跑,值為SOURCE大都為MarkAnnotation样勃,這類Annotation大都用來校驗(yàn),比如Override,Deprecated,SuppressWarnings
- @Target可以用來修飾哪些程序元素妆艘,如TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER等彤灶,未標(biāo)注則表示可修飾所有
- @Inherited是否可以被繼承,默認(rèn)為false
OK,我們來看看butterknife的@Bind注解,@Retention是編譯時旱幼,@Target是字段倘要。
@Retention(CLASS) @Target(FIELD)
public @interface Bind {
/** View ID to which the field will be bound. */
int[] value();
}
編譯時 Annotation 指 @Retention 為 CLASS 的 Annotation,由apt(Annotation Processing Tool) 解析自動解析。ButterKnife便是用了Java Annotation Processing技術(shù),就是在Java代碼編譯成Java字節(jié)碼的時候就已經(jīng)處理了@Bind、@OnClick(ButterKnife還支持很多其他的注解)這些注解了心例。
你可以你定義注解,并且自己定義解析器來處理它們鞋囊。Annotation processing是在編譯階段執(zhí)行的止后,它的原理就是讀入Java源代碼,解析注解溜腐,然后生成新的Java代碼译株。新生成的Java代碼最后被編譯成Java字節(jié)碼,注解解析器(Annotation Processor)不能改變讀入的Java 類挺益,比如不能加入或刪除Java方法歉糜。
ButterKnife 工作流程
當(dāng)你編譯你的Android工程時,ButterKnife工程中ButterKnifeProcessor類的process()方法會執(zhí)行以下操作:
- 開始它會掃描Java代碼中所有的ButterKnife注解@Bind望众、@OnClick匪补、@OnItemClicked等。
- 當(dāng)它發(fā)現(xiàn)一個類中含有任何一個注解時烂翰,ButterKnifeProcessor會幫你生成一個Java類夯缺,名字類似$$ViewBinder,這個新生成的類實(shí)現(xiàn)了ViewBinder接口甘耿。
- 這個ViewBinder類中包含了所有對應(yīng)的代碼踊兜,比如@Bind注解對應(yīng)findViewById(), @OnClick對應(yīng)了view.setOnClickListener()等等。
- 最后當(dāng)Activity啟動ButterKnife.bind(this)執(zhí)行時棵里,ButterKnife會去加載對應(yīng)的ViewBinder類調(diào)用它們的bind()方法润文。
來看個使用butterknife的例子:
@Bind(R.id.button)Button mButton;
@Override
protected voidon Create(Bundlesaved InstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@OnClick(R.id.button) void clickButton() {
Toast.makeText(MainActivity.this,“HelloWorld!”,Toast.LENGTH_SHORT).show();
}
onCreate()方法中調(diào)用了ButterKnife.bind(this)我們點(diǎn)進(jìn)去看看:
public static void bind(Activity target) {
bind(target, target, Finder.ACTIVITY);
}
調(diào)用了bind方法,參數(shù)分別為activity和Finder殿怜,繼續(xù)點(diǎn)進(jìn)去:
static void bind(Object target, Object source, Finder finder) {
Class<?> targetClass = target.getClass();
try {
if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
//查找ViewBinder類
ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
if (viewBinder != null) {
viewBinder.bind(finder, target, source);
}
} catch (Exception e) {
throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
}
}
第5行findViewBinderForClass應(yīng)該是去查找ViewBinder類典蝌,點(diǎn)進(jìn)去:
private static ViewBinder<Object> findViewBinderForClass(Class<?> cls)
throws IllegalAccessException, InstantiationException {
//從內(nèi)存中查找
ViewBinder<Object> viewBinder = BINDERS.get(cls);
if (viewBinder != null) {
if (debug) Log.d(TAG, "HIT: Cached in view binder map.");
return viewBinder;
}
String clsName = cls.getName();
//檢查是否為framework class
if (clsName.startsWith(ANDROID_PREFIX) || clsName.startsWith(JAVA_PREFIX)) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return NOP_VIEW_BINDER;
}
try {
//實(shí)例化“MainActivity$$ViewBinder”這樣的類
Class<?> viewBindingClass = Class.forName(clsName + ButterKnifeProcessor.SUFFIX);
//noinspection unchecked
viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();
if (debug) Log.d(TAG, "HIT: Loaded view binder class.");
} catch (ClassNotFoundException e) {
if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
//異常,則去父類查找
viewBinder = findViewBinderForClass(cls.getSuperclass());
}
//放入內(nèi)存并返回
BINDERS.put(cls, viewBinder);
return viewBinder;
}
第3行是首先從內(nèi)存中查找头谜,看BINDERS的定義:
static final Map<Class<?>, ViewBinder<Object>> BINDERS = new LinkedHashMap<Class<?>, ViewBinder<Object>>();
內(nèi)存中沒有骏掀,第9行判斷如果framework class,就放棄查找并返回ViewBinder的空實(shí)現(xiàn)實(shí)例柱告。
public static final String ANDROID_PREFIX = "android.";
public static final String JAVA_PREFIX = "java.";
static final ViewBinder<Object> NOP_VIEW_BINDER = new ViewBinder<Object>() {
@Override public void bind(Finder finder, Object target, Object source) { }
@Override public void unbind(Object target) { }
};
第14行是去實(shí)例化“MainActivity$$ViewBinder”這樣的類截驮,如果viewBinder不為空,放入緩存并返回际度;如果ClassNotFoundException異常則去父類查找葵袭。
public static final String SUFFIX = "$$ViewBinder";
我們回到上面的bind()方法,查找到的ViewBinder不為空乖菱,則執(zhí)行viewBinder.bind(finder,target,source)方法坡锡。
當(dāng)我們編譯運(yùn)行后,在build文件夾下找到MainActivity$$ViewBinder.java類窒所,具體代碼為:
public class MainActivity$$ViewBinder<T extends com.spirittalk.rxjavatraining.MainActivity> implements ViewBinder<T> {
@Override public void bind(final Finder finder, final T target, Object source) {
View view;
view = finder.findRequiredView(source, 2131492970, "field 'mButton' and method 'clickButton'");
target.mButton = finder.castView(view, 2131492970, "field 'mButton'");
view.setOnClickListener(
new butterknife.internal.DebouncingOnClickListener() {
@Override public void doClick(android.view.View p0) {
target.clickButton();
}
});
}
@Override public void unbind(T target) {
target.mButton = null;
}
}
最終鹉勒,bind()方法會執(zhí)行到MainActivity$$ViewBinder.java類中的bind()方法。
那么吵取,MainActivity$$ViewBinder.java類是如何生成的呢禽额?這個在下篇 編譯期解析注解、生成java代碼流程 中揭曉皮官。
在上面的過程中可以看到脯倒,為什么你用@Bind、@OnClick等注解標(biāo)注的屬性或方法必須是public或protected的臣疑,因?yàn)锽utterKnife是通過ExampleActivity.this.button來注入View的盔憨。
為什么要這樣呢?有些注入框架比如roboguice你是可以把View設(shè)置成private的讯沈,答案就是性能郁岩。如果你把View設(shè)置成private,那么框架必須通過反射來注入View缺狠,一個很大的缺點(diǎn)就是在Activity運(yùn)行時大量使用反射會影響App的運(yùn)行性能问慎,造成卡頓以及生成很多臨時Java對象更容易觸發(fā)GC,不管現(xiàn)在手機(jī)的CPU處理器變得多快挤茄,如果有些操作會影響性能如叼,那么是肯定要避免的,這就是ButterKnife與其他注入框架的不同穷劈。
轉(zhuǎn)載請標(biāo)明出處:http://www.reibang.com/p/95d4f0eb6027