ButterKnife源碼解析一

最近項(xiàng)目不是很忙赁濒,因?yàn)轫?xiàng)目用到了butterknife框架,所以進(jìn)行了下系統(tǒng)的研究孟害。研究下來呢發(fā)現(xiàn)這個(gè)框架真的是吊炸天,而且越研究越覺得太精妙了挪拟。雖然并沒有完全的把各方面細(xì)節(jié)都研究明白不過還是算基本原理走痛了挨务。那么這篇就算是一個(gè)膚淺的分析吧,所以標(biāo)題起的有點(diǎn)不要臉玉组。大家見諒下面呢我就開始介紹這個(gè)框架啦谎柄。
  首先呢我先把這個(gè)框架的整體思路寫出來。然后再擴(kuò)展開這樣大家看起來能清晰點(diǎn)惯雳。
butterkknife一個(gè)編譯時(shí)注解注入框架朝巫。那么相比那些運(yùn)行時(shí)注解框架相比優(yōu)勢在于運(yùn)行時(shí)注解框架非常耗費(fèi)內(nèi)存。而編譯時(shí)注解內(nèi)存則相比很節(jié)約內(nèi)存石景。我們在寫注解后再項(xiàng)目編譯過程中劈猿。
  butterknife 會(huì)有一個(gè)注解處理器。這個(gè)處理器會(huì)掃描所有有關(guān)于我們butterknife 的注解的類潮孽。然后生成對應(yīng)的xxxx_ViewBinding的java類 揪荣,這個(gè)java類寫的就是我們那些注解要做的操作。比如findviewbyid()往史,
  然后這些java類繼承于存在注解的類仗颈。那么生成的類 如何與我們實(shí)際的類相關(guān)聯(lián)的呢?
  在于方法 butterknife.bind()這個(gè)方法會(huì)反射調(diào)用xxx_viewbinding 類的構(gòu)造椎例。那么我們那些findvuewbyid 這些代碼其實(shí)在xxx_viewbinding()的構(gòu)造里寫好了 這樣就相當(dāng)于我們自己寫了這些代碼省去了很多時(shí)間挨决。那么整體的大概原理就是這樣的
  首先開始講講注解的小知識(shí)吧 。創(chuàng)建一個(gè)注解需要你指定他的作用在哪 是方法订歪,類脖祈,字段。 還有要指定他在什么時(shí)候作用陌粹,運(yùn)行時(shí)撒犀,還是編譯時(shí)。
@Target:

@Target說明了Annotation所修飾的對象范圍:Annotation可被用于 packages掏秩、types(類或舞、接口、枚舉蒙幻、Annotation類型)映凳、類型成員(方法、構(gòu)造方法邮破、成員變量诈豌、枚舉值)仆救、方法參數(shù)和本地變量(如循環(huán)變量、catch參數(shù))矫渔。在Annotation類型的聲明中使用了target可更加明晰其修飾的目標(biāo)彤蔽。
 作用:用于描述注解的使用范圍(即:被描述的注解可以用在什么地方)
取值(ElementType)有:
    1.CONSTRUCTOR:用于描述構(gòu)造器
    2.FIELD:用于描述域
    3.LOCAL_VARIABLE:用于描述局部變量
    4.METHOD:用于描述方法
    5.PACKAGE:用于描述包
    6.PARAMETER:用于描述參數(shù)
    7.TYPE:用于描述類、接口(包括注解類型) 或enum聲明

@Retention:

@Retention定義了該Annotation被保留的時(shí)間長短:某些Annotation僅出現(xiàn)在源代碼中庙洼,而被編譯器丟棄顿痪;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會(huì)被虛擬機(jī)忽略油够,而另一些在class被裝載時(shí)將被讀纫舷(請注意并不影響class的執(zhí)行,因?yàn)锳nnotation與class在使用上是被分離的)石咬。使用這個(gè)meta-Annotation可以對 Annotation的“生命周期”限制揩悄。
  作用:表示需要在什么級別保存該注釋信息,用于描述注解的生命周期(即:被描述的注解在什么范圍內(nèi)有效)
  取值(RetentionPoicy)有:
    1.SOURCE:在源文件中有效(即源文件保留)
    2.CLASS:在class文件中有效(即class保留)
    3.RUNTIME:在運(yùn)行時(shí)有效(即運(yùn)行時(shí)保留)

