DataBinding源码浅析

本文假设读者对MVVM模式以及DataBinding的使用有基本的了解,在此基础上再深入讨论DataBinding在MVVM模式中承担的功能,以及其实现的原理。

DataBinding与MVVM

这一节主要讨论DataBinding在MVVM中起到的作用,先来看下MVVM模式的基本结构图:

image-20181112221854298

注意到View与ViewModel有一个Binding的关系。Binding的这一层关系使得我们的ViewModel与View可以实现双向通信。如果不借助DataBinding框架,我们自己用framework原生的能力的话,容易想到的办法就是在ViewModel中将View与ViewModel绑定,毕竟View主要是由xml来实现的,能力有限。

而DataBinding框架就是用来解决这一问题的,它可以将绑定的这部分代码从ViewModel中剥离出来,迁移到xml文件中,使ViewModel可以专注于业务逻辑。

除此之外,如果你实现过不基于Databinding的MVVM,那可能会遇到过这样的问题,对于数据分解映射多个View(即一个对象的多个属性分别独立对应多个View)的情况难以处理,而Databinding则可以优雅的解决这一问题。

DataBinding基本功能模块与整体架构

功能模块
  • compiler:APT模块,负责xml解析以及根据解析出的信息结合注解生成所需要的辅助文件。
  • baseLibrary:向外提供DataBinding可用的注解
  • library:提供可被观察的model
  • adapters:封装view的属性改变方法,隔离VM与实际的View
整体架构

image-20190223110527681

XML解析与APT

这一节主要介绍Databinding通过xml解析与APT的技术提取了哪些信息,并用这些信息生成了哪些文件,以及这些生成文件各自的作用。(*表示通配符)

*-layout.xml

文件地址:app/build/intermediates/data-binding/debug/layout-info/activity_data_binding-layout.xml

在看这个文件的内容之前,先看看我们编写的activity_data_binding.xml在编译前后的有什么不一样。

编译前:

image-20181227201603191

编译后(反编译apk得到):

image-20181227201724640

可以发现编译后的xml为原始xml的第二部分,需要注意的是这里所有的View都添加了属性android:tag。那第一部分的信息哪里去了呢?很自然的,可以知道,这部分信息被解析出来后存储到了最前面提到的*-layout.xml中,主要内容如下

image-20181227210523668这里的部分1与上图的1是一一对应的,除此之外,部分2也记录了View的tag信息,也就是说,我们声明的数据与View的绑定关系在这个生成文件中依然保存着。

接着看这几个标签,Variables与Imports和布局文件中的variable与import标签对应,Targets标签则告诉VM变化时对应的V(通过tag信息)、绑定关系是单向还是双向的以及DataBinding的表达式Expressions。

至于这个生成文件的具体解析生成过程可以参考compiler模块的这几个类 LayoutFileParser.javaLayoutXmlProcessor.javaResourceBundle.java

在完成这个xml的生成之后,之后会利用APT根据*-layout.xml中保存的映射信息进一步生成下面要介绍的几个类,具体的实现过程参考源码compiler包中的代码。

BR.java

BR文件为每个model唯一分配了一个id,功能与R文件类似。

DataBinderMapperImpl.java

保存了上文提到的view tag与layout id的映射,也保存了布局文件和与之对应的ViewDataBinding的映射关系。

ActivityDataBindingBinding.java、ActivityDataBindingBindingImpl.java

真正建立与实现View与数据(双向)绑定的地方。具体的细节参考下一节的内容。

View与Model的绑定

Model

model可以是最简单的实体类。也可以是继承自Observable的,具有消息通知功能的被观察者类。

ViewModel

ViewModel在该框架中的实现,就是所有ViewDataBinding的具体实现,例如上文提到的ActivityDataBindingBindingImpl。当然我们也可以实现自己的ViewModel类,然后与ViewDataBinding组合。

view的绑定

绑定这一步的工作是在VM中建立View与Model之间的联系,使得Model与View能相互通信。

简单来说就是让VM持有View(可以更新View),并具有监听View变化的能力。

