ButterKnife框架原理

大部分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
  • 然後調用ViewBinderbind方法,動態注入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是LinearLayoutFrameLayout等系統自帶View,那麼不是不能用ButterKnife.bind(View)來注入View的,因爲ButterKnife認爲這些類的包名以com.android開頭的類是沒有註解功能的(-。- 這不是廢話嗎?),所以這種情況你需要使用ButterKnife.bind(ViewHolder,View)來注入View。

這表示你是把@Bind@OnClick等註解寫到了這個ViewHolder類中,ViewHolder中的View呢需要從後面那個View中去找, 大概就是這麼個意思

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章