最近項(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í)還沒有寫出來只恨。等寫出來歡迎大家來閱讀哈。