以Activity为例的话,第一次绑定的入口是DataBindingUtil.setContentView()。该方法最终会调用到DataBindingUtil.bind()。看一下这个方法的具体实现(简化代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@SuppressWarnings("unchecked")
public static <T extends ViewDataBinding> T bind(@NonNull View root, DataBindingComponent bindingComponent) {
// ViewDataBinding以tag的形式保存在view中,因此如果已经绑定过,则可以直接从view中取出
T binding = getBinding(root);
if (binding != null) {
return binding;
}
Object tagObj = root.getTag();
// 上一节提到过自动生成的代码为View设置了tag,并且tag值与布局文件之间的映射信息保存在了
// DataBinderMapperImpl 也就是 sMapper 中
String tag = (String) tagObj;
int layoutId = sMapper.getLayoutId(tag);
// 同时DataBinderMapperImpl中也存有layoutId与其VM(ViewDataBinding)的映射
return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
}

继续跟进看sMapper.getDataBinder()方法的实现(简化代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
if(localizedLayoutId > 0) {
final Object tag = view.getTag();
switch(localizedLayoutId) {
case LAYOUT_ACTIVITYDATABINDING: {
if ("layout/activity_data_binding_0".equals(tag)) {
// 可以发现每次都会new新的VM,也即View的每次初始化都会重新生成新的VM与之绑定
return new ActivityDataBindingBindingImpl(component, view);
}
}
}
}
return null;
}

继续跟进代码可以发现,最关键的实现在于ViewDataBinding.mapBindings()方法,该方法的作用是递归遍历View树,将真实的需要被绑定的View实例都取出来,然后再赋值给VM。

完成VM对View的持有之外,完全完成VM与View的绑定还需要VM监听View的状态变化,这部分代码在ActivityDataBindingBindingImpl.executeBindings()中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ActivityDataBindingBindingImpl.class
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
String userInfoName = null;
User userInfo = mUserInfo;

// ...

if ((dirtyFlags & 0x2L) != 0) {
// 添加对View状态的监听,完成VM对View状态的监听
TextViewBindingAdapter.setTextWatcher(this.mboundView1, null, null, null, mboundView1androidTextAttrChanged);
}
}
model的绑定

这个过程简单来说就是让VM持有Model(可以操作Model),并具有监听Model变化的能力。

以上面的例子为例,模型userInfo的初始化绑定,通过第一次调用ActivityDataBindingBindingImpl.setUserInfo()开始:

1
2
3
4
5
6
7
8
9
10
11
// ActivityDataBindingBindingImpl.class
public void setUserInfo(@Nullable User UserInfo) {
updateRegistration(0, UserInfo);
// 使VM 持有 Model
this.mUserInfo = UserInfo;
synchronized(this) {
mDirtyFlags |= 0x1L;
}
notifyPropertyChanged(BR.userInfo);
super.requestRebind();
}

核心的绑定过程在updateRegistration()方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ViewDataBinding.class
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;
}

接着看registerTo()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ViewDataBinding.class
protected void registerTo(int localFieldId, Object observable,
CreateWeakListener listenerCreator) {
if (observable == null) {
return;
}
WeakListener listener = mLocalFieldObservers[localFieldId];
if (listener == null) {
listener = listenerCreator.create(this, localFieldId);
// 保存listener
mLocalFieldObservers[localFieldId] = listener;
if (mLifecycleOwner != null) {
listener.setLifecycleOwner(mLifecycleOwner);
}
}
// 这一步完成observable与listener的绑定
listener.setTarget(observable);
}

listener.setTarget(observable);以及后面的方法不再给出,总之经过一系列调用后,会形成observable->listener->ViewDataBinding的持有链。换言之,observable可以通过该链路将变化通知到ViewDataBinding,并最终通知到View。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
protected void requestRebind() {
// 省略了通过标记位控制冗余绑定,以及在Lifecycle未到达STARTED前不绑定的逻辑
if (USE_CHOREOGRAPHER) {
mChoreographer.postFrameCallback(mFrameCallback);
} else {
mUIThreadHandler.post(mRebindRunnable);
}
}

// 接着会走到mRebindRunnable->executeBindingsInternal()中
private void executeBindingsInternal() {
// 省略部分逻辑
mIsExecutingPendingBindings = true;
mRebindHalted = false;
if (mRebindCallbacks != null) {
mRebindCallbacks.notifyCallbacks(this, REBIND, null);
// The onRebindListeners will change mPendingHalted
if (mRebindHalted) {
mRebindCallbacks.notifyCallbacks(this, HALTED, null);
}
}
if (!mRebindHalted) {
executeBindings();
if (mRebindCallbacks != null) {
mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
}
}
mIsExecutingPendingBindings = false;
}

