ButterKnife源碼解析


使用Butterknife的主要目的是消除關(guān)于view實例化的樣板代碼,這是一個專為View類型的依賴注入框架。Dagger2是一個更加通用的依賴注入框架。

ButterKnife的使用非常簡單,其工作原理是先對添加的注解域@Bind做編譯時解析,生成一個中間類,這個中間類具有將經(jīng)過注解的域?qū)嵗哪芰?/strong>,在運行時使用中間類完成真正的實例化。

1.ButterKnife的使用

1.注入View

在碎片中使用

@Override 
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fancy_fragment, container, false);
    ButterKnife.bind(this, view);
    // TODO Use fields...
    return view;
}

在適配器中使用

static class ViewHolder {
    @Bind(R.id.title) TextView name;
    @Bind(R.id.job_title) TextView jobTitle;

    public ViewHolder(View view) {
      ButterKnife.bind(this, view);
    }
}

2.監(jiān)聽器注入

@OnClick(R.id.submit)
public void sayHi(Button button) {
  button.setText("Hello!");
}

多個同類型的控件注冊一個監(jiān)聽器

@OnClick({ R.id.door1, R.id.door2, R.id.door3 })
public void pickDoor(DoorView door) {
  if (door.hasPrizeBehind()) {
    Toast.makeText(this, "You win!", LENGTH_SHORT).show();
  } else {
    Toast.makeText(this, "Try again", LENGTH_SHORT).show();
  }
}

3.為控件列表注入動作

// 1.注入控件列表
@BindViews({ R.id.first_name, R.id.middle_name, R.id.last_name })
List<EditText> nameViews;

//2.綁定控件列表與動作
ButterKnife.apply(nameViews, DISABLE);
ButterKnife.apply(nameViews, ENABLED, false);

//3.定義動作
static final ButterKnife.Action<View> DISABLE = new ButterKnife.Action<View>() {
  @Override public void apply(View view, int index) {
    view.setEnabled(false);
  }
};

static final ButterKnife.Setter<View, Boolean> ENABLED = new ButterKnife.Setter<View, Boolean>() {
  @Override public void set(View view, Boolean value, int index) {
    view.setEnabled(value);
  }
};

4.單個控件的類型查詢和轉(zhuǎn)型

TextView firstName = ButterKnife.findById(view, R.id.first_name);
ImageView photo = ButterKnife.findById(view, R.id.photo);

5.NavigationView的注入
默認情況下,注入 NavigationView 實例并不意味著其頭視圖的中的元素也跟隨著被注入,例如下例中的 TextView 雖然經(jīng)過注解,但實際上并沒有被實例化。

@Bind(R.id.navigation_view )
View navigationView = navigationView.getHeaderView(0);

@Bind(R.id.title_text_view) TextView titleTv;

必須對頭視圖單獨進行注入

View headerView = navigationView.getHeaderView(0);
TextView textView = ButterKnife.findById(headerView, R.id.tvName);

2.編譯時注解解析

public class MainActivity extends AppCompatActivity{

    @Bind(R.id.relative_layout)
    RelativeLayout mRelativeLayout;
    
    @BindString(R.string.app_name)
    String name;
}

編譯時Java會使用類ButterKnifeProcessor進行編譯時注解處理,為每一個注解過的類生成一個中間類,如MainActivity類將生成中間類MainActivity$$ViewBinder;運行時將執(zhí)行該中間類完成注入。ButterKnifeProcessor處理代碼如下

boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //1.首先找出使用的所有包含注解的類集合Map<TypeElement, BindingClass>,其key是注解類,如MainActivity,其值為相應生成的BindingClass類,包含注解集合。
    Map targetClassMap = this.findAndParseTargets(env);
    
    //2.依次為每一個注解類創(chuàng)建中間類
    Iterator var4 = targetClassMap.entrySet().iterator();
    while(var4.hasNext()) {
        Entry entry = (Entry)var4.next();
        TypeElement typeElement = (TypeElement)entry.getKey();
        BindingClass bindingClass = (BindingClass)entry.getValue();
        //3.使用BindingClass類創(chuàng)建中間類 
        JavaFileObject e = this.filer.createSourceFile(bindingClass.getFqcn(), new Element[]{typeElement});
        Writer writer = e.openWriter();
        writer.write(bindingClass.brewJava());
        writer.flush();
        writer.close();
    }

    return true;
}

上述編譯時注解解析分為兩步,首先搜索所有經(jīng)過注解要處理的類集合,其key是注解類,如 MainActivity,其值為相應生成的 BindingClass 類;而后遍歷集合使用 BindingClass 類提供的信息生成對應的中間類,如 MainActivity$$ViewBinder。

