本文首發(fā):http://yuweiguocn.github.io/
本文介紹了Data Binding的原理。
關(guān)于Data Binding的使用請查看Data Binding。
原文鏈接:Understanding Data-Binding's generated code and How does Android Data-Binding compiler work
這篇文章并不是介紹怎樣使用Data Binding或了解基本概念。建議你直接查看Google文檔,會(huì)幫助你輕松集成,有大量的示例代碼,當(dāng)你決定應(yīng)用它到你的工程你可以實(shí)現(xiàn)很多很酷的東西。體驗(yàn)之后,你可能會(huì)好奇它是如何實(shí)現(xiàn)的,Google是怎樣讓傳統(tǒng)的xml文件和data binding混合的,xml文件是怎樣和java代碼交互的,編譯器是怎樣處理的。本文主題旨在揭露data binding的機(jī)制,深入底層看看到底發(fā)生了什么。因此,開發(fā)者可以深入理解幫助他們正確地使用data binding,利用data binding的強(qiáng)大構(gòu)建完美的應(yīng)用。
近來,data binding是android很火的趨勢,讓開發(fā)者敲代碼時(shí)更輕松。由于它的強(qiáng)大大量的開發(fā)者已經(jīng)開始使用data binding。但說實(shí)話,它也帶給我們一些麻煩。雖然data binding的概念很簡單:“Yay!定義一個(gè)ViewModel類在 layout.xml 文件中引入并且不需要關(guān)注任何UI的東西,我們的數(shù)據(jù)會(huì)直接綁定到UI上用一種神奇的方式”。真的很快,很簡單。當(dāng)出現(xiàn)一些錯(cuò)誤,當(dāng)你的數(shù)據(jù)突然不能綁定到UI上,當(dāng)編譯器報(bào)出大量的錯(cuò)誤信息,你真的不想知道這是什么意思么。我們能做些什么!
我不得不去面對這些data binding的問題并且使用不同的辦法去解決它。并且我認(rèn)為,只有通過查看data binding的源碼,理解它工作的原理,我就不用再去處理這些問題了。讓我們從Google clone下這個(gè)倉庫一起閱讀下它的代碼Data-Binding Repository。
Part 1:Data Binding 流,Obsevable模式機(jī)制和data-binding生成代碼的意思
為了能更好的理解代碼,我創(chuàng)建了一個(gè)簡單的使用data-binding的示例。
1.創(chuàng)建一個(gè)布局文件R.layout.activity_main
xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="com.example.main.MainViewModel" />
</data>
<TextView android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@{ viewModel.text }" />
</layout>
2.創(chuàng)建MainActivity
java
public class MainActivity extends AppCompatActivity {
@Inject
MainViewModel mViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding bind = DataBindingUtil.setContentView(this, R.layout.activity_main);
bind.setViewModel(mViewModel);
}
}
3.創(chuàng)建MainViewModel
java
public class MainViewModel extends BaseObservable {
public final ObservableField<String> text = new ObservableField<>();
public MainViewModel() {
}
}
編譯工程之后,我們可以看到data-binding生成的新的文件:
1.activity_main-layout.xml(在data-binding-info文件夾中)
xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout layout="activity_main" absoluteFilePath="/home/framgia/Projects/harpa-crista/harpacrista/android/app/src/main/res/layout/activity_main.xml"
directory="layout"
isMerge="false" modulePackage="com.harpacrista">
<Variables declared="true" name="viewModel" type="com.example.main.MainViewModel">
<location endLine="8" endOffset="51" startLine="6" startOffset="8" />
</Variables>
<Imports name="View" type="android.view.View">
<location endLine="10" endOffset="42" startLine="10" startOffset="8" />
</Imports>
<Targets>
<Target tag="layout/activity_main_0" view="TextView">
<Expressions>
<Expression attribute="android:text" text=" viewModel.text ">
<Location endLine="16" endOffset="41" startLine="16" startOffset="8" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="16" endOffset="39" startLine="16" startOffset="24" />
</Expression>
</Expressions>
<location endLine="16" endOffset="44" startLine="14" startOffset="4" />
</Target>
</Targets>
</Layout>
2.activity_main.xml(正常的布局文件)的簡潔版本(在data-binding-layout-out文件夾中)
xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent"
android:tag="layout/activity_main_0" />
3.ActivityMainBinding.java文件,這是我們的主角,最神奇的地方。
java
public class ActivityMainBinding extends android.databinding.ViewDataBinding {
private static final android.databinding.ViewDataBinding.IncludedLayouts sIncludes;
private static final android.util.SparseIntArray sViewsWithIds;
static {
sIncludes = null;
sViewsWithIds = null;
}
// views
private final android.widget.TextView mboundView0;
// variables
private com.example.main.MainViewModel mViewModel;
// values
// listeners
// Inverse Binding Event Handlers
public ActivityMainBinding(android.databinding.DataBindingComponent bindingComponent, View root) {
super(bindingComponent, root, 2);
final Object[] bindings = mapBindings(bindingComponent, root, 1, sIncludes, sViewsWithIds);
this.mboundView0 = (android.widget.TextView) bindings[0];
this.mboundView0.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
@Override
public void invalidateAll() {
synchronized(this) {
mDirtyFlags = 0x4L;
}
requestRebind();
}
@Override
public boolean hasPendingBindings() {
synchronized(this) {
if (mDirtyFlags != 0) {
return true;
}
}
return false;
}
public boolean setVariable(int variableId, Object variable) {
switch(variableId) {
case BR.viewModel :
setViewModel((com.example.main.MainViewModel) variable);
return true;
}
return false;
}
public void setViewModel(com.example.main.MainViewModel viewModel) {
updateRegistration(0, viewModel);
this.mViewModel = viewModel;
synchronized(this) {
mDirtyFlags |= 0x1L;
}
notifyPropertyChanged(BR.viewModel);
super.requestRebind();
}
public com.example.main.MainViewModel getViewModel() {
return mViewModel;
}
@Override
protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
switch (localFieldId) {
case 0 :
return onChangeViewModel((com.example.main.MainViewModel) object, fieldId);
case 1 :
return onChangeTextViewMode((android.databinding.ObservableField<java.lang.String>) object, fieldId);
}
return false;
}
private boolean onChangeViewModel(com.example.main.MainViewModel viewModel, int fieldId) {
switch (fieldId) {
case BR._all: {
synchronized(this) {
mDirtyFlags |= 0x1L;
}
return true;
}
}
return false;
}
private boolean onChangeTextViewMode(android.databinding.ObservableField<java.lang.String> textViewModel, int fieldId) {
switch (fieldId) {
case BR._all: {
synchronized(this) {
mDirtyFlags |= 0x2L;
}
return true;
}
}
return false;
}
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
com.example.main.MainViewModel viewModel = mViewModel;
java.lang.String textViewModel = null;
android.databinding.ObservableField<java.lang.String> textViewModel1 = null;
if ((dirtyFlags & 0x7L) != 0) {
if (viewModel != null) {
// read viewModel.text
textViewModel1 = viewModel.text;
}
updateRegistration(1, textViewModel1);
if (textViewModel1 != null) {
// read viewModel.text.get()
textViewModel = textViewModel1.get();
}
}
// batch finished
if ((dirtyFlags & 0x7L) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView0, textViewModel);
}
}
// Listener Stub Implementations
// callback impls
// dirty flag
private long mDirtyFlags = 0xffffffffffffffffL;
public static ActivityMainBinding inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot) {
return inflate(inflater, root, attachToRoot, android.databinding.DataBindingUtil.getDefaultComponent());
}
public static ActivityMainBinding inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot, android.databinding.DataBindingComponent bindingComponent) {
return android.databinding.DataBindingUtil.<ActivityMainBinding>inflate(inflater, com.harpacrista.R.layout.activity_main, root, attachToRoot, bindingComponent);
}
public static ActivityMainBinding inflate(android.view.LayoutInflater inflater) {
return inflate(inflater, android.databinding.DataBindingUtil.getDefaultComponent());
}
public static ActivityMainBinding inflate(android.view.LayoutInflater inflater, android.databinding.DataBindingComponent bindingComponent) {
return bind(inflater.inflate(com.harpacrista.R.layout.activity_main, null, false), bindingComponent);
}
public static ActivityMainBinding bind(android.view.View view) {
return bind(view, android.databinding.DataBindingUtil.getDefaultComponent());
}
public static ActivityMainBinding bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) {
if (!"layout/activity_main_0".equals(view.getTag())) {
throw new RuntimeException("view tag isn't correct on view:" + view.getTag());
}
return new ActivityMainBinding(bindingComponent, view);
}
/* flag mapping
flag 0 (0x1L): viewModel
flag 1 (0x2L): viewModel.text
flag 2 (0x3L): null
flag mapping end*/
//end
}
正如我們所看到的,data-binding從activity_main.xml文件幫我們生成了3個(gè)額外的文件:activity_main-layout.xml,activity_main.xml的簡潔版本和ActivityMainBinding.java。至此,我們對xml布局生成的代碼有了一個(gè)初步的了解。
基本上,一個(gè)xml布局文件最外層的<layout></layout>標(biāo)簽表示和正常布局文件之間的區(qū)別。如果一個(gè)正常的布局文件是直接用于android應(yīng)用,放置在apk包中的res/layout文件夾中,那布局文件中最外層的<layout></layout>標(biāo)簽則是被間接使用。編譯器通過搜索應(yīng)用的layout文件夾編譯所有最外層為<layout></layout>標(biāo)簽的布局文件為activity_main.xml(正常的布局文件)的簡潔版本。這個(gè)版本的xml看起來就像我們沒有使用data-binding的正常的布局文件。
因此,基本上,使用data-binding的布局文件就是正常布局文件的一個(gè)特殊的版本,data-binding給我們一些詞匯和語法,通過這種方式強(qiáng)制它們重寫正常的布局文件。Xml文件不能被Android框架理解,它只能被data-binding編譯器理解,這讓編譯器知道什么地方從ViewModel有怎樣的數(shù)據(jù)映射到View上。最后編譯器把xml文件轉(zhuǎn)換成可以打包到apk中的正常的布局文件,以及activity_main-layout.xml 和 ActivityMainBinding.java。
我們可以想象原始的布局文件包含兩部分:正常部分和綁定部分。正常部分就像我們沒有使用data-binding時(shí)寫的布局文件。綁定部分用于幫助編譯器生成java代碼,它是一個(gè)在UI和數(shù)據(jù)之間很好的橋梁。
activity_main-layout.xml (在data-binding-info文件夾中)包含綁定部分。就像它的名字,這個(gè)文件是布局的綁定信息。我們再來回顧下這個(gè)文件。外層仍是<Layout>標(biāo)簽,但和之前的不一樣。
xml
<Layout layout="activity_main" absoluteFilePath="/home/framgia/android/app/src/main/res/layout/activity_main.xml"
directory="layout" isMerge="false" modulePackage="com.harpacrista">
...
</Layout>
layout="activity_main"屬性表示這個(gè)文件是acitivity_main.xml的綁定部分,當(dāng)前存放在"/home/framgia/android/app/src/main/res/layout/activity_main.xml"。
在<Layout>標(biāo)簽的里面,毫無疑問,有<Variables>標(biāo)簽和<Imports>標(biāo)簽。因?yàn)槲覀兦懊嫖覀冇玫絪etViewModel,并且在布局文件中引用了一些類。
xml
<Variables declared="true" name="viewModel" type="com.example.main.MainViewModel">
<location endLine="8" endOffset="51" startLine="6" startOffset="8" />
</Variables>
<Imports name="View" type="android.view.View">
<location endLine="10" endOffset="42" startLine="10" startOffset="8" />
</Imports>
它們也包含位置location信息。我不知道為什么編譯器需要存儲(chǔ)Variable/Import的位置信息,可能用于追溯或其它什么作用。
接下來是這個(gè)文件中最重要的部分,<Target>標(biāo)簽告訴ViewModel應(yīng)該映射到哪個(gè)View,綁定類型為單向還是雙向,View的位置及View值的位置。
xml
<Targets>
<Target tag="layout/activity_main_0" view="TextView">
<Expressions>
<Expression attribute="android:text" text=" viewModel.text ">
<Location endLine="16" endOffset="41" startLine="16" startOffset="8" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="16" endOffset="39" startLine="16" startOffset="24" />
</Expression>
</Expressions>
<location endLine="16" endOffset="44" startLine="14" startOffset="4" />
</Target>
</Targets>
我們有一個(gè)<Target>標(biāo)簽的列表。在布局文件中我們可以有多個(gè)View/ViewGroup,但只有包含data-binding表達(dá)式的View/ViewGroup才會(huì)出現(xiàn)在這里。<Target>標(biāo)簽里面的<Expressions>標(biāo)簽表示View的data-binding表達(dá)式。例如:
- Data-binding表達(dá)式
android:visibility="@{ viewModel.isVisible ? View.VISIBLE : View.INVISIBLE }"會(huì)被編譯成
<Expression attribute="android:visibility"
text=" viewModel.isVisible ? View.VISIBLE : View.INVISIBLE ">
<Location endLine="29" endOffset="88" startLine="29" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="29" endOffset="86" startLine="29" startOffset="34" />
</Expression>
- Data-binding表達(dá)式
android:text="@{ viewModel.text }"會(huì)被編譯成
<Expression attribute="android:text" text=" viewModel.text ">
<Location endLine="23" endOffset="45" startLine="23" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="23" endOffset="43" startLine="23" startOffset="28" />
</Expression>
注意<Expression attribute="android:text" text=" viewModel.text ">。它正是我們要找的View和ViewModel之間的橋梁。ViewModel中的text變量會(huì)連接到TextView的android:text屬性。這正是我們所期待的。但從這個(gè)布局文件到j(luò)ava代碼仍是一個(gè)謎。接下來看下ActivityMainBinding.java,一探究竟。
ActivityMainBinding.java是ViewDataBinding的一個(gè)子類,開發(fā)者可以setViewModel到布局文件。從我們分析的過程來看它就像xml的java版本。布局文件中的每個(gè)View/ViewGroup標(biāo)簽在這個(gè)類是一個(gè)變量。例如:
xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@{ viewModel.text }"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{ viewModel.isVisible ? View.VISIBLE : View.INVISIBLE }"
/>
</LinearLayout>
將會(huì)生成:
java
public class ActivityMainBinding extends android.databinding.ViewDataBinding {
private static final android.databinding.ViewDataBinding.IncludedLayouts sIncludes;
private static final android.util.SparseIntArray sViewsWithIds;
static {
sIncludes = null;
sViewsWithIds = null;
}
// views
private final android.widget.LinearLayout mboundView0;
private final android.widget.TextView mboundView1;
private final android.widget.Button mboundView2;
...
}
并且如果你在布局文件中為View指定一個(gè)ID,這個(gè)private field將變成 public field,并且你可以直接使用它不用調(diào)用findViewById()。
java
// views
private final android.widget.LinearLayout mboundView0;
private final android.widget.Button mboundView2;
public final android.widget.TextView tvTest;
這用起來很方便!訪問布局文件中的任意View比之前更為容易。變量的名稱和android:id的名稱一致。如果android:id 帶有下劃線,名稱將轉(zhuǎn)換為駝峰式,例如android:id="@+id/tv_test"會(huì)轉(zhuǎn)換為public final android.widget.TextView tvTest;。
<variable>標(biāo)簽定義了java類中的變量,我們通過setViewModel方法設(shè)置的變量。順便說一句,當(dāng)我們討論ViewModel時(shí)我想提到Obsevable模式。Obsevable模式是一個(gè)很關(guān)鍵的東西,無論ViewModel何時(shí)被修改都可以讓View得到更新,并且ViewModel會(huì)自動(dòng)接收一個(gè)新數(shù)據(jù)當(dāng)用戶和View交互時(shí)。這是所有的Obsevable類型覆蓋了所有java數(shù)據(jù)類型。
ObservableArrayList
ObservableArrayMap
ObservableBoolean
ObservableByte
ObservableChar
ObservableDouble
ObservableField
ObservableFloat
ObservableInt
ObservableLong
ObservableParcelable
ObservableShort
這些類的集合很相似也很簡單。全部繼承自BaseObservable,只包含一個(gè)變量,一個(gè)getter和一個(gè)setter。setter會(huì)檢查新值和老值是否不同,如果是新值會(huì)調(diào)用notifyChange(),并且View會(huì)得到更新。但notifyChange()是怎樣影響到View的?
為了回答上面的問題,我會(huì)使用這個(gè)例子盡可能簡單地解釋它:
- 期望: 我們修改ViewModel中的isShowView為false,actiivty_main.xml中的View將不可見。
- 里面發(fā)生了什么: 當(dāng)我們setViewModel到ActivityMainBinding,updateRegistration被調(diào)用用于在ViewModel 和View之間創(chuàng)建一個(gè)“橋”。
我們來看下“橋”到底長什么樣子:
java
/**
* Method object extracted out to attach a listener to a bound Observable object.
*/
private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
@Override
public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();
}
};
....
private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
implements ObservableReference<Observable> {
final WeakListener<Observable> mListener;
public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
mListener = new WeakListener<Observable>(binder, localFieldId, this);
}
@Override
public WeakListener<Observable> getListener() {
return mListener;
}
@Override
public void addListener(Observable target) {
target.addOnPropertyChangedCallback(this);
}
@Override
public void removeListener(Observable target) {
target.removeOnPropertyChangedCallback(this);
}
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
ViewDataBinding binder = mListener.getBinder();
if (binder == null) {
return;
}
Observable obj = mListener.getTarget();
if (obj != sender) {
return; // notification from the wrong object?
}
binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
}
}
...
private static class WeakListener<T> extends WeakReference<ViewDataBinding> {
private final ObservableReference<T> mObservable;
protected final int mLocalFieldId;
private T mTarget;
public WeakListener(ViewDataBinding binder, int localFieldId,
ObservableReference<T> observable) {
super(binder);
mLocalFieldId = localFieldId;
mObservable = observable;
}
public void setTarget(T object) {
unregister();
mTarget = object;
if (mTarget != null) {
mObservable.addListener(mTarget);
}
}
}
-
WeakPropertyListener中的mListener變量持有MainDataBinding的一個(gè)弱引用,WeakListener中的setTarget方法被調(diào)用綁定ViewModel到MainDataBinding。 - 從
ViewModel修改isShowView變量會(huì)調(diào)用mNotifier的notifyCallback():
java
private void notifyCallbacks(T sender, int arg, A arg2, int startIndex, int endIndex, long bits) {
long bitMask = 1L;
for(int i = startIndex; i < endIndex; ++i) {
if((bits & bitMask) == 0L) {
this.mNotifier.onNotifyCallback(this.mCallbacks.get(i), sender, arg, arg2);
}
bitMask <<= 1;
}
}
- 并且
mNotifier也是WeakPropertyListener。因此在ViewModel上修改isShowView將會(huì)通知WeakPropertyListener,這是isShowView影響View讓它不可見的地方。ActivityMainBinding中的executeBindings()拿到值映射到UI上相應(yīng)的View。
java
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
com.example.main.MainViewModel viewModel = mViewModel;
int isVisibleViewModelVI = 0;
boolean isVisibleViewModel = false;
java.lang.String textViewModel = null;
android.databinding.ObservableBoolean isVisibleViewModel1 = null;
android.databinding.ObservableField<java.lang.String> textViewModel1 = null;
if ((dirtyFlags & 0xfL) != 0) {
if ((dirtyFlags & 0xbL) != 0) {
if (viewModel != null) {
// read viewModel.isVisible
isVisibleViewModel1 = viewModel.isVisible;
}
updateRegistration(1, isVisibleViewModel1);
if (isVisibleViewModel1 != null) {
// read viewModel.isVisible.get()
isVisibleViewModel = isVisibleViewModel1.get();
}
if((dirtyFlags & 0xbL) != 0) {
if (isVisibleViewModel) {
dirtyFlags |= 0x20L;
} else {
dirtyFlags |= 0x10L;
}}
// read viewModel.isVisible.get() ? View.VISIBLE : View.INVISIBLE
isVisibleViewModelVI = (isVisibleViewModel) ? (android.view.View.VISIBLE) : (android.view.View.INVISIBLE);
}
if ((dirtyFlags & 0xdL) != 0) {
if (viewModel != null) {
// read viewModel.text
textViewModel1 = viewModel.text;
}
updateRegistration(2, textViewModel1);
if (textViewModel1 != null) {
// read viewModel.text.get()
textViewModel = textViewModel1.get();
}
}
}
// batch finished
if ((dirtyFlags & 0xbL) != 0) {
// api target 1
this.mboundView2.setVisibility(isVisibleViewModelVI);
}
if ((dirtyFlags & 0xdL) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setText(this.tvTest, textViewModel);
}
}
- 我們可以看到上面的代碼,無論ViewModel何時(shí)修改它們的值,它都會(huì)通知
executeBindings()方法,這個(gè)方法依賴于這個(gè)值并且會(huì)在View上執(zhí)行動(dòng)作,例如:
this.mboundView2.setVisibility(isVisibleViewModelVI);
android.databinding.adapters.TextViewBindingAdapter.setText(this.tvTest, textViewModel);
- 這是為啥我們需要寫@BindingAdapter和@BindingMethod用于在View上執(zhí)行一個(gè)動(dòng)作,例如:recyclerView.setAdapter(), viewPager.setOnPageChange(), ...。
- 然而,Android已經(jīng)創(chuàng)建了大量的內(nèi)置BindingAdapters和BindingMethod,因此開發(fā)者大多數(shù)情況下只需要使用它。我們只需要在特殊情況下實(shí)現(xiàn)我們自己的代碼或Android現(xiàn)在還不支持它。這種情況下,
isShowView為false對應(yīng)View.INVISIBLE會(huì)執(zhí)行View.setVisibility(View.INVISIBLE)。That's all。 - 進(jìn)行下一部分之前,我想讓你知道data-binding內(nèi)置的adapters。記得使用這些并且不要重復(fù)發(fā)明輪子。我已經(jīng)看到有很多開發(fā)者濫用@BindingAdapter重寫已存在的東西。真是浪費(fèi)!
Part 2:data-binding編譯器是怎樣生成代碼的?
你有沒有把我上面所給出的官方git倉庫clone下來?Data-Binding Repository
在理解了生成的代碼是怎樣在View和ViewModel之間綁定之后,在這一部分,我們會(huì)找出編譯生成神奇代碼的方法。
注意這兩個(gè)模塊:compiler和compilerCommon。我們看的最多的地方。
- 編譯器的核心為
compiler.android.databinding.annotationprocessor包下的ProcessDataBinding類。這個(gè)類的職責(zé)是一步一步執(zhí)行處理列表。
java
mProcessingSteps = Arrays.asList(
new ProcessMethodAdapters(),
new ProcessExpressions(),
new ProcessBindable()
);
- 我們先看第一個(gè)處理步驟——ProcessMethodAdapters。這個(gè)類提供搜索工程中所有的類,哪一個(gè)類哪一個(gè)方法添加了下面的注解:
@BindingAdapter, @Untaggable, @BindingMethods, @BindingConversion, @InverseBindingAdapter, @InverseBindingMethods。并且把它們保存在SetterStore,后面應(yīng)該在executeBinding用到正如我們上面所說。在編譯期間,注解處理器拿到的這些信息會(huì)被存放在setter_store.bin文件中。
java
@Override
public boolean onHandleStep(RoundEnvironment roundEnv,
ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
L.d("processing adapters");
final ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
Preconditions.checkNotNull(modelAnalyzer, "Model analyzer should be"
+ " initialized first");
SetterStore store = SetterStore.get(modelAnalyzer);
clearIncrementalClasses(roundEnv, store);
addBindingAdapters(roundEnv, processingEnvironment, store);
addRenamed(roundEnv, store);
addConversions(roundEnv, store);
addUntaggable(roundEnv, store);
addInverseAdapters(roundEnv, processingEnvironment, store);
addInverseMethods(roundEnv, store);
try {
store.write(buildInfo.modulePackage(), processingEnvironment);
} catch (IOException e) {
L.e(e, "Could not write BindingAdapter intermediate file.");
}
return true;
}
...
public void write(String projectPackage, ProcessingEnvironment processingEnvironment)
throws IOException {
GenerationalClassUtil.writeIntermediateFile(processingEnvironment,
projectPackage, projectPackage +
GenerationalClassUtil.ExtensionFilter.SETTER_STORE.getExtension(), mStore);
}
...
public enum ExtensionFilter {
BR("-br.bin"),
LAYOUT("-layoutinfo.bin"),
SETTER_STORE("-setter_store.bin");
private final String mExtension;
ExtensionFilter(String extension) {
mExtension = extension;
}
public boolean accept(String entryName) {
return entryName.endsWith(mExtension);
}
public String getExtension() {
return mExtension;
}
}
- 第二步是ProcessExpressions處理表達(dá)式。在這一步中會(huì)搜索工程中所有xml文件并且會(huì)轉(zhuǎn)換最外層為
<layout></layout>標(biāo)簽支持data-binding的xml文件。會(huì)把這個(gè)文件拆分為2個(gè)文件正如第一部分所提到的:activity_main.xml(正常的布局文件)和activity_main-layout.xml(包含綁定信息)。LayoutBinder是最有意思的類,它使用(XmlParser中)layoutBundle在activity_main-layout.xml中計(jì)算表達(dá)式,位置和目標(biāo)。
java
@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name="Layout")
public static class LayoutFileBundle implements Serializable, FileScopeProvider {
@XmlAttribute(name="layout", required = true)
public String mFileName;
@XmlAttribute(name="modulePackage", required = true)
public String mModulePackage;
@XmlAttribute(name="absoluteFilePath", required = true)
public String mAbsoluteFilePath;
private String mConfigName;
// The binding class as given by the user
@XmlAttribute(name="bindingClass", required = false)
public String mBindingClass;
// The location of the name of the generated class, optional
@XmlElement(name = "ClassNameLocation", required = false)
private Location mClassNameLocation;
// The full package and class name as determined from mBindingClass and mModulePackage
private String mFullBindingClass;
// The simple binding class name as determined from mBindingClass and mModulePackage
private String mBindingClassName;
// The package of the binding class as determined from mBindingClass and mModulePackage
private String mBindingPackage;
@XmlAttribute(name="directory", required = true)
public String mDirectory;
public boolean mHasVariations;
@XmlElement(name="Variables")
public List<VariableDeclaration> mVariables = new ArrayList<VariableDeclaration>();
@XmlElement(name="Imports")
public List<NameTypeLocation> mImports = new ArrayList<NameTypeLocation>();
@XmlElementWrapper(name="Targets")
@XmlElement(name="Target")
public List<BindingTargetBundle> mBindingTargetBundles = new ArrayList<BindingTargetBundle>();
@XmlAttribute(name="isMerge", required = true)
private boolean mIsMerge;
...
}
java
public LayoutBinder(ResourceBundle.LayoutFileBundle layoutBundle) {
try {
Scope.enter(this);
mExprModel = new ExprModel();
mExpressionParser = new ExpressionParser(mExprModel);
mBindingTargets = new ArrayList<BindingTarget>();
mBundle = layoutBundle;
mModulePackage = layoutBundle.getModulePackage();
HashSet<String> names = new HashSet<String>();
// copy over data.
for (ResourceBundle.VariableDeclaration variable : mBundle.getVariables()) {
addVariable(variable.name, variable.type, variable.location, variable.declared);
names.add(variable.name);
}
for (ResourceBundle.NameTypeLocation userImport : mBundle.getImports()) {
mExprModel.addImport(userImport.name, userImport.type, userImport.location);
names.add(userImport.name);
}
if (!names.contains("context")) {
mExprModel.builtInVariable("context", "android.content.Context",
"getRoot().getContext()");
names.add("context");
}
for (String javaLangClass : sJavaLangClasses) {
mExprModel.addImport(javaLangClass, "java.lang." + javaLangClass, null);
}
// First resolve all the View fields
// Ensure there are no conflicts with variable names
for (BindingTargetBundle targetBundle : mBundle.getBindingTargetBundles()) {
try {
Scope.enter(targetBundle);
final BindingTarget bindingTarget = createBindingTarget(targetBundle);
if (bindingTarget.getId() != null) {
final String fieldName = LayoutBinderWriterKt.
getReadableName(bindingTarget);
if (names.contains(fieldName)) {
L.w("View field %s collides with a variable or import", fieldName);
} else {
names.add(fieldName);
mExprModel.viewFieldExpr(bindingTarget);
}
}
} finally {
Scope.exit();
}
}
for (BindingTarget bindingTarget : mBindingTargets) {
try {
Scope.enter(bindingTarget.mBundle);
for (BindingTargetBundle.BindingBundle bindingBundle : bindingTarget.mBundle
.getBindingBundleList()) {
try {
Scope.enter(bindingBundle.getValueLocation());
bindingTarget.addBinding(bindingBundle.getName(),
parse(bindingBundle.getExpr(), bindingBundle.isTwoWay(),
bindingBundle.getValueLocation()));
} finally {
Scope.exit();
}
}
bindingTarget.resolveTwoWayExpressions();
bindingTarget.resolveMultiSetters();
bindingTarget.resolveListeners();
} finally {
Scope.exit();
}
}
mSortedBindingTargets = new ArrayList<BindingTarget>(mBindingTargets);
Collections.sort(mSortedBindingTargets, COMPARE_FIELD_NAME);
} finally {
Scope.exit();
}
}
- 第三步是ProcessBindable。這個(gè)處理生成BR類,綁定屬性的id,例如:BR.text, BR.item, BR.isShowView, ...
- 最后,你可能想知道最重要的類MainDataBinding是在哪里創(chuàng)建的。我不知道為啥Google使用Kotlin編寫的,相關(guān)文件為DataBinderWriter.kt 和 LayoutBinderWriter.kt。你可以自己去看這些文件。
總結(jié)
我希望你能讀到這里,因?yàn)檫@篇文章有難點(diǎn)我們讀起來很難理解。Data-Binding用起來不是很容易甚至很難理解。但我認(rèn)為當(dāng)我們真正理解后面的代碼,沒有什么是秘密,因?yàn)槲覀冎纃ata-binding的原理。在一些工程上使用data-binding之后,我看到一些data-binding相關(guān)的bug很難跟蹤和解決,開發(fā)者在它上面花費(fèi)了很多時(shí)間。通過這篇文章,我們不再害怕深入到生成的代碼查找bug原因。了解了編譯器的知識可以幫助我們用最好的方式去寫代碼。
在下面留下評論,告訴我哪一塊是你不理解的,我會(huì)盡力更新,讓這篇文章更有用。Thanks for your time!