ButterKnife源碼解析


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

ButterKnife的使用非常簡(jiǎn)單帘饶,其工作原理是先對(duì)添加的注解域@Bind做編譯時(shí)解析,生成一個(gè)中間類镀裤,這個(gè)中間類具有將經(jīng)過(guò)注解的域?qū)嵗哪芰?/strong>缴饭,在運(yùn)行時(shí)使用中間類完成真正的實(shí)例化

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)聽(tīng)器注入

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

多個(gè)同類型的控件注冊(cè)一個(gè)監(jiān)聽(tīng)器

@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.為控件列表注入動(dòng)作

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

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

//3.定義動(dòng)作
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.單個(gè)控件的類型查詢和轉(zhuǎn)型

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

5.NavigationView的注入
默認(rèn)情況下担猛,注入 NavigationView 實(shí)例并不意味著其頭視圖的中的元素也跟隨著被注入丢氢,例如下例中的 TextView 雖然經(jīng)過(guò)注解,但實(shí)際上并沒(méi)有被實(shí)例化纺且。

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

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

必須對(duì)頭視圖單獨(dú)進(jìn)行注入

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

2.編譯時(shí)注解解析

public class MainActivity extends AppCompatActivity{

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

編譯時(shí)Java會(huì)使用類ButterKnifeProcessor進(jìn)行編譯時(shí)注解處理稍浆,為每一個(gè)注解過(guò)的類生成一個(gè)中間類,如MainActivity類將生成中間類MainActivity$$ViewBinder衅枫;運(yùn)行時(shí)將執(zhí)行該中間類完成注入。ButterKnifeProcessor處理代碼如下

boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //1.首先找出使用的所有包含注解的類集合Map<TypeElement, BindingClass>步咪,其key是注解類益楼,如MainActivity,其值為相應(yīng)生成的BindingClass類感凤,包含注解集合。
    Map targetClassMap = this.findAndParseTargets(env);
    
    //2.依次為每一個(gè)注解類創(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;
}

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

1.BindingClass 類與使用注解的類一一對(duì)應(yīng)劳翰,它包含了該類的注解信息馒疹,如通過(guò)資源機(jī)制獲取到控件的 id 信息,是生成中間類的信息來(lái)源颖变。定義如下

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類表示對(duì)控件域的操作。

2.創(chuàng)建中間類
中間類的創(chuàng)建是使用Java流來(lái)寫(xiě)入的衔峰,其內(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方法就完成了資源的實(shí)例化穴肘。以string為例 2131099669 即根據(jù)@BindString(R.string.app_name)注解得到的資源 id。

3.對(duì)象實(shí)例化

中間類雖然具備了實(shí)例化注解對(duì)象的能力评抚,但在編譯時(shí)并沒(méi)有得到執(zhí)行,必須在運(yùn)行時(shí)進(jìn)行注解對(duì)象實(shí)例化邢笙。

ButterKnife.bind(this);

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

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

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

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

這里 targetClass 就是活動(dòng) MainActivity,因?yàn)橹虚g類的命名是固定的债鸡,即本類加$$ViewBinder后綴,故可以直接使用類名來(lái)查找中間類 MainActivity$$ViewBinder 厌均,而后使用反射創(chuàng)建中間類實(shí)例告唆。

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

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

其中Finder是一個(gè)枚舉類(VIEW侈净,ACTIVITY僧凤,DIALOG)畜侦,之所以要將它們區(qū)別開(kāi)來(lái)躯保,是因?yàn)樗鼈儾檎屹Y源的方式有所差別,對(duì)于不同的類型有不同的實(shí)現(xiàn)验懊。

執(zhí)行注解對(duì)象實(shí)例化的代碼最終委托給Finder類盯孙,包括綁定View與向下轉(zhuǎn)型兩步,例如對(duì)于 ACTIVITY 是這樣實(shí)現(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)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市痛垛,隨后出現(xiàn)的幾起案子桶蛔,更是在濱河造成了極大的恐慌,老刑警劉巖仔雷,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舔示,死亡現(xiàn)場(chǎng)離奇詭異电抚,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)俺祠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)借帘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人肺然,你說(shuō)我怎么就攤上這事≌玻” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)它匕。 經(jīng)常有香客問(wèn)我,道長(zhǎng)豫柬,這世上最難降的妖魔是什么扑浸? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮喝噪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘榴鼎。我一直安慰自己晚唇,他們只是感情好巫财,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布平项。 她就那樣靜靜地躺著,像睡著了一般闽瓢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鸳粉,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音枯夜,去河邊找鬼艰山。 笑死湖雹,一個(gè)胖子當(dāng)著我的面吹牛曙搬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播征讲,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼橡娄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了挽唉?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤匠童,失蹤者是張志新(化名)和其女友劉穎塑顺,沒(méi)想到半個(gè)月后汤求,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體茬暇,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡糙俗,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了巧骚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片格二。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡竣蹦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出长窄,到底是詐尸還是另有隱情,我是刑警寧澤挠日,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布翰舌,位于F島的核電站,受9級(jí)特大地震影響椅贱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜庇麦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望酸役。 院中可真熱鬧,春花似錦、人聲如沸贱呐。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)馁蒂。三九已至呵晚,卻和暖如春沫屡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沮脖。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工芯急, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留驶俊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓榕酒,卻偏偏與公主長(zhǎng)得像故俐,于是被迫代替她去往敵國(guó)和親想鹰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子购披,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,163評(píng)論 25 707
  • 本文主要介紹Android之神JakeWharton的一個(gè)注解框架刚陡,聽(tīng)說(shuō)現(xiàn)在面試官現(xiàn)在面試都會(huì)問(wèn)知不知道JakeW...
    Zeit丶閱讀 981評(píng)論 4 6
  • 參考1參考2參考3參考4 一:基本原理 編譯時(shí)注解+APT編譯時(shí)注解:Rentention為CLASS的直接。保留...
    shuixingge閱讀 553評(píng)論 0 3
  • 11月20日我讀了十頁(yè)從68頁(yè)到78頁(yè) 好詞:栩栩如生歌殃、 鑲嵌蝙云、不屈不撓氓皱、澎湃勃刨、剽竊、期期艾艾身隐、滾落、南北戰(zhàn)爭(zhēng)贾铝、粘...
    靜如子鈺閱讀 428評(píng)論 0 0
  • Don’t stand before me,or behind me.Just stand by me. 文|熊...
    熊恩閱讀 541評(píng)論 0 0