Android的databinding已经出来好久了,一直也没有用到项目中,这两天在郭霖的公众号上看到分析databinding的一篇文章,遂打算练习一下,使用之后发现非常方便,个人认为对于交互不多,展示性强的界面可以使用databinding。另外之前写过一篇android组件化开发的文章Android组件化开发实践, 在后续的开发中,发现butterknife从version8.2.0才开始支持用在library中,而且需要把R.xxx.xxx 更改为R2.xxx.xxx, 直接导致在组件化开发中使用坑非常深,因此使用databinding是一个不错的代替,虽然也有一些坑,记一次 Data Binding 在 library module 中遇到的大坑中有介绍。
基本用法
1. 依赖
在使用databinding的module的build.gradle中设置:
dataBinding {
enabled true
}
注意如果moduleA依赖moduleB,muduleB中使用了databinding,那么moduleA中也需要如上设置。
2. xml
使用databindind简单来讲就是帮助我们把数据data和视图view进行bind,我们需要在xml中定义这种绑定。修改后的xml分为两部分:数据定义和视图布局
<?xml version="1.0" encoding="utf-8"?>
//最外层用layout
<layout xmlns:android="http://schemas.android.com/apk/res/android">
//数据部分定义
<data>
<variable
name="user"
type="cn.easydone.componentizationapp.model.User" />
</data>
//布局部分定义
<RelativeLayout
android:id="@+id/activity_data_bind"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user.name}" />
<TextView
android:id="@+id/age"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/name"
android:text="@{String.valueOf(user.age)}" />
</RelativeLayout>
</layout>
可以看到在数据部分我们定义了一个User类的变量user,User类定义在路径
cn.easydone.componentizationapp.model.User
下。之后在布局文件中就可以使用user变量进行text的设置了。但是user是什么时候进行设置呢:
3. 数据的绑定
在代码中要修改原来
setContentView(R.layout.activity_data_bind )
ActivityDataBindBinding bindBinding = DataBindingUtil.setContentView(this, R.layout.activity_data_bind);
final User user = new User();
bindBinding.setUser(user);
可以看到通过binding把user set进去即可,使用很方便。其中ActivityDataBindBinding是根据xml的名字生成的,之后在databinding原理部分会进行介绍。
4. 数据的更新
databinding的功能不仅仅是完成data和view的映射,更重要是可以实现data和view的绑定,即实现数据视图同步,更新数据后,view的展示可以实时更新。
实现这种绑定有两种方法:
方法一:继承BaseObservable类
之前我们的User类仅仅是一个普通的POJO,现在继承BaseObservable:
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.user);
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
notifyPropertyChanged(BR.user);
}
这样在使用setXxx后就可以直接更新view了,其中BR类是databinding生成的代码,在原理部分进行探讨。
方法二:使用Observable变量
public final ObservableField<String> name = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
个人比较喜欢这种方式,非常方便直观。
databinding分析
再来回顾一下databinding的功能:data view绑定,即实现数据和视图的映射和同步。
1. 映射
我们在xml中分别定义了数据变量和视图布局,在编译过程中,databinding会把xml拆分为两部分,其中数据部分的xml如下:
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<Layout layout="activity_data_bind" modulePackage="cn.easydone.componentizationapp" absoluteFilePath="/Users/xxx/Documents/workspace/ComponentizationApp/App/src/main/res/layout/activity_data_bind.xml" directory="layout" isMerge="false">
<Variables declared="true" type="cn.easydone.componentizationapp.model.User" name="user">
<location startLine="5" startOffset="8" endLine="7" endOffset="63" /></Variables>
<Targets>
<Target id="@+id/activity_data_bind" tag="layout/activity_data_bind_0" view="RelativeLayout">
<Expressions/>
<location startLine="11" startOffset="4" endLine="28" endOffset="20" /></Target>
<Target id="@+id/name" tag="binding_1" view="TextView">
<Expressions>
<Expression text="user.name" attribute="android:text">
<Location startLine="20" startOffset="12" endLine="20" endOffset="38" />
<TwoWay>false</TwoWay>
<ValueLocation startLine="20" startOffset="28" endLine="20" endOffset="36" /></Expression>
</Expressions>
<location startLine="16" startOffset="8" endLine="20" endOffset="41" /></Target>
<Target id="@+id/age" tag="binding_2" view="TextView">
<Expressions>
<Expression text="String.valueOf(user.age)" attribute="android:text">
<Location startLine="27" startOffset="12" endLine="27" endOffset="53" />
<TwoWay>false</TwoWay>
<ValueLocation startLine="27" startOffset="28" endLine="27" endOffset="51" /></Expression>
</Expressions>
<location startLine="22" startOffset="8" endLine="27" endOffset="56" /></Target>
</Targets>
</Layout>
布局部分的xml如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:id="@+id/activity_data_bind"
android:layout_width="match_parent"
android:layout_height="match_parent" android:tag="layout/activity_data_bind_0" xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="binding_1" />
<TextView
android:id="@+id/age"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/name"
android:tag="binding_2" />
</RelativeLayout>
注意其中id和tag的设置。
之后根据两个xml在编译时生成 ActivityDataBindBinding和BR类,其中ActivityDataBindBinding的名字是根据xml的命名生成的,我们看一下其构造函数:
public ActivityDataBindBinding(android.databinding.DataBindingComponent bindingComponent, View root) {
super(bindingComponent, root, 2);
final Object[] bindings = mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds);
this.activityDataBind = (android.widget.RelativeLayout) bindings[0];
this.activityDataBind.setTag(null);
this.age = (android.widget.TextView) bindings[2];
this.age.setTag(null);
this.name = (android.widget.TextView) bindings[1];
this.name.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
构造函数中根据上文提到的tag对view变量进行了赋值,之后设置tag为null(至此tag的任务已经完成),这里就完成了findViewById的任务。
2. 绑定
数据绑定的完成是在ActivityDataBindBinding的executeBindings方法中:
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
android.databinding.ObservableInt ageUser = null;
android.databinding.ObservableField<java.lang.String> nameUser = null;
java.lang.String nameUser1 = null;
cn.easydone.componentizationapp.model.User user = mUser;
int ageUser1 = 0;
java.lang.String stringValueOfStringA = null;
if ((dirtyFlags & 0xfL) != 0) {
if ((dirtyFlags & 0xdL) != 0) {
if (user != null) {
// read user.age
ageUser = user.age;
}
updateRegistration(0, ageUser);
if (ageUser != null) {
// read user.age.get()
ageUser1 = ageUser.get();
}
// read String.valueOf(user.age.get())
stringValueOfStringA = java.lang.String.valueOf(ageUser1);
}
if ((dirtyFlags & 0xeL) != 0) {
if (user != null) {
// read user.name
nameUser = user.name;
}
updateRegistration(1, nameUser);
if (nameUser != null) {
// read user.name.get()
nameUser1 = nameUser.get();
}
}
}
// batch finished
if ((dirtyFlags & 0xdL) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setText(this.age, stringValueOfStringA);
}
if ((dirtyFlags & 0xeL) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setText(this.name, nameUser1);
}
}
可以看到把user 的name和age设置到了textview上,之后每次数据的更新,都会执行executeBindings方法进行视图View的更新。
我们来看一下BR类的作用:
public class BR {
public static final int _all = 0;
public static final int user = 1;
}
BR类非常简单,其中的常量是一种标识符,标识一个会发生变化的数据,当数据改变后,可以用该标识符通知 DataBinding,很快,**DataBinding **就会用新的数据去更新UI。
3. 实时更新
这里我们就文中例子进行分析,如有错误之处请进行指正。我们再来看一下定义的User类:
public class User {
public final ObservableField<String> name = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
首先看一下ObservableField类
/**
* Set the stored value.
*/
public void set(T value) {
if (value != mValue) {
mValue = value;
notifyChange();
}
}
可以看到当进行值的设置时,进行了notifychange,我们看一下这里是通知谁进行更新,跟踪代码,可以发现ActivityDataBindBinding的父类ViewDataBinding中的
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
ViewDataBinding binder = mListener.getBinder();
if (binder == null) {
return;
}
Observable obj = mListener.getTarget();
if (obj != sender) {
return; // notification from the wrong object?
}
binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
}
被调用,之后调用了handleFieldChangerequestRebind -> requestRebind -> executePendingBindings -> executeBindings,可以看到最终调用了executeBindings,联系上文,最终在这里完成数据的更新。
这是根据代码调用关系完成分析的,我们正向进行分析datadinding的过程:
ViewDataBinding是ActivityDataBindBinding的父类,其初始化时监听attachState的变化:
static {
if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
ROOT_REATTACHED_LISTENER = null;
} else {
ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
@TargetApi(VERSION_CODES.KITKAT)
@Override
public void onViewAttachedToWindow(View v) {
// execute the pending bindings.
final ViewDataBinding binding = getBinding(v);
binding.mRebindRunnable.run();
v.removeOnAttachStateChangeListener(this);
}
@Override
public void onViewDetachedFromWindow(View v) {
}
};
}
}
当AttachedToWindow时,执行了mRebindRunnable:
@Override
public void run() {
synchronized (this) {
mPendingRebind = false;
}
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
// Nested so that we don't get a lint warning in IntelliJ
if (!mRoot.isAttachedToWindow()) {
// Don't execute the pending bindings until the View
// is attached again.
mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
return;
}
}
executePendingBindings();
}
mRebindRunnable实现了Runnable接口,在其run方法,执行了executePendingBindings, executePendingBindings方法中会执行ActivityDataBindBinding方法中的executeBindings,executeBindings方法是根据xml生成的,User 类中我们定义了:
public final ObservableInt age = new ObservableInt();
对应的executeBindings 方法中定义
android.databinding.ObservableInt ageUser = null;
并执行
updateRegistration(0, ageUser);
updateRegistration 调用ViewDataBinding中的
updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
ViewDataBinding中有一个回调数组mLocalFieldObservers和一个回调处理CREATE_PROPERTY_LISTENER,updateRegistration方法中即把ObservableInt的回调绑定到了CREATE_PROPERTY_LISTENER中,这样当产生set操作时,就会回调CREATE_PROPERTY_LISTENER,CREATE_PROPERTY_LISTENER实际create一个WeakPropertyListener变量:
private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
implements ObservableReference<Observable> {
final WeakListener<Observable> mListener;
public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
mListener = new WeakListener<Observable>(binder, localFieldId, this);
}
@Override
public WeakListener<Observable> getListener() {
return mListener;
}
@Override
public void addListener(Observable target) {
target.addOnPropertyChangedCallback(this);
}
@Override
public void removeListener(Observable target) {
target.removeOnPropertyChangedCallback(this);
}
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
ViewDataBinding binder = mListener.getBinder();
if (binder == null) {
return;
}
Observable obj = mListener.getTarget();
if (obj != sender) {
return; // notification from the wrong object?
}
binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
}
}
可以看到在WeakPropertyListener的方法onPropertyChanged中进行了处理,连接之前我们分析的handleFieldChangerequestRebind -> requestRebind -> executePendingBindings -> executeBindings调用链,至此完成了对于User类中变量的监听并处理。
简单的讲,就是解析xml之后,把User类中的两个ObservableField加入到了ViewDataBinding的一个数组中,并设置了回调,当ObservableField进行数据set操作时,可以实时回调,执行executeBindings方法,对视图进行更新。
总结
databinding主要涉及到了ViewDataBinding, BaseObservable以及ViewDataBinding的内部静态类WeakPropertyListener等几个类,想分析等同学可以查看一下。
另外很重要的datadingding中对于xml的处理,还有binding类和BR类的生成,还需要继续探索。
参考
https://github.com/LyndonChin/MasteringAndroidDataBinding
http://www.jianshu.com/p/de4d50b88437
推荐阅读: