【實(shí)戰(zhàn)】Android Data Binding從抵觸到愛不釋手
1 引入
如何高效地實(shí)現(xiàn)以下界面?
登錄/未登錄
有好幾年findViewById實(shí)戰(zhàn)經(jīng)驗(yàn)的我,感覺并不難啊。一般會(huì)
1.先定義一個(gè)User的Model類,數(shù)據(jù)來自JSON解析;
2.創(chuàng)建一個(gè)xml,隨后在xml中布局完所有View,對(duì)頭像、標(biāo)題、積分、登錄按鈕一個(gè)id;
3.在Activity中通過findViewById獲取到頭像ImageView、標(biāo)題TextView、積分TextView、登錄Button,然后給Button設(shè)置監(jiān)聽器,再根據(jù)登陸狀態(tài)展示對(duì)應(yīng)數(shù)據(jù);
實(shí)現(xiàn)如下:
- User.java
- activity_detail.xml
- DetailActivity
2 去掉煩人的findViewById(View注入)
可以看到,在Activity中View的定義、find、判空占據(jù)了大量篇幅,我們需要更優(yōu)雅的實(shí)現(xiàn)。
2.1 ButterKnife
你可能聽說過Jake Wharton的ButterKnife,這個(gè)庫只需要在定義View變量的時(shí)候通過注解傳入對(duì)應(yīng)id,隨后在onCreate時(shí)調(diào)用ButterKnife.bind(this)即可完成view的注入,示例如下:
2.2 Android Data Binding
如果使用了Android Data Binding,那么View的定義、find、判空這些都不用寫了,如何做呢?
2.2.1 準(zhǔn)備工作
首先,你需要滿足一個(gè)條件:你的Android Plugin for Gradle版本必須等于或高于1.5.0-alpha1版本,這個(gè)版本位于根目錄build.gradle中,示例如下:
- buildscript {
- repositories {
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:2.1.0-rc1'
- }
- }
接著,你必須告訴編譯器開啟Data Binding,一般位于app:build.gradle的android標(biāo)簽中,示例如下:
- android {
- compileSdkVersion 23
- buildToolsVersion "23.0.2"
- dataBinding {
- enabled true
- }
- ...
- }
2.2.2 修改layout.xml
以activity_detail.xml為例,原來的根節(jié)點(diǎn)為L(zhǎng)inearLayout,如下所示:
2.2.3 開始享受樂趣吧!
在上述操作完成后,編譯器會(huì)自動(dòng)為我們生成
com.asha.demo.databinding.ActivityDetail2Binding.java類,這個(gè)類的命令方式為:包名 + databinding + activity_detail2駝峰命名方式 + Binding.java。隨后,使用這個(gè)activity_detail2的DetailActivity2.java的代碼可以簡(jiǎn)化為:
- public class DetailActivity2 extends AppCompatActivity {
- ActivityDetail2Binding binding; @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState);
- binding = DataBindingUtil.setContentView(this,R.layout.activity_detail2);
- login();
- } private void login(){ fill(User.newInstance()); } private void logout(){ fill(null); } private void fill(final User user){ final int visibility = user != null ? View.VISIBLE : View.GONE; if (user != null){
- binding.detailAvatar.setImageDrawable(ContextCompat.getDrawable(this,user.getAvatar()));
- binding.detailName.setText(user.getName());
- binding.detailDesc.setText(String.format("積分:%d 等級(jí):%d",user.getScore(),user.getLevel()));
- }
- binding.detailAvatar.setVisibility(visibility);
- binding.detailName.setVisibility(visibility);
- binding.detailDesc.setVisibility(visibility);
- binding.detailActionButton.setOnClickListener(new View.OnClickListener() { @Override
- public void onClick(View v) { if (user == null) login(); else logout();
- }
- });
- binding.detailActionButton.setText(user == null ? "登錄":"退出登錄");
- }
- }
是的,所有View的定義、find、判空都不見了,所有的這些操作都在編譯器為我們生成的ActivityDetail2Binding.java中完成,只需要在onCreate時(shí)調(diào)用如下代碼進(jìn)行setContentView即可實(shí)現(xiàn),
binding = DataBindingUtil.setContentView(this,R.layout.activity_detail2);
我的天哪
2.2.4 ActivityDetail2Binding中注入View相關(guān)的代碼分析
可以在as中方便的查看編譯器自動(dòng)生成的類,這個(gè)類位于/app/build/intermediates/classes/debug/com/asha/demo/databinding/ActivityDetail2Binding.class中,縮減掉Binding邏輯后的代碼為:
其中全局靜態(tài)SparseIntArray數(shù)組中存放了4個(gè)數(shù)字,這個(gè)四個(gè)數(shù)字為R.java中生成的對(duì)應(yīng)View的id,
- public final class R {
- ... public static final class id {
- ... public static final int detail_action_button = 2131492951; public static final int detail_avatar = 2131492948; public static final int detail_desc = 2131492950; public static final int detail_name = 2131492949;
- ...
- }
- ...
- }
在ActvityDetail2Binding實(shí)例構(gòu)造的時(shí)候調(diào)用了mapBindings,一次解決了所有View的查找,mapBindings函數(shù)在ActvityDetail2Binding父類ViewDataBinding中實(shí)現(xiàn)。
3 使用表達(dá)式在layout.xml中填充model數(shù)據(jù)
在ActivityDetail2.java中還存在大量的View控制、數(shù)據(jù)填充代碼,如何把這些代碼在交給layout.xml完成呢?
3.1 ModelAdapter類
第2節(jié)中已經(jīng)定義了User.java類作為Model類,但是我們經(jīng)常會(huì)遇到Model類和真正View展示不一致的情況,本例子中定義一個(gè)來ModelAdapter類來完整Model數(shù)據(jù)到展示數(shù)據(jù)的適配。示例代碼為ActivityDetail3.java的內(nèi)部類,可以調(diào)用ActivityDetail3.java中的函數(shù),代碼定義如下:
3.2 activity_detail3.xml中使用model
同樣復(fù)制一份activity_detail2.xml為activity_detail3.xml,在<layout>節(jié)點(diǎn)加入<data>節(jié)點(diǎn),并且在里面定義需要用的model類(比如ModelAdapter adapter),當(dāng)然也可以是基礎(chǔ)類型變量(比如int visibility);
隨后,就可以在下面的view中使用表達(dá)式了,全部布局文件如下:
- <?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android">
- <data>
- <variable name="adapter" type="com.asha.demo.DetailActivity3.ModelAdapter"/>
- <variable name="visibility" type="int"/>
- </data>
- <LinearLayout
- android:orientation="vertical" android:layout_width="match_parent"
- android:layout_height="match_parent">
- <View
- android:background="@color/detail_background"
- android:layout_width="match_parent"
- android:layout_height="66dp">
- </View>
- <ImageView
- android:src="@{adapter.avatar}"
- android:visibility="@{visibility}"
- android:id="@+id/detail_avatar"
- android:layout_gravity="center"
- android:layout_marginTop="-33dp"
- android:layout_width="66dp"
- android:layout_height="66dp" />
- <TextView
- android:visibility="@{visibility}"
- android:text="@{adapter.name}"
- android:id="@+id/detail_name"
- android:textSize="17sp"
- android:textColor="@color/textColorPrimary"
- android:layout_marginTop="15dp"
- android:layout_gravity="center"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- <TextView
- android:visibility="@{visibility}"
- android:text="@{adapter.desc}"
- android:id="@+id/detail_desc"
- android:layout_marginTop="15dp"
- android:textSize="13sp"
- android:layout_gravity="center"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- <Button
- android:text="@{adapter.actionText}"
- android:onClick="@{adapter.clickHandler}"
- android:id="@+id/detail_action_button"
- android:layout_marginTop="15dp"
- android:layout_gravity="center"
- android:textColor="@color/white"
- android:background="@drawable/selector_g_button"
- android:layout_width="220dp"
- android:layout_height="wrap_content" />
- </LinearLayout></layout>
3.3 DetailActivity3.java中調(diào)用填充
如下代碼所示,只需要在登錄狀態(tài)改變的時(shí)候,給viewDataBinding設(shè)置所需要的adatper、visibility值,即可完成數(shù)據(jù)的填充
3.4 ActivityDetail3Binding中填充相關(guān)的代碼分析
同樣,ActivityDetail3Binding中,編譯器根據(jù)activity_detail3.xml中的<data>標(biāo)簽,自動(dòng)生成了諸如setAdapter、setVisibility的代碼,setAdapter相關(guān)代碼如下:
非常簡(jiǎn)單,自動(dòng)生成了getter和setter,在完成set操作后,調(diào)用執(zhí)行notifyPropertyChanged和super.requestRebind()
- notifyPropertyChanged
ViewDataBinding本身就是一個(gè)BaseObservable, 在往ViewDataBinding注冊(cè)觀察某個(gè)屬性的變化,如果注冊(cè)了mAdapter的變化,對(duì)應(yīng)的觀察器就會(huì)接收到回調(diào)。相關(guān)邏輯與反向Binding相關(guān),谷歌官方還沒給出相關(guān)使用文檔,不再深入分析;
- super.requestRebind()
1.此函數(shù)為ViewDataBinding中的函數(shù),具體實(shí)現(xiàn)為判斷現(xiàn)在是否有Rebind請(qǐng)求,如果有則return;如果沒有則根據(jù)運(yùn)行時(shí)sdk版本交給handler或者choreographer插入到下一幀中執(zhí)行mRebindRunnable。
2.在mRebindRunnable中會(huì)根據(jù)當(dāng)前sdk版本,如果大于等于KITKAT,則需要在onAttachToWindow后執(zhí)行executePendingBindings;否則直接執(zhí)行executePendingBindings。
3.在父類ViewDataBinding中經(jīng)過一些的判斷,調(diào)用到ActivityDetail3Binding中的executeBindings,在executeBindings中根據(jù)dirtyFlags執(zhí)行不同的View屬性賦值,以下所有ActivityDetail3Binding相關(guān)代碼都是編譯器自動(dòng)生成的
- public class ActivityDetail3Binding extends ViewDataBinding{
- ... protected void executeBindings() { long dirtyFlags = 0L; synchronized(this) {
- dirtyFlags = this.mDirtyFlags; this.mDirtyFlags = 0L;
- }
- Drawable avatarAdapter = null;
- ModelAdapter adapter = this.mAdapter;
- String descAdapter = null;
- String nameAdapter = null;
- ActivityDetail3Binding.OnClickListenerImpl androidViewViewOnCli = null;
- String actionTextAdapter = null; int visibility = this.mVisibility; if((dirtyFlags & 5L) != 0L && adapter != null) {
- avatarAdapter = adapter.getAvatar();
- descAdapter = adapter.getDesc();
- nameAdapter = adapter.getName();
- androidViewViewOnCli = (this.mAndroidViewViewOnCl == null?(this.mAndroidViewViewOnCl = new ActivityDetail3Binding.OnClickListenerImpl()):this.mAndroidViewViewOnCl).setValue(adapter);
- actionTextAdapter = adapter.actionText();
- } if((dirtyFlags & 6L) != 0L) {
- ;
- } if((dirtyFlags & 5L) != 0L) {
- TextViewBindingAdapter.setText(this.detailActionButton, actionTextAdapter); this.detailActionButton.setOnClickListener(androidViewViewOnCli);
- ImageViewBindingAdapter.setImageDrawable(this.detailAvatar, avatarAdapter);
- TextViewBindingAdapter.setText(this.detailDesc, descAdapter);
- TextViewBindingAdapter.setText(this.detailName, nameAdapter);
- } if((dirtyFlags & 6L) != 0L) { this.detailAvatar.setVisibility(visibility); this.detailDesc.setVisibility(visibility); this.detailName.setVisibility(visibility);
- }
- }
- ...
- }
至此,完成了View數(shù)據(jù)的填充分析。
4 Binding
自動(dòng)生成的ViewDataBinding類(例如ActivityDetail3Binding)內(nèi)包含了Model + View,是MVVM中的MV的概念。
第2章的View注入,第3章的View賦值都是鋪墊,他們最后都是為Binding操作進(jìn)行服務(wù)。目前谷歌已經(jīng)支持雙向Binding,但上文已經(jīng)提到,目前資料比較少。本文只關(guān)注單向的Binding,即:Model的變化,自動(dòng)同步到View上。
4.1 使用ObservableField
目前所提供的ObservableField有:
Observable類型 | 對(duì)應(yīng)原類型 |
---|---|
ObservableArrayList | ArrayList |
ObservableArrayMap | ArrayMap |
ObservableBoolean | boolean |
ObservableByte | byte |
ObservableChar | char |
ObservableFloat | float |
ObservableDouble | double |
ObservableLong | long |
ObservableInt | int |
ObservableParcelable<T extends Parcelable> | <T extends Parcelable> |
ObservableField<T> | <T> |
本文使用簡(jiǎn)單的ObservableInt作為示例,解決visibility的單項(xiàng)綁定問題。
- 改造activity_detail4.xml:定義類型為ObservableInt的variable,name為visibility,隨后賦值給ImageView的android:visibility,示例如下:
- <layout xmlns:android="http://schemas.android.com/apk/res/android">
- <data>
- <variable name="visibility" type="android.databinding.ObservableInt"/>
- </data>
- <LinearLayout
- android:orientation="vertical" android:layout_width="match_parent"
- android:layout_height="match_parent">
- ... <ImageView
- android:visibility="@{visibility.get()}"
- android:id="@+id/detail_avatar"
- android:layout_gravity="center"
- android:layout_marginTop="-33dp"
- android:layout_width="66dp"
- android:layout_height="66dp" />
- ... </LinearLayout></layout>
- 改造DetailActivity4.java,只需要在onCreate時(shí)把visibility賦值給binding(ActivityDetail4Binding)即可,后面對(duì)visibility的操作,就會(huì)更新到view上,示例代碼如下:
- public class DetailActivity4 extends AppCompatActivity {
- ActivityDetail4Binding binding;
- ObservableInt visibility = new ObservableInt(); @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState);
- binding = DataBindingUtil.setContentView(this,R.layout.activity_detail4,new MyComponent());
- binding.setVisibility(visibility);
- login();
- } private void login(){ fill(User.newInstance()); } private void logout(){ fill(null); } private void fill(final User user){
- visibility.set(user != null ? View.VISIBLE : View.GONE);
- ....
- }
- ....
- }
4.2 ActivityDetail4Binding中單向綁定相關(guān)的代碼分析
與給ActivityDetail4Binding直接set純Model不同,所有的ObservableField都實(shí)現(xiàn)了Observable接口,只要實(shí)現(xiàn)了Observable接口,都是單向Binding類型,所以ActivityDetail4Binding中的setVisibility多加了一行代碼:this.updateRegistration(1, visibility),其中1為propertyId,目前一共自動(dòng)生成了2個(gè),0為adatper,1為visibility,代碼如下:
- public class ActivityDetail4Binding extends ViewDataBinding {
- ... public void setVisibility(ObservableInt visibility) { this.updateRegistration(1, visibility); this.mVisibility = visibility; synchronized(this) { this.mDirtyFlags |= 2L;
- } this.notifyPropertyChanged(3); super.requestRebind();
- }
- ...
- }
updateRegistration函數(shù)為ViewDataBinding中的函數(shù),會(huì)根據(jù) Observable、ObservableList、ObservableMap三種類型,分別創(chuàng)建對(duì)應(yīng)的Listener。ObservableInt為Observable,所以會(huì)使用CREATE_PROPERTY_LISTENER,在registerTo函數(shù)中創(chuàng)建WeakPropertyListener,
代碼如下:
- public abstract class ViewDataBinding extends BaseObservable {
- ... private boolean updateRegistration(int localFieldId, Object observable,
- CreateWeakListener listenerCreator) { if (observable == null) { return unregisterFrom(localFieldId);
- }
- WeakListener listener = mLocalFieldObservers[localFieldId]; if (listener == null) {
- registerTo(localFieldId, observable, listenerCreator); return true;
- } if (listener.getTarget() == observable) { return false;//nothing to do, same object
- }
- unregisterFrom(localFieldId);
- registerTo(localFieldId, observable, listenerCreator); return true;
- }
- ...
- }
在WeakPropertyListener的mListener有個(gè)setTarget函數(shù),這個(gè)函數(shù)會(huì)向mObservable(即外面?zhèn)鬟M(jìn)來的visibility)注冊(cè)一個(gè)監(jiān)聽器,如果visibility值發(fā)生變化,這個(gè)listener就會(huì)得到通知,回調(diào)到WeakPropertyListener的onPropertyChanged,接著通知到binding(ActivityDetail4Binding)的handleFieldChange,在handleFieldChange中調(diào)用了ActivityDetail4Binding的onFieldChange函數(shù),如果返回值為true,則在handleFieldChange中調(diào)用requestRebind(),通知View進(jìn)行賦值更新界面,onFieldChange相關(guān)代碼如下:
- public abstract class ViewDataBinding extends BaseObservable {
- ... private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) { boolean result = onFieldChange(mLocalFieldId, object, fieldId); if (result) {
- requestRebind();
- }
- }
- ...
- }
- public class ActivityDetail4Binding extends ViewDataBinding {
- ... protected boolean onFieldChange(int localFieldId, Object object, int fieldId) { switch(localFieldId) { case 0: return this.onChangeAdapter((ModelAdapter)object, fieldId); case 1: return this.onChangeVisibility((ObservableInt)object, fieldId); default: return false;
- }
- }
- ...
- }
4.3 Observable Objects
與4.1 ObservableField類似,可以改造一下ModelAdapter:為getter方法增加@Bindable注解,為setter方法增加notifyPropertyChanged(com.asha.demo.BR.name)通知。其中,BR是根據(jù)@Bindalbe自動(dòng)生成的類,給getter方法增加@Bindable注解后,BR文件自動(dòng)會(huì)生成一個(gè)整型的name。改造后代碼如下:
- public class DetailActivity4 extends AppCompatActivity {
- ActivityDetail4Binding binding;
- ObservableInt visibility = new ObservableInt(); public class ModelAdapter extends BaseObservable{ private User user; public ModelAdapter(User user) { this.user = user;
- }
- ... @Bindable
- public String getName(){ return user != null ? user.getName() : null;
- } public void setName(String name){ if (user != null) user.setName(name);
- notifyPropertyChanged(com.asha.demo.BR.name);
- }
- ...
- }
- ...
- }
隨后,在DetailActivity4.java中調(diào)用測(cè)試代碼,執(zhí)行完會(huì)在1秒后改變adapter上的name值,并且同步到View上,測(cè)試代碼如下:
- binding.detailActionButton.postDelayed(new Runnable() { @Override
- public void run() {
- adapter.setName("haha");
- }
- },1000);
具體原理與4.1類似,不再贅述。
5 layout.xml中View屬性的setter
在下述示例中,detail_name這個(gè)TextView想把a(bǔ)dapter.name賦值給自身的text屬性,就需要調(diào)用textView.setText(String)方法,這個(gè)方法就是View屬性的setter方法。
- <TextView
- android:text="@{adapter.name}"
- android:id="@+id/detail_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
5.1 @BindingAdapter
上述的setter方法,Data Binding庫幫我們實(shí)現(xiàn)了大部分默認(rèn)方法,具體方法參見android.databinding.adapters包下的類,下圖為ViewBindingAdatper具體實(shí)現(xiàn),
ViewBindingAdatper
其中setter方法都為static方法,第一個(gè)參數(shù)都為自身的實(shí)例,后面為xml中傳入的參數(shù),只要加入@BindingAdapter注解,編譯器就會(huì)全局搜索保存在一個(gè)temp文件中,并在生成類似ActivityDetail4Binding過程中去查找所需的setter方法的。如果需要自定義,只需要在任意app代碼中定義@BindingAdapter即可,例如:
- public class DetailActivity4 extends AppCompatActivity { @BindingAdapter("android:alpha") public static void globalSetAlpha(View view, float alpha) {
- view.setAlpha(alpha);
- }
- }
5.2 DataBindingComponent
很多情況下只是某個(gè)Binding文件(例如ActivityDetail4Binding)需要自定義setter方法,這個(gè)時(shí)候就需要使用DataBindingComponent,
- 首先,定義一個(gè)MyComponent,
- public class MyComponent implements android.databinding.DataBindingComponent { @BindingAdapter("android:alpha") public void setAlpha(View view, float alpha) {
- view.setAlpha(0.5f);
- } @Override
- public MyComponent getMyComponent() { return new MyComponent();
- }
- }
- 接著,在生成Binding對(duì)象時(shí)傳入這個(gè)DataBindingComponent實(shí)例,代碼如下:
- @Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState);
- binding = DataBindingUtil.setContentView(this,R.layout.activity_detail4,new MyComponent());
- ...
- }
完成后,這個(gè)ActivityDetail4Binding范圍內(nèi)的所有android:alpha="@{foo}"的方式賦值alpha的setter函數(shù)都會(huì)使用MyComponent#setAlpha。
5.3 @BindingConversion
有時(shí)候會(huì)遇到類型不匹配的問題,比如R.color.white是int,但是通過Data Binding賦值給android:background屬性后,需要把int轉(zhuǎn)換為ColorDrawable,實(shí)現(xiàn)方式如下:
- 1.定義一個(gè)靜態(tài)函數(shù),放在項(xiàng)目任意類中,
- @BindingConversionpublic static Drawable convertColorToDrawable(int drawable) { return new ColorDrawable(drawable);
- }
- 2.在layout.xml中使用Data Binding,如:
- <Viewandroid:background="@{adapter.avatar != null ? @color/detail_background : @color/colorAccent }"android:layout_width="match_parent"android:layout_height="66dp">
對(duì)應(yīng)在ActivityDetail4Binding.java中生成的代碼如下所示,其中AvatarAdapterObjectn1為int類型:
- ViewBindingAdapter.setBackground(this.mboundView1, DetailActivity4.convertColorToDrawable(AvatarAdapterObjectn1));
5.4 @BindingMethod
例如layout.xml中android:onClick屬性,在Binding中真正使用setter時(shí),就對(duì)應(yīng)到了setOnClickListener方法,
- @BindingMethod(type = View.class, attribute = "android:onClick", method = "setOnClickListener"),
6 Data Binding利用編譯器在背后做的那些事兒
Data Binding相關(guān)的jar包由四部分組成,
- 1.baseLibrary-2.1.0-rc1.jar
作為運(yùn)行時(shí)類庫被打進(jìn)APK中;
- 2.DataBinderPlugin(gradle plugin)
在編譯期使用,利用gradle-api(之前叫transform-api,1.5生,2.0改名)處理xml文件,生成DataBindingInfo.java;
- 3.compiler-2.1.0-rc1.jar
在編譯器使用,入口類繼承自AbstractProcessor,用于處理注解,并生成Binding類,DataBindingCompoent.java,DataBinderMapper.java類;
- 4.compilerCommon-2.1.0-rc1.jar
被DataBinderPlugin和compiler-2.1.0-rc1.jar所依賴
為了提高運(yùn)行時(shí)的效率,Data Binding在背后做了非常多的工作,下圖是我整理的編譯流程,如圖所示:
Data Binding編譯流程
6.1 相關(guān)對(duì)象介紹
- 白色部分為輸入,包括
1.res/layout;
2.源代碼中的注解;
- 黃色部分為編譯器處理類,包括
1.aapt編譯時(shí)處理,入口類名為MakeCopy.java;
2.gradle-api處理,入口類名為DataBinderPlugin.java;
3.AbstractProcessor處理,入口類名為ProcessDataBinding.java;
- 藍(lán)色部分為中間產(chǎn)物,包括
1.data-binding-info文件夾,包含了layout的基本信息,導(dǎo)入的變量,View標(biāo)簽中的表達(dá)式,標(biāo)簽的位置索引等等,如下所示為data-binding-info/activity_detail3-layout.xml:
2.setter_store.bin,包含所有setter相關(guān)信息;
3.layoutinfo.bin,包含所有l(wèi)ayout相關(guān)信息;
4.br.bin,包含所有BR相關(guān)信息;
以上bin文件都以Serializable方式序列化到磁盤上,需要的時(shí)候進(jìn)行反序列化操作;
- 綠色部分為最終產(chǎn)物,包括
1.data-binding-layout-out(最終輸出到res/layout),即去掉根節(jié)點(diǎn)<layout>,去掉節(jié)點(diǎn)<data>,與不使用Data Binding時(shí)的layout相一致,例如data-binding-layout-out/activity_detail2.xml:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout
- android:orientation="vertical" android:layout_width="match_parent"
- android:layout_height="match_parent" android:tag="layout/activity_detail2_0" xmlns:android="http://schemas.android.com/apk/res/android">
- <View
- android:background="@color/detail_background"
- android:layout_width="match_parent"
- android:layout_height="66dp">
- </View>
- ...
- </LinearLayout>
2.DataBindingInfo.class,一個(gè)看似空的類,但在SOURCE階段包含了一個(gè)@BindingBuildInfo注解,包含了基本DataBinding的基本信息,代碼如下:
- // DataBindingInfo.classpublic class DataBindingInfo { public DataBindingInfo() {
- }
- }// @BindingBuildInfo@Target({ElementType.TYPE})@Retention(RetentionPolicy.SOURCE)public @interface BindingBuildInfo { String buildId(); String modulePackage(); String sdkRoot(); int minSdk(); String layoutInfoDir(); String exportClassListTo(); boolean isLibrary(); boolean enableDebugLogs() default false; boolean printEncodedError() default false;
- }
3.DataBindingComponent.class,會(huì)根據(jù)自定義的DataBindingComponent自動(dòng)生成對(duì)應(yīng)實(shí)例化方法,例如:
- public interface DataBindingComponent { MyComponent getMyComponent();
- }
4.ViewDataBinding.class的子類(ActivityDetail2Binding.class等)
5.BR.class,Bindable屬性索引表,例如:
- public class BR { public static final int _all = 0; public static final int adapter = 1; public static final int name = 2; public static final int visibility = 3; public BR() {
- }
- }
6.DataBindingMapper.class,Mapper,用于尋找某個(gè)layout.xml對(duì)應(yīng)的ViewDataBinding類,例如:
- class DataBinderMapper { static final int TARGET_MIN_SDK = 16; public DataBinderMapper() {
- } public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view, int layoutId) { switch(layoutId) { case 2130968602: return ActivityDetail2Binding.bind(view, bindingComponent); case 2130968603: return ActivityDetail3Binding.bind(view, bindingComponent);
- .... default: return null;
- }
- } ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View[] views, int layoutId) { return null;
- } int getLayoutId(String tag) { if(tag == null) { return 0;
- } else { int code = tag.hashCode(); switch(code) { case -600937657: if(tag.equals("layout/activity_detail2_0")) { return 2130968602;
- } break; case -600936696: if(tag.equals("layout/activity_detail3_0")) { return 2130968603;
- } break;
- .... return 0;
- }
- } String convertBrIdToString(int id) { return id >= 0 && id < DataBinderMapper.InnerBrLookup.sKeys.length?DataBinderMapper.InnerBrLookup.sKeys[id]:null;
- } private static class InnerBrLookup { static String[] sKeys = new String[]{"_all", "adapter", "name", "visibility"}; private InnerBrLookup() {
- }
- }
- }
6.2 相關(guān)編譯流程
- STEP1 資源處理
aapt或者gradle執(zhí)行時(shí),都會(huì)觸發(fā)資源處理,在資源處理過程中,DataBinding都會(huì)掃描一遍現(xiàn)有的資源,生成不包含<layout>的data-binding-layout-out以及DataBinding所需要的data-binding-info;
- STEP2 DataBindingInfo.class生成
在完成資源處理后,aapt或者gradle-api都會(huì)去執(zhí)行DataBindingInfo.class生成操作,把相關(guān)的信息寫入DataBindingInfo.class的@BindingBuildInfo注解中;
- STEP3 監(jiān)聽到注解變化
生成@BindingBuildInfo注解,或者code中發(fā)現(xiàn)有新的注解寫入,AbstractProcessor注解處理器就開始執(zhí)行注解處理。DataBinding中有一個(gè)ProcessDataBinding.java類專門來處理DataBinding相關(guān)的注解;
- STEP4 ProcessDataBinding處理注解,生成bin
ProcessDataBinding中處理注解永遠(yuǎn)會(huì)按順執(zhí)行3步,ProcessMethodAdapter,ProcessExpressions,ProcessBindable。每次執(zhí)行都會(huì)從磁盤反序列化對(duì)應(yīng)的bin文件,然后忘bin中寫入新的,完成后再序列化到磁盤;
- STEP5 生成最終產(chǎn)物
執(zhí)行ProcessMethodAdapter生成DataBindingComponents.class;執(zhí)行ProcessExpressions生成ViewDataBinding.class子類(ActivityDetail2Binding.class),并觸發(fā)DataBindingMapper.class更新;執(zhí)行ProcessBindable生成BR.class,并觸發(fā)DataBindingMapper.class更新;
7 細(xì)節(jié)補(bǔ)充-View Tag的使用
第二章有講到View是如何注入的,其實(shí)需要分兩種情況:
- 1.如果這個(gè)View標(biāo)簽屬性中只有id,沒有其他"@{表達(dá)式}"形式,則按照第2章提到的方式直接通過id查找;
- 2.如果這個(gè)View標(biāo)簽屬性中有"@{表達(dá)式}"形式的值,則編譯器會(huì)自動(dòng)給這個(gè)View加個(gè)android:tag="binding_{N}", 其中{N}按順序從0開始遞增,如android:tag="binding_0"。當(dāng)執(zhí)行ViewDataBinding#mapBindings去注入View時(shí),會(huì)找tag為binding_開頭的View,隨后執(zhí)行View注入;
另外,如果View標(biāo)簽原來就有android:tag值,則編譯器會(huì)先保存原有值信息,寫入android:tag="binding_{N}"。當(dāng)執(zhí)行完view注入后,再把原來的值賦值給android:tag。注意如果原來的android:tag值為"binding_0",那么在View注入時(shí)將會(huì)發(fā)生錯(cuò)亂。
在完成View注入后,ActivityDetail3Binding會(huì)執(zhí)行this.setRootTag(root),代碼如下:
這與ListView中的ViewHoloder實(shí)現(xiàn)方式相似,所以如果把DataBinding運(yùn)用到ListView的ViewHolder中,就不需要多生成一個(gè)ViewHolder,直接使用這個(gè)ViewDataBinding類即可,例如ListAdapter實(shí)現(xiàn):
8 總結(jié)
- DataBinding 庫非常小
目前Android Data Binding在運(yùn)行類庫只有632個(gè)方法數(shù),算上每個(gè)layout.xml自動(dòng)生成的ViewDataBinding子類(demo中每個(gè)類不超過20個(gè)方法數(shù)),方法數(shù)總和也非常有限。
Data Binding方法數(shù)
- DataBinding 運(yùn)行時(shí)沒有多余性能損耗
DataBinding所有的View注入、View賦值、Binding都是編譯器自動(dòng)生成的代碼,這些重復(fù)的體力勞動(dòng)本身就需要去做,只是交給了編譯器來完成,所以運(yùn)行時(shí)沒有多余的性能損耗。
- DataBinding 可以減少錯(cuò)誤率
既然View注入、View賦值、Binding都是編譯器自動(dòng)完成的,只要使用正確,100%無低級(jí)錯(cuò)誤保證,可以提高代碼質(zhì)量,讓開發(fā)者心情愉悅。
- DataBinding 對(duì)編譯時(shí)長(zhǎng)的影響
還沒實(shí)際運(yùn)用到生產(chǎn)環(huán)境,肯定有所延長(zhǎng),具體量級(jí)還未知。