最终真正实现绑定的地方是ActivityDataBindingBindingImpl.executeBindings()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
String userInfoName = null;
User userInfo = mUserInfo;

if ((dirtyFlags & 0x3L) != 0) {
if (userInfo != null) {
// read userInfo.name
userInfoName = userInfo.getName();
}
}
// batch finished
if ((dirtyFlags & 0x3L) != 0) {
// api target 1
TextViewBindingAdapter.setText(this.mboundView1, userInfoName);
TextViewBindingAdapter.setText(this.mboundView2, userInfoName);
}
if ((dirtyFlags & 0x2L) != 0) {
// api target 1
// 添加对View状态的监听,完成VM对View状态的监听
TextViewBindingAdapter.setTextWatcher(this.mboundView1, (TextViewBindingAdapter.BeforeTextChanged)null, (TextViewBindingAdapter.OnTextChanged)null, (TextViewBindingAdapter.AfterTextChanged)null, mboundView1androidTextAttrChanged);
}
}

消息通知

这一节介绍绑定之后双向的消息发送的实现细节。

model变化通知到view

以改变上文提到的userInfo为例,调用入口为ActivityDataBindingBindingImpl.setUserInfo()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void setUserInfo(@Nullable com.xybean.mvpdemo.databinding.User UserInfo) {
this.mUserInfo = UserInfo;
synchronized(this) {
// 该值就等于BR文件中指定的userInfo对应的id值
mDirtyFlags |= 0x1L;
}
notifyPropertyChanged(BR.userInfo);
super.requestRebind();
}

// BR文件
public class BR {
public static final int _all = 0;
public static final int userInfo = 1;
}

super.requestRebind()最终也会调用到ActivityDataBindingBindingImpl.executeBindings(),其过程与第一次数据绑定时几乎一样,区别在于不会执行以下逻辑

1
2
3
if ((dirtyFlags & 0x2L) != 0) {
TextViewBindingAdapter.setTextWatcher(mboundView1androidTextAttrChanged);
}

显然这是监听View变化通知Model的监听器,因此只要在初始化的时候注册一次即可,具体的细节下一节再讨论。

view通知model变化

这里需要关注的类是android.databinding.InverseBindingListener,从名字上也可以看出来它的作用是反向绑定,即view通过这个监听器将变化发送到model。

同样以前面的UserInfo为例子,当我们改变了与之绑定的TextView的值的时候,由于TextView事先被注册了TextWatcher,具体见setTextWatcher.setTextWatcher(),而这个监听器最终会将Text的变化信息传递到InverseBindingListener中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ActivityDataBindingBindingImpl.class
private InverseBindingListener mboundView1androidTextAttrChanged = new InverseBindingListener() {
@Override
public void onChange() {
// Inverse of userInfo.name
// is userInfo.setName((java.lang.String) callbackArg_0)
String callbackArg_0 = TextViewBindingAdapter.getTextString(mboundView1);
// localize variables for thread safety
// userInfo != null
boolean userInfoJavaLangObjectNull = false;
// userInfo.name
String userInfoName = null;
// userInfo
User userInfo = mUserInfo;

userInfoJavaLangObjectNull = (userInfo) != (null);
if (userInfoJavaLangObjectNull) {
// 在这里改变了model的值
userInfo.setName(((java.lang.String) (callbackArg_0)));
}
}
};

同时也注意到,上面view改变后,只通知了数据实例改变值,却没有进一步通知其他绑定了该数据的view也同时刷新视图,对于这种情况,下一节展开讨论。

model变化自动驱动view改变

上面实现的绑定,想要model的改变通知到view,需要用户手动调用通知方法。如果想要完成自动驱动UI改变,则需要使用Observable系列的类封装model,以及使用@Bindable注解指示需要被监听的字段。

首先被@Bindable注解标记的字段会在BR文件中生成唯一的key,然后不论是用BaseObservable还是使用ObservableField的方式来包装被观察字段,最后的实现逻辑都是类似的,这里以BaseObservable为例。

