前言
DataBinding是JetPack系列的架构组件,它的作用时实现数据与UI的绑定,支持单向绑定或者双向绑定,官网地址:数据绑定库 。
系列文章
Android Jetpack ViewModel解析
Android Jetpack LiveData解析
Android Jetpack DataBinding原理浅析(简版)
目前关于DataBinding使用和源码解读的文章不在少数,本文就不再重复造轮子,主要是梳理一下其核心部分的工作原理,帮助大家更好的使用和理解这个库。我个人认为看DataBinding源码应当重点关注2个部分:
1、数据是如何映射到View的?
2、双向绑定的原理是什么?
先看一个例子:
layout需要用到的model
public class User {
public ObservableField<String> name=new ObservableField<>();
public ObservableField<String> nickName=new ObservableField<>();
}
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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="user"
type="com.zx.databindingdemo.User" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/name_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/nick_name_edt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:text="@={user.nickName}"
app:layout_constraintLeft_toLeftOf="@id/name_tv"
app:layout_constraintTop_toBottomOf="@id/name_tv" />
</android.support.constraint.ConstraintLayout>
</layout>
MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
final User user = new User();
user.name.set("科比.布莱恩特");
user.nickName.set("black manba");
binding.setUser(user);
}
}
首先来看看ActivityMainBinding
是什么时间生成的?
回顾一下,在activity_main.xml
中把根布局改为<layout>
标签之后,在回到对应的MainActivity就可以使用ActivityMainBinding对象了。这一点也可以得到证实:在引入<layout>
之后,build项目会发现在Project目录下生成多个新的文件,目录大致如下:
主要文件都在apt目录。此外原来的activity_main.xml
文件还会生成2个XML文件,具体信息如下:
- activity_main-layout.xml(DataBinding需要的布局控件信息)
路径
app/build/intermediates/data_binding_layout_info_type_merge/debug/mergeDebugResources/out/activity_main-layout.xml
- activity_main.xml(Android OS 渲染的布局文件)
路径
app/build/intermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml
结合生成文件和XML的位置和时间节点,大致可以看出生成的原理是Gradle插件+APT,这个插件是Gradle内置的,目前还没有查找到相关插件的实现在哪里。
数据是如何绑定到View的?
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
先看看大致的调用关系,找到最终绑定View的地方
`setContentView`
----->`DataBinderMapperImpl#getDataBinder()`
----->`根据根布局标记的tag ActivityMainBindingImpl`
----->`内部构造方法调用invalidateAll();`
----->`mRebindRunnablemRebindRunnable`
---->`executePendingBindings()`
---->`executeBindingsInternal()`
---->` executeBindings()`
数据绑定的具体实现是在ActivityMainBindingImpl#executeBindings()
方法中:
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
android.databinding.ObservableField<java.lang.String> userName = null;
java.lang.String userNickNameGet = null;
java.lang.String userNameGet = null;
android.databinding.ObservableField<java.lang.String> userNickName = null;
com.zx.databindingdemo.User user = mUser;
if ((dirtyFlags & 0xfL) != 0) {
if ((dirtyFlags & 0xdL) != 0) {
if (user != null) {
// read user.name
userName = user.name;
}
updateRegistration(0, userName);
if (userName != null) {
// read user.name.get()
userNameGet = userName.get();
}
}
if ((dirtyFlags & 0xeL) != 0) {
if (user != null) {
// read user.nickName
userNickName = user.nickName;
}
updateRegistration(1, userNickName);
if (userNickName != null) {
// read user.nickName.get()
userNickNameGet = userNickName.get();
}
}
}
// batch finished
if ((dirtyFlags & 0xdL) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setText(this.nameTv, userNameGet);
}
if ((dirtyFlags & 0xeL) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setText(this.nickNameEdt, userNickNameGet);
}
if ((dirtyFlags & 0x8L) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.nickNameEdt, (android.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, nickNameEdtandroidTextAttrChanged);
}
}
这里通过TextViewBindingAdapter.setText()
去给UI控件赋值;而且在赋值之前会对model对象进行判空,这样就避免了set XX()方法时出现空指针异常。
到这里还没完,这个地方只能算是完成数据到View的映射,当我们更改Model的ObservableField
属性去更新数据的时候,又是如何更新UI的呢?
ObservableField是对常用数据结构对包装类,它最终继承
BaseObservable
,它内部封装了观察者模式,可以监听数据的变化。
看一下它的set方法
/**
* Set the stored value.
*/
public void set(T value) {
if (value != mValue) {
mValue = value;
notifyChange();
}
}
这里notifyChange()之后会通过观察者模式的OnPropertyChangedCallback
回调到ViewDataBinding #WeakListListener#onChanged()
private static class WeakListListener extends ObservableList.OnListChangedCallback
implements ObservableReference<ObservableList> {
final WeakListener<ObservableList> mListener;
public WeakListListener(ViewDataBinding binder, int localFieldId) {
mListener = new WeakListener<ObservableList>(binder, localFieldId, this);
}
@Override
public WeakListener<ObservableList> getListener() {
return mListener;
}
@Override
public void addListener(ObservableList target) {
target.addOnListChangedCallback(this);
}
@Override
public void removeListener(ObservableList target) {
target.removeOnListChangedCallback(this);
}
@Override
public void onChanged(ObservableList sender) {
ViewDataBinding binder = mListener.getBinder();
if (binder == null) {
return;
}
ObservableList target = mListener.getTarget();
if (target != sender) {
return; // We expect notifications only from sender
}
binder.handleFieldChange(mListener.mLocalFieldId, target, 0);
}
然后
`handleFieldChange`
--->`requestRebind`
---->`executePendingBindings()`
---->`executeBindingsInternal()`
---->` executeBindings()`
最终又调用到了最终调用了executeBindings()
,这里就是完成UI更新的地方。
观察View变化更新Model数据
再次回到更新UI的地方ActivityMainBindingImpl#executeBindings()
@Override
protected void executeBindings() {
....
// batch finished
if ((dirtyFlags & 0xdL) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setText(this.nameTv, userNameGet);
}
if ((dirtyFlags & 0xeL) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setText(this.nickNameEdt, userNickNameGet);
}
if ((dirtyFlags & 0x8L) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.nickNameEdt, (android.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, nickNameEdtandroidTextAttrChanged);
}
}
看一下这个setTextWatcher()
方法,当数据发生变化的时候,TextWatcher在回调onTextChanged()
的最后,会通过回调传入的ActivityMainBindingImpl # nickNameEdtandroidTextAttrChanged # onChange()
private android.databinding.InverseBindingListener nickNameEdtandroidTextAttrChanged = new android.databinding.InverseBindingListener() {
@Override
public void onChange() {
// Inverse of user.nickName.get()
// is user.nickName.set((java.lang.String) callbackArg_0)
java.lang.String callbackArg_0 = android.databinding.adapters.TextViewBindingAdapter.getTextString(nickNameEdt);
// localize variables for thread safety
// user.nickName != null
boolean userNickNameJavaLangObjectNull = false;
// user != null
boolean userJavaLangObjectNull = false;
// user
com.zx.databindingdemo.User user = mUser;
// user.nickName.get()
java.lang.String userNickNameGet = null;
// user.nickName
android.databinding.ObservableField<java.lang.String> userNickName = null;
userJavaLangObjectNull = (user) != (null);
if (userJavaLangObjectNull) {
userNickName = user.nickName;
userNickNameJavaLangObjectNull = (userNickName) != (null);
if (userNickNameJavaLangObjectNull) {
userNickName.set(((java.lang.String) (callbackArg_0)));
}
}
}
};
这里就比较简单了,获取控件中最新的值,然后给ObservableField属性赋值。