先上一段布局文件代码,如下:
引入layout、data标签需要在项目的gradle中加入如下配置:
dataBinding{
enabled = true
}
此时编译代码,发现在编译目录下会多出一些文件,如下截图:
这是因为dataBinding使用了 APT(Annotation Processing Tool)即注解处理器的技术,编译期间会扫描res/layout目录下所有的布局文件,只要有data标签,都会生成两个布局文件:配置文件和带标签的布局文件。
其中配置文件主要是通过tag能够快速定位绑定的控件,生成的布局文件主要是生成控件的tag值。两个文件的代码截图如下:
下面讲解java中如何使用dataBinding实现单项绑定和双向绑定,具体apt是如何进行编译的,后面的博客中会进行讲解。这里只需要知道apt已经为我们生成了数据和控件绑定(即view和model绑定)的代码。
1、创建activity类,在onCreate方法中实现dataBinding的setContentView()方法。代码如下:
ActivityDatabindingBinding databindingBinding = DataBindingUtil.setContentView(this,
R.layout.activity_databinding);
1、单向绑定,数据变化不会导致view更新
(1)创建model对象DataBean1,代码如下:
public class DataBean1 {
private String userName;
private String userPwd;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserPwd() {
return userPwd;
}
public void setUserPwd(String userPwd) {
this.userPwd = userPwd;
}
@Override
public String toString() {
return "DataBean1{" +
"userName='" + userName + '\'' +
", userPwd='" + userPwd + '\'' +
'}';
}
}
(2)在activity中实例化对象,并调用绑定方法,代码如下
mDataBean.setUserName("qb");
mDataBean.setUserPwd("123456");
databindingBinding.setDataBean(mDataBean);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mDataBean.setUserName("qb2222");
mDataBean.setUserPwd("654321");
Log.e("更新model",mDataBean.toString());
}
},3000);
测试发现,界面一开始显示的是"qb","123456"。当3s后,我们虽然改变了实体的字段信息,但是界面没有发生变化。
2、单向绑定,数据变化会导致view更新
(1)创建model对象DataBean2,代码如下:
public class DataBean2 {
public ObservableField userName = new ObservableField<>();
public ObservableField userPwd = new ObservableField<>();
@Override
public String toString() {
return "DataBean2{" +
"userName='" + userName.get() + '\'' +
", userPwd='" + userPwd.get() + '\'' +
'}';
}
}
(2)在activity中实例化对象,并调用绑定方法,代码如下
mDataBean2.userName.set("qb");
mDataBean2.userPwd.set("123456");
databindingBinding.setDataBean(mDataBean2);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mDataBean2.userName.set("qb2222");
mDataBean2.userPwd.set("654321");
Log.e("更新model", mDataBean2.toString());
}
}, 3000);
测试发现,界面一开始显示的是"qb","123456"。当3s后,我们改变了实体的字段信息,界面也会发生变化。
3、双向绑定,数据变化会导致view更新,view更新也会导致数据变化
(1)同第二种方式基本上一直,只是布局文件发生了小小的变化,代码对比如下:
对比发现,仅仅在"@"后面增加"="号
(2)在activity中实例化对象,并调用绑定方法,代码如下
mDataBean2.userName.set("qb");
mDataBean2.userPwd.set("123456");
databindingBinding.setDataBean(mDataBean2);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Log.e("更新model", mDataBean2.toString());
}
}, 10000);
测试发现,界面一开始显示的是"qb","123456"。当我们界面输入新的数据后,打印的就是我们输入的信息。
1、Model绑定View(Model变化导致界面刷新)
(1)点击 DataBindingUtil.setContentView()进入源码中。依次跟踪方法setContentView()—>bindToAddedViews()—>bind()—>sMapper.getDataBinder。最终发现这个方法是一个抽象方法,那么我们就需要去子类中查看该方法,这个子类就是DataBinderMapperImpl.class
(2)进入DataBinderMapperImpl.class中,找到getDataBinder()方法。如下代码:
public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
if(localizedLayoutId > 0) {
//找到布局文件的父节点是否有tag,没有的话就抛出异常
final Object tag = view.getTag();
if(tag == null) {
throw new RuntimeException("view must have a tag");
}
switch(localizedLayoutId) {
case LAYOUT_ACTIVITYDATABINDING: {
//找到的话,进入ActivityDatabindingBindingImpl
if ("layout/activity_databinding_0".equals(tag)) {
return new ActivityDatabindingBindingImpl(component, view);
}
throw new IllegalArgumentException("The tag for activity_databinding is invalid. Received: " + tag);
}
}
}
return null;
}
(3)进入ActivityDatabindingBindingImpl.class中,构造方法代码如下:
// views
@NonNull
private final android.widget.LinearLayout mboundView0;
@NonNull
private final android.widget.EditText mboundView1;
@NonNull
private final android.widget.EditText mboundView2;
public ActivityDatabindingBindingImpl(@Nullable android.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));
}
private ActivityDatabindingBindingImpl(android.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
super(bindingComponent, root, 2
);
//根据tag找到控件
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
this.mboundView1 = (android.widget.EditText) bindings[1];
this.mboundView1.setTag(null);
this.mboundView2 = (android.widget.EditText) bindings[2];
this.mboundView2.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
将bindings数组强转为我们的控件类型。数组的生成过程如下代码
private static void mapBindings(DataBindingComponent bindingComponent, View view, Object[] bindings, ViewDataBinding.IncludedLayouts includes, SparseIntArray viewsWithIds, boolean isRoot) {
ViewDataBinding existingBinding = getBinding(view);
if (existingBinding == null) {
...
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup)view;
count = viewGroup.getChildCount();
int minInclude = 0;
for(int i = 0; i < count; ++i) {
if (childTag.endsWith("_0") && childTag.startsWith("layout") &&
childTag.indexOf(47) > 0) {
...//遍历子view,根据tag,将view放到数组中
}
}
}
}
也就是说所有的布局控件都会放到一个数组对象中,那么这个数组对象大小是不定的,如果你有多个activity就会存在多个数组对象,这是比较占用内存的。
(4)回到构造方法,继续跟踪invalidateAll()—>requestRebind()。方法部分代码如下:
/**
* @hide
*/
protected void requestRebind() {
if (mContainingBinding != null) {
mContainingBinding.requestRebind();
} else {
...
if (USE_CHOREOGRAPHER) {
mChoreographer.postFrameCallback(mFrameCallback);
} else {
//通过handler形式更新ui
mUIThreadHandler.post(mRebindRunnable);
}
}
}
也就说所有的model更新都是通过handler通知view进行更新的,handler内部拥有一个Looper对象,是不断的在执行消息循环。每一个activity都会存在一个handler,这样也会导致内存的大量消耗。
(5)进入mRebindRunnable,跟踪最终会进入ActivityDatabindingBindingImpl.class的executeBindings()方法。代码如下:
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
java.lang.String dataBeanUserPwdGet = null;
java.lang.String dataBeanUserNameGet = null;
android.databinding.ObservableField dataBeanUserPwd = null;
android.databinding.ObservableField dataBeanUserName = null;
com.xinyartech.mymvpdemo.dataBinding.DataBean2 dataBean = mDataBean;
if ((dirtyFlags & 0xfL) != 0) {
if ((dirtyFlags & 0xdL) != 0) {
if (dataBean != null) {
// read dataBean.userPwd
dataBeanUserPwd = dataBean.userPwd;
}
updateRegistration(0, dataBeanUserPwd);
if (dataBeanUserPwd != null) {
// read dataBean.userPwd.get()
dataBeanUserPwdGet = dataBeanUserPwd.get();
}
}
if ((dirtyFlags & 0xeL) != 0) {
if (dataBean != null) {
// read dataBean.userName
dataBeanUserName = dataBean.userName;
}
updateRegistration(1, dataBeanUserName);
if (dataBeanUserName != null) {
// read dataBean.userName.get()
dataBeanUserNameGet = dataBeanUserName.get();
}
}
}
// batch finished
if ((dirtyFlags & 0xeL) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, dataBeanUserNameGet);
}
...
if ((dirtyFlags & 0xdL) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView2, dataBeanUserPwdGet);
}
}
代码中很清晰的看到,通过dataBeanUserPwd.get()和 dataBeanUserPwd.get()获取Model的字段值。通过android.databinding.adapters.TextViewBindingAdapter.setText()方法显示在View层。
2、View绑定Model(View变化导致Model字段值更新)
同样在executeBindings()方法中,代码如下:
protected void executeBindings() {
if ((dirtyFlags & 0x8L) != 0) {
android.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView1, (android.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, mboundView1androidTextAttrChanged);
android.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView2, (android.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, mboundView2androidTextAttrChanged);
}
}
针对每一个控件都添加了一个监听mboundView1androidTextAttrChanged,mboundView2androidTextAttrChanged。针对第一个监听,进入监听回调方法。代码如下:
private android.databinding.InverseBindingListener mboundView1androidTextAttrChanged = new android.databinding.InverseBindingListener() {
@Override
public void onChange() {
//获取控件的值
java.lang.String callbackArg_0 = android.databinding.adapters.TextViewBindingAdapter.getTextString(mboundView1);
...
if (dataBeanJavaLangObjectNull) {
dataBeanUserName = dataBean.userName;
dataBeanUserNameJavaLangObjectNull = (dataBeanUserName) != (null);
if (dataBeanUserNameJavaLangObjectNull) {
//更新model属性值
dataBeanUserName.set(((java.lang.String) (callbackArg_0)));
}
}
}
};
首先当监听到控件有变化时,会通过回调的形式拿到控件的值,并赋值给Model属性值。
另外这里看到,针对每一个控件都会存在一个回调对象,针对多个activity,也会导致内存的大量消耗
1、dataBinding只是一种工具,和MVVM(设计思想)是完全没有关系的,MVC、MVP同样可以使用dataBinding。
2、使用dataBinding会导致大量的内存消耗,具体在3个地方会导致该情况发生。
(1)会产生多余的数组,存放view对象
(2)存在大量handler,looper不断的循环消息
(3)针对每一个控件都会产生一个回调对象
3、dataBinding使用APT技术,编译期间生成控件与model的绑定代码。所以会产生很多类,随着布局文件的增加,编译会越来越慢。