监听绑定的过程上面介绍过了,这里就直接从BaseObservable的notifyPropertyChanged()开始,接下来会直接调用CallbackRegistry.notifyCallbacks(),然后会调用到PropertyChangeRegistry.onNotifyCallback(),并最终调用到WeakPropertyListener的onPropertyChanged()方法中。

1
2
3
4
5
6
7
8
9
10
11
private void notifyCallbacks(T sender, int arg, A arg2, final int startIndex,
final int endIndex, final long bits) {
long bitMask = 1;
for (int i = startIndex; i < endIndex; i++) {
if ((bits & bitMask) == 0) {
// (mCallbacks.get(i)即为前文建立VM与Model联系的listener
mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
}
bitMask <<= 1;
}
}

最终的处理逻辑会回到ViewDataBinding.handleFieldChange()

1
2
3
4
5
6
7
8
private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
// 在这里根据fieldId 设置mDirtyFlags 标记发生更新的字段
boolean result = onFieldChange(mLocalFieldId, object, fieldId);
if (result) {
// 根据mDirtyFlags刷新UI
requestRebind();
}
}

接着看requestRebind()的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
protected void requestRebind() {
// 省略了通过标记位控制冗余绑定,以及在Lifecycle未到达STARTED前不绑定的逻辑
if (USE_CHOREOGRAPHER) {
mChoreographer.postFrameCallback(mFrameCallback);
} else {
mUIThreadHandler.post(mRebindRunnable);
}
}

// 接着会走到mRebindRunnable->executeBindingsInternal()中
private void executeBindingsInternal() {
// 省略部分逻辑
mIsExecutingPendingBindings = true;
mRebindHalted = false;
if (mRebindCallbacks != null) {
mRebindCallbacks.notifyCallbacks(this, REBIND, null);
// The onRebindListeners will change mPendingHalted
if (mRebindHalted) {
mRebindCallbacks.notifyCallbacks(this, HALTED, null);
}
}
if (!mRebindHalted) {
executeBindings();
if (mRebindCallbacks != null) {
mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
}
}
mIsExecutingPendingBindings = false;
}

最终真正实现UI更新的地方是ActivityDataBindingBindingImpl.executeBindings()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
String userInfoName = null;
User userInfo = mUserInfo;

if ((dirtyFlags & 0x3L) != 0) {
if (userInfo != null) {
// read userInfo.name
userInfoName = userInfo.getName();
}
}
// batch finished
if ((dirtyFlags & 0x3L) != 0) {
// 在这里更新UI
TextViewBindingAdapter.setText(this.mboundView1, userInfoName);
TextViewBindingAdapter.setText(this.mboundView2, userInfoName);
}
// ...
}
避免冗余的UI更新

根据上文的源码分析,可以发现,在这样一个场景下(View自己更新UI导致model更新,而model的更新又会导致View再更新一次),可能会导致UI的重复更新。

但是实际上这样的情况是不会发生的,原因就在于,Model改变后,我们更新UI不会直接调用View的原生方法传递model更新UI,而是通过一个Adapter,间接调用View的更新方法。以TextView为例:

1
2
3
4
5
6
7
8
// 先看ActivityDataBindingBindingImpl.executeBindings()
@Override
protected void executeBindings() {
// ...
// 可以发现这里更新UI是通过调用TextViewBindingAdapter的封装方法
TextViewBindingAdapter.setText(this.btnTest, userInfoName);
// ...
}

接下来看TextViewBindingAdapter.setText()的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@BindingAdapter("android:text")
public static void setText(TextView view, CharSequence text) {
final CharSequence oldText = view.getText();
if (text == oldText || (text == null && oldText.length() == 0)) {
return;
}
if (text instanceof Spanned) {
if (text.equals(oldText)) {
return; // No change in the spans, so don't set anything.
}
} else if (!haveContentsChanged(text, oldText)) {
return; // No content changes, so don't set anything.
}
view.setText(text);
}

可以发现,正是几次模型变化的校验,保证了内容不变时不刷新UI的逻辑,也就解决了重复刷新UI的问题。

参考资料

浅谈 MVC、MVP 和 MVVM 架构模式

AOSP data-binding

DataBinding源码解析