1.BindingClass 類與使用注解的類一一對應,它包含了該類的注解信息,如通過資源機制獲取到控件的 id 信息,是生成中間類的信息來源。定義如下

final class BindingClass {
    //1.被注解的View信息集合
    private final Map<Integer, ViewBindings> viewIdMap; 
    //2.被注解的View集合信息集合
    private final Map<FieldCollectionViewBinding, int[]> collectionBindings; 
    //3.被注解的資源信息集合
    private final List<FieldResourceBinding> resourceBindings;
    //4.包名
    private final String classPackage;
    //5.類名
    private final String className;
    private final String targetClass;
    private String parentViewBinder;
}

其中字典集合Map<Integer, ViewBindings>key值表示控件的資源 id,而ViewBindings類表示對控件域的操作。

2.創(chuàng)建中間類
中間類的創(chuàng)建是使用Java流來寫入的,其內(nèi)容由bindingClass.brewJava()方法提供,可以處理原類中的注解,生成中間類如下

public class MainActivity$$ViewBinder<T extends MainActivity> implements ViewBinder<T> {

  @Override 
  public void bind(final Finder finder, final T target, Object source) {
    View view;
    view = finder.findRequiredView(source, 2131492971, "field 'mRelativeLayout'");
    target.mRelativeLayout = finder.castView(view, 2131492971, "field 'mRelativeLayout'");
    
    Resources res = finder.getContext(source).getResources();
    target.name = res.getString(2131099669);
  }

  @Override 
  public void unbind(T target) {
    target.mRelativeLayout = null;
  }
}

調(diào)用中間類中的bind方法就完成了資源的實例化。以string為例 2131099669 即根據(jù)@BindString(R.string.app_name)注解得到的資源 id。

3.對象實例化

中間類雖然具備了實例化注解對象的能力,但在編譯時并沒有得到執(zhí)行,必須在運行時進行注解對象實例化。

ButterKnife.bind(this);

該方法的實際執(zhí)行代碼如下

@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
    return getViewBinder(target).bind(Finder.ACTIVITY, target, target);
}

1.首先根據(jù)活動類MainActivity尋找其所生成的中間類MainActivity$$ViewBinder,并生成對象。

@NonNull @CheckResult @UiThread
static ViewBinder<Object> getViewBinder(@NonNull Object target){
    Class<?> targetClass = target.getClass();
    return findViewBinderForClass(targetClass);
}

這里 targetClass 就是活動 MainActivity,因為中間類的命名是固定的,即本類加$$ViewBinder后綴,故可以直接使用類名來查找中間類 MainActivity$$ViewBinder ,而后使用反射創(chuàng)建中間類實例。

2.執(zhí)行中間類對象的bind方法對所注解對象進行實例化。該類繼承于接口ViewBinder,只有一個bind方法。

public interface ViewBinder<T> {
  Unbinder bind(Finder finder, T target, Object source);
}

其中Finder是一個枚舉類(VIEW,ACTIVITY,DIALOG),之所以要將它們區(qū)別開來,是因為它們查找資源的方式有所差別,對于不同的類型有不同的實現(xiàn)。

執(zhí)行注解對象實例化的代碼最終委托給Finder類,包括綁定View與向下轉(zhuǎn)型兩步,例如對于 ACTIVITY 是這樣實現(xiàn)的。

public class MainActivity$$ViewBinder<T extends MainActivity> implements ViewBinder<T> {

    public void bind(Finder finder, T target, Object source) {
        View view = (View)finder.findRequiredView(source, 2131492944, "field \'mTextView\'");
        target.mTextView = (TextView)finder.castView(view, 2131492944, "field \'mTextView\'");
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,323評論 25 708
  • 本文主要介紹Android之神JakeWharton的一個注解框架,聽說現(xiàn)在面試官現(xiàn)在面試都會問知不知道JakeW...
    Zeit丶閱讀 1,074評論 4 6
  • 參考1參考2參考3參考4 一:基本原理 編譯時注解+APT編譯時注解:Rentention為CLASS的直接。保留...
    shuixingge閱讀 651評論 0 3
  • 11月20日我讀了十頁從68頁到78頁 好詞:栩栩如生、 鑲嵌、不屈不撓、澎湃、剽竊、期期艾艾、滾落、南北戰(zhàn)爭、粘...
    靜如子鈺閱讀 457評論 0 0
  • Don’t stand before me,or behind me.Just stand by me. 文|熊...
    熊恩閱讀 592評論 0 0

友情鏈接更多精彩內(nèi)容