我們的butterknife 的注解就是class 編譯時(shí)有效鬼悠。下面介紹完這個(gè)注解的基礎(chǔ)然后我們開始分析源碼

首先這個(gè)以bindview 為例

@Retention(CLASS) @Target(FIELD)
public @interface BindView {
/** View ID to which the field will be bound. */
  @IdRes int value();
}

這里我們看到指定保留有效為編譯 指定使用范圍是field 及我們的類變量上 所以我們初始化控件的注解是這樣的

@BindView(R.id.swipeLayout)
public MySwipeRefreshLayout swipeLayout;

到這里注解的工作完成删性。那么我們開始往下走 butterknife.bind(this);這里的話就是通過我們activity的對象獲取到了我們activty的根布局view 我們只要有了這個(gè)sourceview 就可以進(jìn)行findviewbyid 的操作了。

@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
  View sourceView = target.getWindow().getDecorView();
  return createBinding(target, sourceView);
}

我們接下來看createbinding() 方法 焕窝,發(fā)現(xiàn)tagert.getClass() 獲取到了activity 的對象字節(jié)碼 然后我們通過findBindingConstructorForClass 獲取到了ConstructorConstructor 能干什么 他可以直接來創(chuàng)建我們的對象調(diào)用對象的構(gòu)造方法镇匀。這就是我說的調(diào)用構(gòu)造來間接調(diào)用
注解處理器實(shí)現(xiàn)的那些我們省略的代碼

private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
   Class<?> targetClass = target.getClass();
  if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
   Constructor<? extends Unbinder> constructor =            findBindingConstructorForClass(targetClass);
  if (constructor == null) {
   return Unbinder.EMPTY;
  }
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
  return constructor.newInstance(target, source);
  } catch (IllegalAccessException e) {
  }
}

這個(gè)Constructor 并不是我們的activty對象 是注解處理器實(shí)現(xiàn)的java類的對象 這個(gè)對象其實(shí)是activity的子類 為了證明這點(diǎn)我們看findBindingConstructorForClass 方法。 我們看到 是首先在 BINDINGS里拿 這個(gè)BINDINGS 是什么 他是一個(gè)map 他存儲(chǔ)了Constructor 對象 static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
我們?nèi)绻谶@里拿到了就返回如果沒有就往下走 cls.getname() 獲取我們的activty的類名 到這里 cls 還是activty的字節(jié)碼對象 袜啃,然后判斷他是不是無效的java類 如果是返回null 如果不是 那么CLassforName(clsname+"+VIewBinding")這個(gè) 方法獲得我們注解處理器生成的 xxx_viewbinding 類的字節(jié)碼對象

然后getConstructor (cls,View.class) 這里為什么要傳入兩個(gè)參數(shù) 因?yàn)槲覀儀x_viewbinding 的構(gòu)造需要我們activty的對象和 activty的跟View 這樣拿到了構(gòu)造器 放到map集合 并返回 到這里我們知道了 上面我說的 返回的Constructor 并不是我們activity的構(gòu)造器 是xxx_viewbinding 的構(gòu)造器

Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls); if (bindingCtor != null) { return bindingCtor; } String clsName = cls.getName(); if (clsName.startsWith("android.") || clsName.startsWith("java.")) { return null; } try { Class<?> bindingClass = Class.forName(clsName + "_ViewBinding"); bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class); } catch (ClassNotFoundException e) { bindingCtor = findBindingConstructorForClass(cls.getSuperclass()); } BINDINGS.put(cls, bindingCtor); return bindingCtor;
  上面我們吧這一個(gè)過程分析完畢了汗侵,那么我想給大家看看神秘的 xxx_viewbinding 類 其實(shí)這個(gè)類我們是能找到的 他就是在build/generated/source/apt/debug 下面 你會(huì)發(fā)現(xiàn)所有用了注解的類都會(huì)有一個(gè)對應(yīng)的xxx_viewbinding 那么我拿出一個(gè)來給大家看看 他的 廬山真面目 ,這是我項(xiàng)目里一個(gè)登陸的activty大家可以看到 他繼承于 我們Loginactivity 所以 你到這里明白了為什么我們的注解 要求用public而不是private 大家可能沒看到我這個(gè)文章之前不太了解現(xiàn)在你應(yīng)該知道 就是為了我直接使用我們的 變量 然后進(jìn)行他們的findviewbyid 等操作。如果是私有的 那么還要通過反射這樣很耗費(fèi)cpu 資源群发,所以butterknife這個(gè)框架可以說考慮的非常全面了晰韵。 而且在unbind 的時(shí)候 會(huì)把這些對象都置為null 這樣也大大的避免了內(nèi)存泄露。

