使用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\'");
}
}