本文首发: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文档,会帮助你轻松集成,有大量的示例代码,当你决定应用它到你的工程你可以实现很多很酷的东西。体验之后,你可能会好奇它是如何实现的,Google是怎样让传统的xml文件和data binding混合的,xml文件是怎样和java代码交互的,编译器是怎样处理的。本文主题旨在揭露data binding的机制,深入底层看看到底发生了什么。因此,开发者可以深入理解帮助他们正确地使用data binding,利用data binding的强大构建完美的应用。
近来,data binding是android很火的趋势,让开发者敲代码时更轻松。由于它的强大大量的开发者已经开始使用data binding。但说实话,它也带给我们一些麻烦。虽然data binding的概念很简单:“Yay!定义一个ViewModel类在 layout.xml 文件中引入并且不需要关注任何UI的东西,我们的数据会直接绑定到UI上用一种神奇的方式”。真的很快,很简单。当出现一些错误,当你的数据突然不能绑定到UI上,当编译器报出大量的错误信息,你真的不想知道这是什么意思么。我们能做些什么!
我不得不去面对这些data binding的问题并且使用不同的办法去解决它。并且我认为,只有通过查看data binding的源码,理解它工作的原理,我就不用再去处理这些问题了。让我们从Google clone下这个仓库一起阅读下它的代码Data-Binding Repository。
Part 1:Data Binding 流,Obsevable模式机制和data-binding生成代码的意思
为了能更好的理解代码,我创建了一个简单的使用data-binding的示例。
1.创建一个布局文件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.创建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.创建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>
标签表示和正常布局文件之间的区别。如果一个正常的布局文件是直接用于android应用,放置在apk包中的res/layout文件夹中,那布局文件中最外层的<layout></layout>
标签则是被间接使用。编译器通过搜索应用的layout文件夹编译所有最外层为<layout></layout>
标签的布局文件为activity_main.xml(正常的布局文件)的简洁版本。这个版本的xml看起来就像我们没有使用data-binding的正常的布局文件。
因此,基本上,使用data-binding的布局文件就是正常布局文件的一个特殊的版本,data-binding给我们一些词汇和语法,通过这种方式强制它们重写正常的布局文件。Xml文件不能被Android框架理解,它只能被data-binding编译器理解,这让编译器知道什么地方从ViewModel有怎样的数据映射到View上。最后编译器把xml文件转换成可以打包到apk中的正常的布局文件,以及activity_main-layout.xml 和 ActivityMainBinding.java。
我们可以想象原始的布局文件包含两部分:正常部分和绑定部分。正常部分就像我们没有使用data-binding时写的布局文件。绑定部分用于帮助编译器生成java代码,它是一个在UI和数据之间很好的桥梁。
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才会出现在这里。<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的一个子类,开发者可以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,并且你可以直接使用它不用调用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 带有下划线,名称将转换为驼峰式,例如android:id="@+id/tv_test"
会转换为public final android.widget.TextView tvTest;
。
<variable>
标签定义了java类中的变量,我们通过setViewModel方法设置的变量。顺便说一句,当我们讨论ViewModel时我想提到Obsevable模式。Obsevable模式是一个很关键的东西,无论ViewModel何时被修改都可以让View得到更新,并且ViewModel会自动接收一个新数据当用户和View交互时。这是所有的Obsevable类型覆盖了所有java数据类型。
ObservableArrayList
ObservableArrayMap
ObservableBoolean
ObservableByte
ObservableChar
ObservableDouble
ObservableField
ObservableFloat
ObservableInt
ObservableLong
ObservableParcelable
ObservableShort
这些类的集合很相似也很简单。全部继承自BaseObservable,只包含一个变量,一个getter和一个setter。setter会检查新值和老值是否不同,如果是新值会调用notifyChange(),并且View会得到更新。但notifyChange()是怎样影响到View的?
为了回答上面的问题,我会使用这个例子尽可能简单地解释它:
- 期望: 我们修改ViewModel中的isShowView为false,actiivty_main.xml中的View将不可见。
- 里面发生了什么: 当我们setViewModel到ActivityMainBinding,updateRegistration被调用用于在ViewModel 和View之间创建一个“桥”。
我们来看下“桥”到底长什么样子:
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
方法被调用绑定ViewModel
到MainDataBinding
。 - 从
ViewModel
修改isShowView
变量会调用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上执行动作,例如:
this.mboundView2.setVisibility(isVisibleViewModelVI);
android.databinding.adapters.TextViewBindingAdapter.setText(this.tvTest, textViewModel);
- 这是为啥我们需要写@BindingAdapter和@BindingMethod用于在View上执行一个动作,例如:recyclerView.setAdapter(), viewPager.setOnPageChange(), ...。
- 然而,Android已经创建了大量的内置BindingAdapters和BindingMethod,因此开发者大多数情况下只需要使用它。我们只需要在特殊情况下实现我们自己的代码或Android现在还不支持它。这种情况下,
isShowView
为false对应View.INVISIBLE会执行View.setVisibility(View.INVISIBLE)。That's all。 - 进行下一部分之前,我想让你知道data-binding内置的adapters。记得使用这些并且不要重复发明轮子。我已经看到有很多开发者滥用@BindingAdapter重写已存在的东西。真是浪费!
Part 2:data-binding编译器是怎样生成代码的?
你有没有把我上面所给出的官方git仓库clone下来?Data-Binding Repository
在理解了生成的代码是怎样在View和ViewModel之间绑定之后,在这一部分,我们会找出编译生成神奇代码的方法。
注意这两个模块:compiler
和compilerCommon
。我们看的最多的地方。
- 编译器的核心为
compiler.android.databinding.annotationprocessor
包下的ProcessDataBinding
类。这个类的职责是一步一步执行处理列表。
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文件并且会转换最外层为
<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是在哪里创建的。我不知道为啥Google使用Kotlin编写的,相关文件为DataBinderWriter.kt 和 LayoutBinderWriter.kt。你可以自己去看这些文件。
总结
我希望你能读到这里,因为这篇文章有难点我们读起来很难理解。Data-Binding用起来不是很容易甚至很难理解。但我认为当我们真正理解后面的代码,没有什么是秘密,因为我们知道data-binding的原理。在一些工程上使用data-binding之后,我看到一些data-binding相关的bug很难跟踪和解决,开发者在它上面花费了很多时间。通过这篇文章,我们不再害怕深入到生成的代码查找bug原因。了解了编译器的知识可以帮助我们用最好的方式去写代码。
在下面留下评论,告诉我哪一块是你不理解的,我会尽力更新,让这篇文章更有用。Thanks for your time!
网友评论