public class LoginActivity_ViewBinding<T extends LoginActivity> implements Unbinder {
protected T target;
private View view2131493049;
private View view2131493030;
private View view2131493050;
@UiThread
public LoginActivity_ViewBinding(final T target, View source) {
this.target = target;
View view;
target.et_phone = Utils.findRequiredViewAsType(source, R.id.et_phone, "field 'et_phone'", EditText.class);
target.et_pwd = Utils.findRequiredViewAsType(source, R.id.et_pwd, "field 'et_pwd'", EditText.class);
view = Utils.findRequiredView(source, R.id.tv_forget, "field 'tv_forget' and method 'intentForgetActivity'");
target.tv_forget = Utils.castView(view, R.id.tv_forget, "field 'tv_forget'", TextView.class);
view2131493049 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.intentForgetActivity(p0);
}
});
view = Utils.findRequiredView(source, R.id.img_pwd_isvisible, "field 'img_pwd_isvisible' and method 'changePwdVisible'");
target.img_pwd_isvisible = Utils.castView(view, R.id.img_pwd_isvisible, "field 'img_pwd_isvisible'", ImageView.class);
view2131493030 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.changePwdVisible(p0);
}
});
view = Utils.findRequiredView(source, R.id.bt_login, "method 'clickLogin'");
view2131493050 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.clickLogin(p0);
}
});
}
@Override
@CallSuper
public void unbind() {
T target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
target.et_phone = null;
target.et_pwd = null;
target.tv_forget = null;
target.img_pwd_isvisible = null;
view2131493049.setOnClickListener(null);
view2131493049 = null;
view2131493030.setOnClickListener(null);
view2131493030 = null;
view2131493050.setOnClickListener(null);
view2131493050 = null;
this.target = null;
}
}`

到這里 我就吧butterknife的整體的框架分析出來了熟妓。不過還是膚淺的 分析雪猪。 但是大家肯定很好奇 我們的xxx_viewbinding 是如何產(chǎn)生的呢。那么就請大家關(guān)注我的下一篇文章吧起愈。暫時(shí)還沒有寫出來只恨。等寫出來歡迎大家來閱讀哈。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末抬虽,一起剝皮案震驚了整個(gè)濱河市官觅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌阐污,老刑警劉巖休涤,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡功氨,警方通過查閱死者的電腦和手機(jī)序苏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捷凄,“玉大人忱详,你說我怎么就攤上這事《宓樱” “怎么了踱阿?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長钦铁。 經(jīng)常有香客問我,道長才漆,這世上最難降的妖魔是什么牛曹? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮醇滥,結(jié)果婚禮上黎比,老公的妹妹穿的比我還像新娘。我一直安慰自己鸳玩,他們只是感情好阅虫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著不跟,像睡著了一般颓帝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上窝革,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天购城,我揣著相機(jī)與錄音,去河邊找鬼虐译。 笑死瘪板,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的漆诽。 我是一名探鬼主播侮攀,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼厢拭!你這毒婦竟也來了兰英?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤供鸠,失蹤者是張志新(化名)和其女友劉穎箭昵,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體回季,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡家制,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年正林,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片颤殴。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡觅廓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出涵但,到底是詐尸還是另有隱情杈绸,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布矮瘟,位于F島的核電站瞳脓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏澈侠。R本人自食惡果不足惜劫侧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望哨啃。 院中可真熱鬧烧栋,春花似錦、人聲如沸拳球。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽祝峻。三九已至魔吐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間莱找,已是汗流浹背画畅。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宋距,地道東北人轴踱。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像谚赎,于是被迫代替她去往敵國和親淫僻。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

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