本文首發(fā):http://yuweiguocn.github.io/
本文介紹了Data Binding的原理硝逢。
關于Data Binding的使用請查看Data Binding炼七。
原文鏈接:Understanding Data-Binding's generated code and How does Android Data-Binding compiler work
這篇文章并不是介紹怎樣使用Data Binding或了解基本概念引润。建議你直接查看Google文檔酒甸,會幫助你輕松集成,有大量的示例代碼,當你決定應用它到你的工程你可以實現(xiàn)很多很酷的東西。體驗之后嗽冒,你可能會好奇它是如何實現(xiàn)的,Google是怎樣讓傳統(tǒng)的xml文件和data binding混合的补履,xml文件是怎樣和java代碼交互的添坊,編譯器是怎樣處理的。本文主題旨在揭露data binding的機制箫锤,深入底層看看到底發(fā)生了什么贬蛙。因此,開發(fā)者可以深入理解幫助他們正確地使用data binding麻汰,利用data binding的強大構(gòu)建完美的應用速客。
近來,data binding是android很火的趨勢五鲫,讓開發(fā)者敲代碼時更輕松。由于它的強大大量的開發(fā)者已經(jīng)開始使用data binding岔擂。但說實話位喂,它也帶給我們一些麻煩。雖然data binding的概念很簡單:“Yay乱灵!定義一個ViewModel類在 layout.xml 文件中引入并且不需要關注任何UI的東西塑崖,我們的數(shù)據(jù)會直接綁定到UI上用一種神奇的方式”。真的很快痛倚,很簡單规婆。當出現(xiàn)一些錯誤,當你的數(shù)據(jù)突然不能綁定到UI上蝉稳,當編譯器報出大量的錯誤信息抒蚜,你真的不想知道這是什么意思么。我們能做些什么耘戚!
我不得不去面對這些data binding的問題并且使用不同的辦法去解決它嗡髓。并且我認為,只有通過查看data binding的源碼收津,理解它工作的原理饿这,我就不用再去處理這些問題了浊伙。讓我們從Google clone下這個倉庫一起閱讀下它的代碼Data-Binding Repository。
Part 1:Data Binding 流长捧,Obsevable模式機制和data-binding生成代碼的意思
為了能更好的理解代碼嚣鄙,我創(chuàng)建了一個簡單的使用data-binding的示例。
1.創(chuàng)建一個布局文件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個額外的文件:activity_main-layout.xml
,activity_main.xml
的簡潔版本和ActivityMainBinding.java
声功。至此烦却,我們對xml布局生成的代碼有了一個初步的了解。
基本上先巴,一個xml布局文件最外層的<layout></layout>
標簽表示和正常布局文件之間的區(qū)別其爵。如果一個正常的布局文件是直接用于android應用,放置在apk包中的res/layout文件夾中伸蚯,那布局文件中最外層的<layout></layout>
標簽則是被間接使用摩渺。編譯器通過搜索應用的layout文件夾編譯所有最外層為<layout></layout>
標簽的布局文件為activity_main.xml(正常的布局文件)的簡潔版本。這個版本的xml看起來就像我們沒有使用data-binding的正常的布局文件剂邮。
因此摇幻,基本上,使用data-binding的布局文件就是正常布局文件的一個特殊的版本挥萌,data-binding給我們一些詞匯和語法绰姻,通過這種方式強制它們重寫正常的布局文件。Xml文件不能被Android框架理解引瀑,它只能被data-binding編譯器理解狂芋,這讓編譯器知道什么地方從ViewModel有怎樣的數(shù)據(jù)映射到View上。最后編譯器把xml文件轉(zhuǎn)換成可以打包到apk中的正常的布局文件憨栽,以及activity_main-layout.xml 和 ActivityMainBinding.java帜矾。
我們可以想象原始的布局文件包含兩部分:正常部分和綁定部分。正常部分就像我們沒有使用data-binding時寫的布局文件屑柔。綁定部分用于幫助編譯器生成java代碼屡萤,它是一個在UI和數(shù)據(jù)之間很好的橋梁。
activity_main-layout.xml (在data-binding-info文件夾中)包含綁定部分锯蛀。就像它的名字灭衷,這個文件是布局的綁定信息。我們再來回顧下這個文件旁涤。外層仍是<Layout>
標簽翔曲,但和之前的不一樣迫像。
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"屬性表示這個文件是acitivity_main.xml的綁定部分,當前存放在"/home/framgia/android/app/src/main/res/layout/activity_main.xml"瞳遍。
在<Layout>
標簽的里面闻妓,毫無疑問,有<Variables>
標簽和<Imports>
標簽掠械。因為我們前面我們用到setViewModel由缆,并且在布局文件中引用了一些類。
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信息猾蒂。我不知道為什么編譯器需要存儲Variable/Import的位置信息均唉,可能用于追溯或其它什么作用。
接下來是這個文件中最重要的部分肚菠,<Target>
標簽告訴ViewModel應該映射到哪個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>
我們有一個<Target>
標簽的列表层扶。在布局文件中我們可以有多個View/ViewGroup,但只有包含data-binding表達式的View/ViewGroup才會出現(xiàn)在這里烙荷。<Target>
標簽里面的<Expressions>
標簽表示View的data-binding表達式镜会。例如:
- Data-binding表達式
android:visibility="@{ viewModel.isVisible ? View.VISIBLE : View.INVISIBLE }"
會被編譯成
<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表達式
android:text="@{ viewModel.text }"
會被編譯成
<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變量會連接到TextView的android:text屬性戳表。這正是我們所期待的。但從這個布局文件到java代碼仍是一個謎昼伴。接下來看下ActivityMainBinding.java扒袖,一探究竟。
ActivityMainBinding.java是ViewDataBinding的一個子類亩码,開發(fā)者可以setViewModel到布局文件。從我們分析的過程來看它就像xml的java版本野瘦。布局文件中的每個View/ViewGroup標簽在這個類是一個變量描沟。例如:
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>
將會生成:
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指定一個ID,這個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"
會轉(zhuǎn)換為public final android.widget.TextView tvTest;
佩伤。
<variable>
標簽定義了java類中的變量聊倔,我們通過setViewModel方法設置的變量。順便說一句生巡,當我們討論ViewModel時我想提到Obsevable模式耙蔑。Obsevable模式是一個很關鍵的東西,無論ViewModel何時被修改都可以讓View得到更新孤荣,并且ViewModel會自動接收一個新數(shù)據(jù)當用戶和View交互時甸陌。這是所有的Obsevable類型覆蓋了所有java數(shù)據(jù)類型。
ObservableArrayList
ObservableArrayMap
ObservableBoolean
ObservableByte
ObservableChar
ObservableDouble
ObservableField
ObservableFloat
ObservableInt
ObservableLong
ObservableParcelable
ObservableShort
這些類的集合很相似也很簡單盐股。全部繼承自BaseObservable钱豁,只包含一個變量,一個getter和一個setter疯汁。setter會檢查新值和老值是否不同牲尺,如果是新值會調(diào)用notifyChange(),并且View會得到更新涛目。但notifyChange()是怎樣影響到View的秸谢?
為了回答上面的問題,我會使用這個例子盡可能簡單地解釋它:
- 期望: 我們修改ViewModel中的isShowView為false霹肝,actiivty_main.xml中的View將不可見估蹄。
- 里面發(fā)生了什么: 當我們setViewModel到ActivityMainBinding,updateRegistration被調(diào)用用于在ViewModel 和View之間創(chuàng)建一個“橋”沫换。
我們來看下“橋”到底長什么樣子:
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
的一個弱引用臭蚁,WeakListener
中的setTarget
方法被調(diào)用綁定ViewModel
到MainDataBinding
。 - 從
ViewModel
修改isShowView
變量會調(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
將會通知WeakPropertyListener
垮兑,這是isShowView
影響View讓它不可見的地方。ActivityMainBinding
中的executeBindings()
拿到值映射到UI上相應的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何時修改它們的值,它都會通知
executeBindings()
方法磕谅,這個方法依賴于這個值并且會在View上執(zhí)行動作私爷,例如:
this.mboundView2.setVisibility(isVisibleViewModelVI);
android.databinding.adapters.TextViewBindingAdapter.setText(this.tvTest, textViewModel);
- 這是為啥我們需要寫@BindingAdapter和@BindingMethod用于在View上執(zhí)行一個動作,例如:recyclerView.setAdapter(), viewPager.setOnPageChange(), ...膊夹。
- 然而衬浑,Android已經(jīng)創(chuàng)建了大量的內(nèi)置BindingAdapters和BindingMethod,因此開發(fā)者大多數(shù)情況下只需要使用它放刨。我們只需要在特殊情況下實現(xiàn)我們自己的代碼或Android現(xiàn)在還不支持它工秩。這種情況下,
isShowView
為false對應View.INVISIBLE會執(zhí)行View.setVisibility(View.INVISIBLE)。That's all助币。 - 進行下一部分之前浪听,我想讓你知道data-binding內(nèi)置的adapters。記得使用這些并且不要重復發(fā)明輪子奠支。我已經(jīng)看到有很多開發(fā)者濫用@BindingAdapter重寫已存在的東西馋辈。真是浪費!
Part 2:data-binding編譯器是怎樣生成代碼的倍谜?
你有沒有把我上面所給出的官方git倉庫clone下來迈螟?Data-Binding Repository
在理解了生成的代碼是怎樣在View和ViewModel之間綁定之后,在這一部分尔崔,我們會找出編譯生成神奇代碼的方法答毫。
注意這兩個模塊:compiler
和compilerCommon
。我們看的最多的地方季春。
- 編譯器的核心為
compiler.android.databinding.annotationprocessor
包下的ProcessDataBinding
類洗搂。這個類的職責是一步一步執(zhí)行處理列表。
java
mProcessingSteps = Arrays.asList(
new ProcessMethodAdapters(),
new ProcessExpressions(),
new ProcessBindable()
);
- 我們先看第一個處理步驟——ProcessMethodAdapters载弄。這個類提供搜索工程中所有的類耘拇,哪一個類哪一個方法添加了下面的注解:
@BindingAdapter, @Untaggable, @BindingMethods, @BindingConversion, @InverseBindingAdapter, @InverseBindingMethods
。并且把它們保存在SetterStore宇攻,后面應該在executeBinding用到正如我們上面所說惫叛。在編譯期間,注解處理器拿到的這些信息會被存放在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處理表達式嘉涌。在這一步中會搜索工程中所有xml文件并且會轉(zhuǎn)換最外層為
<layout></layout>
標簽支持data-binding的xml文件茧吊。會把這個文件拆分為2個文件正如第一部分所提到的:activity_main.xml(正常的布局文件)和activity_main-layout.xml(包含綁定信息)廉羔。LayoutBinder
是最有意思的類萨西,它使用(XmlParser中)layoutBundle在activity_main-layout.xml
中計算表達式康聂,位置和目標。
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乳附。這個處理生成BR類乌妒,綁定屬性的id典徘,例如:BR.text, BR.item, BR.isShowView, ...
- 最后坯钦,你可能想知道最重要的類MainDataBinding是在哪里創(chuàng)建的法严。我不知道為啥Google使用Kotlin編寫的,相關文件為DataBinderWriter.kt 和 LayoutBinderWriter.kt葫笼。你可以自己去看這些文件。
總結(jié)
我希望你能讀到這里拗馒,因為這篇文章有難點我們讀起來很難理解路星。Data-Binding用起來不是很容易甚至很難理解。但我認為當我們真正理解后面的代碼,沒有什么是秘密洋丐,因為我們知道data-binding的原理呈昔。在一些工程上使用data-binding之后,我看到一些data-binding相關的bug很難跟蹤和解決友绝,開發(fā)者在它上面花費了很多時間堤尾。通過這篇文章,我們不再害怕深入到生成的代碼查找bug原因迁客。了解了編譯器的知識可以幫助我們用最好的方式去寫代碼郭宝。
在下面留下評論,告訴我哪一塊是你不理解的掷漱,我會盡力更新粘室,讓這篇文章更有用。Thanks for your time!