DataBinding是谷歌15年推出的一个工具支持库,借助该库可以使用声明性格式将布局中的界面组件绑定到应用中的数据源。DataBinding支持双向绑定,能大大减少setText、findViewById等代码。双向绑定,即数据发生变化时界面跟着变化,反过来界面内容变化也会同步更新到数据上。
DataBinding在MVVM模式中使用比较多,双向绑定机制实现了View和Model的同步更新。
二、DataBinding使用DataBinding一般都是配合LiveData、ViewModel等其他框架使用,这里通过一个简单的例子展示单独使用方法。
定义数据User类,注意Bindable注解和notifyPropertyChanged方法。public class User extends baseObservable {
private String name;
private String age;
public User(String name, String age) {
this.name = name;
this.age = age;
}
@Bindable // BR里面标记生成 name数值标记
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name); // APT生成的BR文件
}
@Bindable // BR里面标记生成 pwd数值标记
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
notifyPropertyChanged(BR.age); // APT生成的BR文件
}
}
对布局文件进行修改,使用layout包裹这个布局,data标签用来定义数据源上面的User类,控件中可以使用@{}进行绑定,@{}为单向绑定,@={}为双向绑定。
Activity中进行绑定和模拟数据更新,这样就完成了数据和UI双向绑定。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
User user = new User("jack", "25");
binding.setUser(user); // 必须要建立绑定关系,否则没有任何效果
// 修改User的数据 ---> View(UI的控件就自动刷新)
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
user.setName(user.getName() + "哈"); // view.setText(text);
user.setAge(user.getAge() + "哈");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
user.setName("marry");
}
}
DataBinding用起来很简单,越简单表示框架帮我们完成了越多的代码,框架完成的代码毫无疑问是通过apt技术自动生成的。
三、DataBinding源码 布局变化先看看布局文件加了layout等标签后,经过框架处理后有什么变化?
路径:appbuildintermediatesincrementalmergeDebugResourcesstripped.dirlayoutactivity_main.xml
路径:appbuildintermediatesdata_binding_layout_info_type_mergedebugoutactivity_main-layout.xml
根据上面两个图,可以看到原来的布局文件被拆分为activity_main-layout.xml和activity_main.xml。
activity_main.xml中的每个控件都增加了一个tag属性。
activity_main-layout.xml定义了多个Target标签,这些Target的定义其实就是定义对应的tag,将tag与activity_main.xml布局中的对应的View的id对应起来;expression标签标记了控件属性和数据类型;TwoWay标签表示是否双向绑定;Variables标签标记了数据类型的绝对路径;
DataBindingUtil我们使用时在Activity中调用了DataBindingUtil.setContentView()来绑定布局,来看看这个方法。
DataBindingUtil的setContentView()方法,主要就是调用Activity的setContentView设置布局,并且绑定添加对应的View。最后会调用bindToAddedViews()。
parent.getChildCount获取的子View就是上面布局文件中的LinearLayout,所以只有一个子View,最后会走到if里的bing()方法。
这里的sMapper就是DataBinderMapper对象,其实现类是DataBinderMapperImpl,DataBinderMapperImpl是通过注解处理器生成的。这里的sMapper.getDataBinder()其实就是调用的MergedDataBinderMapper的getDataBinder()方法,而sMapper中的数据,其实就是DataBinderMapperImpl的构造器中调用其父类MergedDataBinderMapper的addMapper()方法添加的对象。
在DataBinding中有两个DataBinderMapperImpl类,一个是上面这个在androidx.databinding包下,继承了MergedDataBinderMapper,另一个是在com.wyq.databinding_java包下,直接继承DataBinderMapper。
接下来看MergedDataBinderMapper.getDataBinder()
mMappers集合中的数据就是来源于androidx.databinding.DataBinderMapperImpl的构造器中调用addMapper方法传入的对象添加的,所以这里的mapper就是com.wyq.databinding_java.DataBinderMapperImpl对象。
接下来是com.wyq.databinding_java.DataBinderMapperImpl.getDataBinder()
这里如果是布局的顶层View,比如tag为layout/activity_main_0,那么就会new一个ActivityMainBindingImpl对象。这个tag其实可以从前面activity_main.xml布局中的LinearLayout的tag看到。
接下来是new出的ActivityMainBindingImpl对象
ActivityMainBindingImpl的构造器中会进行一些View的绑定操作,将通过tag取出的View与ActivityMainBindingImpl中对应的View属性进行绑定。
这里会调用了一个mapBindings方法,第三个参数是一个3,就是指activity_main.xml 布局文件中有3个节点,mapBindings会返回一个Object[]bindings数组。
ViewDataBinding.mapBindings()主要工作,就是将布局中的View保存在对应的bindings数组中,然后取出这个数组中的数据赋值给ActivityMainBindingImpl中的View,这样就取到View了,给下一步设置数据做准备。
设置数据上面例子中,我们使用binding.setUser来建立绑定,来看看如何实现的
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
User user = new User("jack", "25");
binding.setUser(user);
ActivityMainBindingImpl.setUser
ViewDataBinding.updateRegistration()
这里的localFieldId=0,这个id其实就BR文件中的id,就是BR文件中对应的静态final属性的,根据BR的每个属性的属性值做index,存储每个BR属性对应的监听器 。而第二个就是观察者对象,比如传入的ViewModel对象。
ViewDataBinding.registerTo()
这个registerTo方法,其实就是将Activity这个观察者和User这个被观察者统一添加到ObservableReference中。
这里通过WeakListener监听器中的ObservableReference对象保存观察者与被观察者,当被观察者发生改变的时候,就会找到对应的WeakListener监听器,然后通知观察者做修改。而ObservableReference 方法的实现有多个,比如:WeakPropertyListener。这里WeakListener.setTarget()其实就是通过WeakPropertyListener给被观察者添加callback,然后当被观察者数据发生改变的时候,被观察者通过遍历其内部的PropertyChangeRegistry中的OnPropertyChangedCallback回调(其实就是WeakPropertyListener),然后通过WeakPropertyListener监听通知给ViewDataBinding以及其实现类ActivityMainBindingImpl具体进行数据的处理和设置。类图如下:
ActivityMainBindingImpl.notifyPropertyChanged()
这里其实就是调用的baseObservable的notifyPropertyChanged()方法。
CallbackRegistry.notifyCallbacks()
这里的mNotifier.onNotifyCallback其实就会调用到下面的PropertyChangeRegistry中定义的
NOTIFIER_CALLBACK属性中的onNotifyCallback实现,而这里的callback其实就是
WeakPropertyListener,因为WeakPropertyListener是OnPropertyChangedCallback的子类,这里其实会回调给mLocalFieldObservers数组中所有的WeakListener。
ViewDataBinding中的WeakPropertyListener类
从mListener中取出target,而这里的mListener其实就是WeakListener,而每个被观察者其实都是有一个对应的LocalFieldId,这个id就是BR文件中定义的,刚才的流程中传入的是0,所以这里的mLocalFieldId=0,继续走到handleFieldChange方法。
这里的onFieldChange的方法的实现在ActivityMainBindingImpl.java中,我们这里会返回true,所以就会继续执行requestRebind()。
requestRebind()最终会执行mRebindRunnable的run()方法,只不过SDK版本大于等于16的时候,会采用Choreographer编舞者来处理,而之前的版本则是采用Handler来执行。最终会走到executeBindings()方法,而这个方法实现也是在ActivityMainBindingImpl
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
java.lang.String userName = null;
java.lang.String userAge = null;
com.wyq.databinding_java.User user = mUser;
if ((dirtyFlags & 0xfL) != 0) {
if ((dirtyFlags & 0xbL) != 0) {
if (user != null) {
// read user.name
userName = user.getName();
}
}
if ((dirtyFlags & 0xdL) != 0) {
if (user != null) {
// read user.age
userAge = user.getAge();
}
}
}
// batch finished
if ((dirtyFlags & 0xbL) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv1, userName);
}
//这里最终再执行setText的操作,其实就是调用了页面布局的View的setText
if ((dirtyFlags & 0x8L) != 0) {
// api target 1 androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.tv1, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, tv1androidTextAttrChanged);
androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.tv2, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, tv2androidTextAttrChanged);
}
if ((dirtyFlags & 0xdL) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv2, userAge);
}
}



