有写过前端的同学应该对前端数据双向绑定简直不要太熟悉,如果你是客户端同学,你也应该知道android最开始是没有数据双向绑定的能力的,但前几年android也适时地推出了自己的DataBinding框架,用过的人简直不要说太爽,今天来一探究竟,android的DataBinding如何实现,如何使用的参考我的前一篇博客 https://www.chenzujie.com/android-databinding-tutorial ,这篇主要讲实现原理。
绑定类介绍
先从代码说起,使用DataBingding就从这么一行简单代码开始
mUserInfoBinding = DataBindingUtil.setContentView(activity, R.layout.activity_binding);
这行代码就是从DataBinding内部根据view拿到Binding类
switch(layoutId) {
case com.dbinding.jc.databindingdemo.R.layout.activity_binding:
return com.dbinding.jc.databindingdemo.binding.UserInfoBinding.bind(view, bindingComponent);
}
return null;
而这个Binding类就是整个DataBinding的核心,它有点类似代码生成的原理,根据xml里的配置生成对应的Java代码,下面来细看这个Binding对象。
首先在构造函数里把xml里绑定过的View对象都获取到,包括对应的点击事件
public UserInfoBinding(@NonNull android.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
super(bindingComponent, root, 1);
final Object[] bindings = mapBindings(bindingComponent, root, 23, sIncludes, sViewsWithIds);
this.btnAddAge = (android.widget.Button) bindings[9];
this.btnAddAge.setTag(null);
this.btnListPage = (android.widget.Button) bindings[10];
this.btnListPage.setTag(null);
this.clAge = (android.support.constraint.ConstraintLayout) bindings[13];
this.clAvatar = (android.support.constraint.ConstraintLayout) bindings[21];
this.clJob = (android.support.constraint.ConstraintLayout) bindings[17];
this.clName = (android.support.constraint.ConstraintLayout) bindings[11];
this.clSex = (android.support.constraint.ConstraintLayout) bindings[15];
this.clWebsite = (android.support.constraint.ConstraintLayout) bindings[19];
this.ivAvatar = (android.widget.ImageView) bindings[8];
this.ivAvatar.setTag(null);
//.......(省略部分代码)
setRootTag(root);
// listeners
mCallback2 = new android.databinding.generated.callback.OnClickListener(this, 2);
mCallback1 = new android.databinding.generated.callback.OnClickListener(this, 1);
invalidateAll();
}
以上其实Binding类生成的时候主要做的事,帮你找到对应的控件对象,并做一些初始化的工作。
数据通知
接下去我们就讲讲在具体使用的时候如何通过改变数据对应UI变化,最简单的设置方式就是setXXX,我们先来看看如果我们对非对象类型的参数做设置
mUserInfoBinding.setTitle("个人介绍");
public void setTitle(@Nullable java.lang.String Title) {
this.mTitle = Title;
synchronized(this) {
mDirtyFlags |= 0x4L;
}
notifyPropertyChanged(BR.title);
super.requestRebind();
}
里面的实现其实就两件事通知数据变化notifyPropertyChanged,发起重绘请求requestRebind,如果跟代码进去看我们会发现非对象类型的参数的通知数据变化不会有任何操作,因为它的mCallback都为空
public void notifyPropertyChanged(int fieldId) {
synchronized (this) {
if (mCallbacks == null) {
return;
}
}
mCallbacks.notifyCallbacks(this, fieldId, null);
}
那什么情况下mCallbacks不会为空呢,我们再来看看对象类型的set方式有何差别
mUserInfoBinding.setUserInfo(userInfo);
public void setUserInfo(@Nullable com.dbinding.jc.databindingdemo.model.UserInfo UserInfo) {
updateRegistration(0, UserInfo);
this.mUserInfo = UserInfo;
synchronized(this) {
mDirtyFlags |= 0x1L;
}
notifyPropertyChanged(BR.userInfo);
super.requestRebind();
}
我们发现和非对象类型的参数多了一步updateRegistration,这个方法我们不做展开,因为我们传入的UserInfo本身是继承与BaseObserver,因此它最后会对mCallbacks进行赋值,并传一个callback进去
@Override
public void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
synchronized (this) {
if (mCallbacks == null) {
mCallbacks = new PropertyChangeRegistry();
}
}
mCallbacks.add(callback);
}
这里的callback我们断点看下就会发现它就是我们的binding类,后续我们会再用到它
当我们完成各种setXXX的参数就会看到代码执行到之前每个setXXX里requestRebind里发送的mRebindRunnable,它会调用Binding类里的executeBinding
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
int userInfoAge = 0;
com.dbinding.jc.databindingdemo.presenter.BindingPresenter presenter = mPresenter;
java.lang.String userInfoSex = null;
java.lang.String userInfoJob = null;
java.lang.String title = mTitle;
java.lang.String userInfoName = null;
java.lang.String userInfoPersonalWebsite = null;
com.dbinding.jc.databindingdemo.model.UserInfo userInfo = mUserInfo;
java.lang.String userInfoWebsiteSymbol = null;
java.lang.String userInfoAvatar = null;
java.lang.String utilsIntToStringUserInfoAge = null;
if ((dirtyFlags & 0x24L) != 0) {
}
if ((dirtyFlags & 0x39L) != 0) {
if ((dirtyFlags & 0x31L) != 0) {
if (userInfo != null) {
// read userInfo.age
userInfoAge = userInfo.getAge();
}
// read Utils.intToString(userInfo.age)
utilsIntToStringUserInfoAge = com.dbinding.jc.databindingdemo.util.Utils.intToString(userInfoAge);
}
if ((dirtyFlags & 0x21L) != 0) {
if (userInfo != null) {
// read userInfo.sex
userInfoSex = userInfo.getSex();
// read userInfo.job
userInfoJob = userInfo.getJob();
// read userInfo.personalWebsite
userInfoPersonalWebsite = userInfo.getPersonalWebsite();
// read userInfo.websiteSymbol
userInfoWebsiteSymbol = userInfo.getWebsiteSymbol();
// read userInfo.avatar
userInfoAvatar = userInfo.getAvatar();
}
}
if ((dirtyFlags & 0x29L) != 0) {
if (userInfo != null) {
// read userInfo.name
userInfoName = userInfo.getName();
}
}
}
// batch finished
if ((dirtyFlags & 0x20L) != 0) {
// api target 1
this.btnAddAge.setOnClickListener(mCallback1);
this.btnListPage.setOnClickListener(mCallback2);
}
if ((dirtyFlags & 0x21L) != 0) {
// api target 1
com.dbinding.jc.databindingdemo.util.BindingUtil.setImageUrl(this.ivAvatar, userInfoAvatar);
com.dbinding.jc.databindingdemo.util.BindingUtil.setImageUrl(this.ivWebsite, userInfoWebsiteSymbol);
android.databinding.adapters.TextViewBindingAdapter.setText(this.tvJob, userInfoJob);
android.databinding.adapters.TextViewBindingAdapter.setText(this.tvSex, userInfoSex);
android.databinding.adapters.TextViewBindingAdapter.setText(this.tvWebsite, userInfoPersonalWebsite);
}
if ((dirtyFlags & 0x31L) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setText(this.tvAge, utilsIntToStringUserInfoAge);
}
if ((dirtyFlags & 0x29L) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setText(this.tvName, userInfoName);
}
if ((dirtyFlags & 0x24L) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setText(this.tvTitle, title);
}
}
上面的代码很长,挑重点说,核心就是拿到该绑定类里面的绑定的数据,做初始化的复制,比如文本使用如下方式设置,还会帮忙判断是否文本和之前一样,杨则不帮忙setText
android.databinding.adapters.TextViewBindingAdapter.setText(this.tvAge, utilsIntToStringUserInfoAge);
如果你绑定的数据是通过自有方法进行设置的,这个方法也会对应调用自有数据进行绑定,例如图片的设置我们使用了另外的框架封装在BindingUtil里,该绑定类也自动调用对应方法
com.dbinding.jc.databindingdemo.util.BindingUtil.setImageUrl(this.ivWebsite, userInfoWebsiteSymbol);
所以其实所有的setXXX数据之后都是通过requestRebind做数据的重新刷新。
复杂对象的更新
当我们更新复杂对象内部的数据的,我们并没有直接调用Binding类的setXXX,它又是如何去更新的,其实前面的代码多少有点答案了,我们来看看源码
我们更新对象内部数据的时候,要去主动调用notifyPropertyChanged,
BR.xxx是DataBinding框架类帮我们提前生成好的id,这个notifyf方法其实就是通知刚刚的mCallbacks里添加的那个callback去更新数据
public void notifyPropertyChanged(int fieldId) {
synchronized (this) {
if (mCallbacks == null) {
return;
}
}
mCallbacks.notifyCallbacks(this, fieldId, null);
}
并且最后调用的还是前面说的requestRebind
总结
所以介绍到这里,基本整个DataBinding比较重要的流程我们都介绍到了,是不是感觉很简答,其实核心就是根据xml里的绑定写法去生成代码,并做好相应的观察者的数据通知,不过DataBingding太过于灵活了,我们甚至可以在xml里写逻辑表达式,这样要是逻辑表达式写错了,崩溃都不知道不能定位到xml里,所以尽量不要在xml里写负责的逻辑表达式,最好通过工具类来调用,这样也符合写代码的逻辑与UI分离的原则,整体上我还是觉得DataBinding是一个很赞的框架。