源碼剖析——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.lxweimin.com/p/95d4f0eb6027