大部分Android開發應該都知道@JakeWharton 大神的ButterKnife註解庫,使用這個庫我們可以不用寫很多無聊的findViewById()
和setOnClickListener()
等代碼
ButterKnife項目的主頁在這裏:http://jakewharton.github.io/butterknife/ 簡單介紹一下使用方法:
public class ExampleActivity extends Activity {
@Bind(R.id.title) EditText titleView;
@Bind(R.id.subtitle) EditText subtitleView;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.example_activity);
ButterKnife.bind(this);
}
}
但是這個庫是如何工作的呢?可能很多人都覺得ButterKnife在bind(this)
方法執行的時候通過反射獲取ExampleActivity
中所有的帶有@Bind
註解的屬性並且獲得註解中的R.id.xxx
值,最後還是通過反射拿到Activity.findViewById()
方法獲取View,並賦值給ExampleActivity
中的某個屬性
這是一個註解庫的實現方式,比較原始,一個很大的缺點就是在Activity運行時大量使用反射會影響App的運行性能,造成卡頓以及生成很多臨時Java對象更容易觸發GC
ButterKnife
顯然沒有使用這種方式,它用了Java Annotation Processing技術,就是在Java代碼編譯成Java字節碼的時候就已經處理了@Bind
、@OnClick
(ButterKnife還支持很多其他的註解)這些註解了
Java Annotation Processing
Annotation processing 是javac中用於編譯時掃描和解析Java註解的工具
你可以你定義註解,並且自己定義解析器來處理它們。Annotation processing是在編譯階段執行的,它的原理就是讀入Java源代碼,解析註解,然後生成新的Java代碼。新生成的Java代碼最後被編譯成Java字節碼,註解解析器(Annotation Processor)不能改變讀入的Java 類,比如不能加入或刪除Java方法
下圖是Java 編譯代碼的整個過程,可以幫助我們很好理解註解解析的過程:
ButterKnife 工作流程
當你編譯你的Android工程時,ButterKnife工程中ButterKnifeProcessor
類的process()
方法會執行以下操作:
- 開始它會掃描Java代碼中所有的ButterKnife註解
@Bind
、@OnClick
、@OnItemClicked
等 - 當它發現一個類中含有任何一個註解時,
ButterKnifeProcessor
會幫你生成一個Java類,名字類似<className>$$ViewBinder
,這個新生成的類實現了ViewBinder<T>
接口 - 這個
ViewBinder
類中包含了所有對應的代碼,比如@Bind
註解對應findViewById()
,@OnClick
對應了view.setOnClickListener()
等等 - 最後當Activity啓動
ButterKnife.bind(this)
執行時,ButterKnife會去加載對應的ViewBinder
類調用它們的bind()
方法
一個栗子
一段Java代碼:
class ExampleActivity extends Activity {
@Bind(R.id.user) EditText username;
@Bind(R.id.pass) EditText password;
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
// TODO Use fields…
}
@OnClick(R.id.submit) void submit() {
// TODO call server…
}
}
編譯成功後,下面的代碼生成了:
public class ExampleActivity$$ViewBinder<T extends
io.bxbxbai.samples.ui.ExampleActivity> implements ViewBinder<T> {
@Override public void bind(final Finder finder, final T target, Object source) {
View view;
view = finder.findRequiredView(source, 21313618, “field ‘user’”);
target.username = finder.castView(view, 21313618, “field ‘user’”);
view = finder.findRequiredView(source, 21313618, “field ‘pass’”);
target.password = finder.castView(view, 21313618, “field ‘pass’”);
view = finder.findRequiredView(source, 21313618, “field ‘submit’ and method ‘submit’”);
view.setOnClickListener(
new butterknife.internal.DebouncingOnClickListener() {
@Override public void doClick(android.view.View p0) {
target.submit();
}
});
}
@Override public void reset(T target) {
target.username = null;
target.password = null;
}
}
用一張圖來說明一下:
ButterKnife.bind 執行階段
最後,執行bind
方法時,我們會調用ButterKnife.bind(this)
:
- ButterKnife會調用
findViewBinderForClass(targetClass)
加載ExampleActivity$$ViewBinder.java
類 - 然後調用
ViewBinder
的bind
方法,動態注入ExampleActivity
類中所有的View屬性和 - 如果Activity中有
@OnClick
註解的方法,ButterKnife會在ViewBinder
類中給View設置onClickListener,並且將@OnClick
註解的方法傳入其中
在上面的過程中可以看到,爲什麼你用@Bind
、@OnClick
等註解標註的屬性或方法必須是public或protected的,因爲ButterKnife是通過ExampleActivity.this.editText
來注入View的
爲什麼要這樣呢?有些注入框架比如roboguice你是可以把View設置成private的,答案就是性能。如果你把View設置成private,那麼框架必須通過反射來注入View,不管現在手機的CPU處理器變得多快,如果有些操作會影響性能,那麼是肯定要避免的,這就是ButterKnife與其他注入框架的不同
有一點需要注意
通過ButterKnife來注入View時,ButterKnife有bind(Object, View)
和 bind(View)
兩個方法,有什麼區別呢?
如果你自定義了一個View,比如public class BadgeLayout extends Fragment
,那麼你可以可以通過ButterKnife.bind(BadgeLayout)
來注入View的
如果你在一個ViewHolder中inflate了一個xml佈局文件,得到一個View
對象,並且這個View是LinearLayout
或FrameLayout
等系統自帶View,那麼不是不能用ButterKnife.bind(View)
來注入View的,因爲ButterKnife認爲這些類的包名以com.android
開頭的類是沒有註解功能的(-。-
這不是廢話嗎?),所以這種情況你需要使用ButterKnife.bind(ViewHolder,View)
來注入View。
這表示你是把@Bind
、@OnClick
等註解寫到了這個ViewHolder類中,ViewHolder中的View呢需要從後面那個View
中去找,
大概就是這麼個意思