DataBinding 的使用
关于 DataBinding 如何使用 官方文档 已经有详细介绍,该篇文章主要讲解的 DataBinding 的原理,所以基本使用不会赘述。
个人使用 DataBinding 认为还是有使用的注意事项,DataBinding 框架虽然非常强大能处理很多数据绑定操作,但不建议将过多的操作都放在 xml 做数据绑定,应保持尽量以刷新 UI 更新的角度使用 DataBinding,否则当出现问题时,可能会存在难以定位的问题。
DataBinding 的布局绑定过程
在使用 DataBinding 时,为了能做到控件与数据的绑定,xml 会有如下更改:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.example.demo.User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#3C3B54"
android:paddingLeft="5dp"
android:paddingTop="7dp"
android:paddingRight="5dp">
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}" />
</LinearLayout>
</layout>
xml 的根节点是 <layout>
标签,<data>
标签提供数据相关的信息和类,数据绑定通过 @{}
描述说明。xml 文件描述都更改了,inflater dom 解析并不会识别新增的这些标签,那么 DataBinding 是怎么识别的?
/app/build/imtermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#3C3B54"
android:orientation="vertical"
android:paddingLeft="5dp"
android:paddingTop="7dp"
android:paddingRight="5dp"
android:tag="layout/activity_main_0">
<!-- 根布局生成 tag 属性标记,用于创建界面的 DataBinding 类 -->
<!-- 数据绑定的位置生成了 tag 属性标记 -->
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="binding_1" />
</LinearLayout>
/app/build/imtermediates/data_binding_layout_info_type_merge/debug/out/activity_main-layout.xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout directory="layout" filePath="app/src/main/res/layout/activity_main.xml"
isBindingData="true" isMerge="false" layout="activity_main"
modulePackage="com.example.demo" rootNodeType="android.widget.LinearLayout">
<Variables name="user" declared="true" type="com.example.demo.User">
<location endLine="6" endOffset="42" startLine="4" startOffset="8" />
</Variables>
<Targets>
<Target tag="layout/activity_main_0" view="LinearLayout">
<Expressions />
<location endLine="23" endOffset="18" startLine="9" startOffset="4" />
</Target>
<!-- 通过 binding_1 能找到绑定的控件及绑定的数据 -->
<Target id="@+id/text_view" tag="binding_1" view="TextView">
<Expressions>
<Expression attribute="android:text" text="user.name">
<Location endLine="22" endOffset="38" startLine="22" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="22" endOffset="36" startLine="22" startOffset="28" />
</Expression>
</Expressions>
<location endLine="22" endOffset="41" startLine="18" startOffset="8" />
</Target>
</Targets>
</Layout>
当我们将项目 build 重新构建时,DataBinding 会将我们的布局 xml 文件拆成了两个 xml 文件:
-
/app/build/imtermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml
-
/app/build/imtermediates/data_binding_layout_info_type_merge/debug/out/activity_main-layout.xml
为了讲解方便,我将前者称为控件数据标记文件,后者称为控件数据查找文件。
DataBinding 将布局中使用了 @{}
的 View 生成 tag 属性标记,去控件数据查找文件就能查找到绑定信息。综合两个 xml 文件就能做到将控件和绑定信息的关系描述清楚。
如上例 TextView 生成了 tag 属性标记为 binding_1,当数据更新时就能通过 binding_1 找到属性 android:text
绑定的数据是 user.name。
或许你会有疑问:标记?查找绑定?那具体又是怎么个标记和查找绑定?
我们继续分析。先写一个简单使用 DataBinding 的 demo:
public class User extends BaseObservable {
private String name;
private String pwd;
public User(String name, String pwd) {
this.name = name;
this.pwd = pwd;
}
@Bindable
public String getName() {
return name;
}
@Bindable
public String getPwd() {
return pwd;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
public void setPwd(String pwd) {
this.pwd = pwd;
notifyPropertyChanged(BR.pwd);
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding =
DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
binding.user = User("vincent", "12345")
// binding.textView
}
}
上面的 demo 非常简单,创建一个 User 对象,将 User 对象的属性和 xml 中的控件绑定。Activity 对界面的绑定也不是用的 setContentView(),而是通过 DataBindingUtils.setContentView()。
使用 DataBinding 的时候我们可以很方便的通过 binding.textView 的方式获取到控件,它是怎么做到的?DataBindingUtils.setContentView() 到底做了什么事情?
DataBindingUtils.java
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId) {
return setContentView(activity, layoutId, sDefaultComponent);
}
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId, @Nullable DataBindingComponent bindingComponent) {
// 还是调用 Activity 的 setContentView() 填充布局
activity.setContentView(layoutId);
View decorView = activity.getWindow().getDecorView();
ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
}
private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component,
ViewGroup parent, int startChildren, int layoutId) {
final int endChildren = parent.getChildCount();
final int childrenAdded = endChildren - startChildren;
if (childrenAdded == 1) {
// 如果我们的布局只有一个控件,调用 bind() 绑定
final View childView = parent.getChildAt(endChildren - 1);
return bind(component, childView, layoutId);
} else {
// 如果布局有多个控件,循环获取布局的控件,然后 bind() 绑定
final View[] children = new View[childrenAdded];
for (int i = 0; i < childrenAdded; i++) {
children[i] = parent.getChildAt(i + startChildren);
}
return bind(component, children, layoutId);
}
}
// DataBinderMapperImpl 是 APT 生成的类
private static DataBinderMapper sMapper = new DataBinderMapperImpl();
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
}
DataBindingUtils.setContentView() 通过源码分析,其实还是调用的 Activity 的 setContentView(),然后拿到 DecorView 下的 FrameLayout(android.R.id.content),将我们布局中的每个控件进行绑定。
其中 sMapper 是 APT 生成的类,文件生成在 /app/build/generated/ap_generated_sources/debug/out/com/example/demo/databinding/DataBindingMapperImpl.java
。
DataBindingMapperImpl.java
private static final int LAYOUT_ACTIVITYMAIN = 1;
private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(1);
static {
INTERNAL_LAYOUT_ID_LOOKUP.put(com.example.demo.R.layout.activity_main, LAYOUT_ACTIVITYMAIN);
}
@Override
public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
if(localizedLayoutId > 0) {
final Object tag = view.getTag();
if(tag == null) {
throw new RuntimeException("view must have a tag");
}
switch(localizedLayoutId) {
case LAYOUT_ACTIVITYMAIN: {
// /app/build/imtermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml
// xml 文件的根节点 tag
if ("layout/activity_main_0".equals(tag)) {
// APT 生成的实现类
return new ActivityMainBindingImpl(component, view);
}
throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
}
}
}
return null;
}
getDataBinder() 就做了一件事情,根据根布局生成的 tag 标记 layout/activity_main_0 创建对应界面的 ViewDataBinding 即 ActivityMainBindingImpl,在它的构造函数中就做了控件绑定的操作,让我们能够通过 binding.textView
的方式拿到控件:
ActivityMainBindingImpl.java
public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
// 数字 2 表示布局绑定的控件数量
this(bindingComponent, root, mapBindings(bindingComponent, root, 2, sIncludes, sViewsWithIds));
}
private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
super(bindingComponent, root, 1
, (android.widget.TextView) bindings[1] // 控件绑定
);
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
this.textView.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
ViewDataBinding.java
protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root,
int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
Object[] bindings = new Object[numBindings];
// mapBindings() 把 View 对象都存到 bindings 数组里面
// 应用层就能够直接 binding.textView 使用
mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);
return bindings;
}
构造函数中最主要的一句代码是 mapBindings(),该方法将我们布局中的 View 都存到数组中,最终应用层就可以通过 binding.textView 的方式拿到布局中的 View。
我们梳理下 DataBindingUtils.setContentView() 做了什么事情:
-
通过 Activity 的 setContentView() 加载布局
-
根据布局根节点的 tag 属性 layout/activity_xxx_0 创建对应布局的 ViewDataBinding
-
在 ViewDataBinding 创建时遍历布局所有的 View 存到数组中,数组的 View 赋值给 ViewDataBinding 的控件变量。实现可以通过 binding 获取控件 View
DataBinding 是如何做到数据驱动 UI 的?
DataBinding 使用的是观察者模式,只是这个观察者模式的变种比较大。既然是观察者模式,还是会需要有注册、监听和通知三个操作。那么 DataBinding 是在什么时候开始做注册的?
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding =
DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
binding.user = User("vincent", "12345")
// binding.textView
}
}
上面的代码是创建 ActivityMainBindingImpl 后,调用了 setUser() 将我们要提供数据的对象传给了 DataBinding。
ActivityMainBindingImpl.java
public void setUser(@Nullable com.example.demo.User User) {
updateRegistration(0, User);
this.mUser = User;
synchronized(this) {
mDirtyFlags |= 0x1L;
}
notifyPropertyChanged(BR.user);
super.requestRebind();
}
上面的代码虽然很简短,但是却做了几个操作:
-
updateRegistration():注册监听流程,绑定观察者 observer 和被观察者 observable
-
mDirtyFlags:数据是否更新标志
-
super.requestRebind():请求执行界面刷新
注册监听流程
因为涉及的类比较多,在分析时我们需要时刻记得区分好观察者和被观察者,否则很容易会出现分析了流程,但是都不知道这些类是用来干嘛的。
ViewDataBinding.java
// 存储观察者的数组
private WeakListener[] mLocalFieldObservers;
private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
@Override
public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();
}
};
// localFieldId:每个数据对象都会分配一个数据对象绑定 id
// observable:数据对象 User,被观察者
// CREATE_PROPERTY_LISTENER:用于创建 WeakPropertyListener 和 WeakListener
// WeakListener:持有 DataBinding、数据对象绑定 id、数据对象
protected boolean updateRegistration(int localFieldId, Observable observable) {
return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
}
private boolean updateRegistration(int localFieldId, Object observable,
CreateWeakListener listenerCreator) {
...
WeakListener listener = mLocalFieldObservers[localFieldId];
if (listener == null) {
// 第一次获取为 null,走此处的注册过程
// listenerCreator 是 CREATE_PROPERTY_LISTENER
registerTo(localFieldId, observable, listenerCreator);
return true;
}
...
return true;
}
protected void registerTo(int localFieldId, Object observable,
CreateWeakListener listenerCreator) {
if (observable == null) {
return;
}
WeakListener listener = mLocalFieldObservers[localFieldId];
if (listener == null) {
// CREATE_PROPERTY_LISTENER.create() 创建 WeakPropertyListener
// WeakPropertyListener 会创建 WeakListener
listener = listenerCreator.create(this, localFieldId);
mLocalFieldObservers[localFieldId] = listener;
...
}
// 注册数据监听
listener.setTarget(observable);
}
通过分析 updateRegistration() 我们列出几个比较重要信息和区分下观察者和被观察者:
-
localFieldId:数据对象绑定 id。用于查找存储 WeakListener 的数组索引
-
observable:数据对象 User,被观察者
-
WeakPropertyListener:持有 WeakListener
-
WeakListener:ViewDataBinding、数据对象绑定 id、数据对象的包装类,观察者。一个 WeakListener 对应一个数据对象
源码中 mLocalFieldObservers 虽然是存储 WeakListener 的观察者数组,通过源码分析它只是临时记录用于解除绑定操作,和数据绑定通知 UI 刷新无关,从注册流程角度分析我们可以忽略它的作用。
updateRegistration() 内调用 registerTo(),创建 WeakListener 并且存储到 mLocalFieldObservers 数组中。从这个角度分析,ViewDataBinding 是被观察者。
我们继续看 registerTo() 的 listener.setTarget(observable)
做了什么事情:
private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
implements ObservableReference<Observable> {
final WeakListener<Observable> mListener;
public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
// 创建 WeakPropertyListener 的同时创建了 WeakListener
mListener = new WeakListener<Observable>(binder, localFieldId, this);
}
@Override
public WeakListener<Observable> getListener() {
return mListener;
}
@Override
public void addListener(Observable target) {
// 注册数据更新监听回调,target 是 User 对象
target.addOnPropertyChangedCallback(this);
}
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
}
...
}
private static class WeakListener<T> extends WeakReference<ViewDataBinding> {
private final ObservableReference<T> mObservable;
protected final int mLocalFieldId; // 数据对象绑定 id
private T mTarget; // 数据对象
public WeakListener(ViewDataBinding binder, int localFieldId,
ObservableReference<T> observable) {
super(binder, sReferenceQueue);
mLocalFieldId = localFieldId;
mObservable = observable; // WeakPropertyListener
}
public void setTarget(T object) {
unregister();
mTarget = object; // 记录数据对象 User
if (mTarget != null) {
// 调用 WeakPropertyListener 的 addListener
mObservable.addListener(mTarget);
}
}
...
}
public class BaseObservable implements Observable {
private transient PropertyChangeRegistry mCallbacks;
public BaseObservable() {
}
// OnPropertyChangedCallback 就是 WeakPropertyListener
// WeakPropertyListener 持有 WeakListener
// WeakListener 保存有 ViewDataBinding、数据对象绑定 id、数据对象
@Override
public void addOnPropertyChangedCallback(@NonNull OnPropertyChangedCallback callback) {
synchronized (this) {
if (mCallbacks == null) {
mCallbacks = new PropertyChangeRegistry();
}
}
mCallbacks.add(callback);
}
}
public class PropertyChangeRegistry extends
CallbackRegistry<Observable.OnPropertyChangedCallback, Observable, Void> {
}
public class CallbackRegistry<C, T, A> implements Cloneable {
// 存储 WeakPropertyListener 的列表
private List<C> mCallbacks = new ArrayList<C>();
public synchronized void add(C callback) {
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null");
}
int index = mCallbacks.lastIndexOf(callback);
if (index < 0 || isRemoved(index)) {
mCallbacks.add(callback);
}
}
}
- PropertyChangeRegistry/CallbackRegistry:为了方便理解,可以将它理解为 WeakListener 观察者列表
listener.setTarget(observable)
其实就是注册观察者的操作,将 WeakPropertyListener 存储到 CallbackRegistry 的观察者列表。
简单总结下 updateRegistration() 就做了两件事情:
-
为每一个需要绑定的数据对象提供一个数据对象绑定 id,创建 WeakListener,WeakListener 是 ViewDataBinding、数据对象绑定 id、数据对象 User 的包装类
-
创建 CallbackRegistry,把它理解为 Observable 被观察者,将 WeakListener 注册到列表存储
为了方便理解,我们用伪代码描述下 updateRegistration():
public class ActivityMainBindingImpl {
public void updateRegistration(User user) {
registerTo(0, user);
}
public void registerTo(int localFieldId, User user) {
WeakListener listener = new WeakListener(this, localFieldId);
listener.setTarget(user);
}
}
public class WeakListener {
private ViewDataBinding binder;
private int localFieldId;
private User user;
public WeakListener(ViewDataBinding binder, int localFieldId) {
this.binder = binder;
this.localFieldId = localFieldId;
}
public void setTarget(User user) {
user.addOnPropertyChangedCallback(this);
}
}
public class User extends BaseObservable {
// PropertyChangeRegistry/CallbackRegitry 本质上就是对这个观察者列表的操作
private List<WeakListener> mCallbacks;
// 其实就是注册观察者的操作
public void addOnPropertyChangedCallback(WeakListener callback) {
mCallbacks.add(callback);
}
}
具体流程如下:
刷新 UI 流程
在上面经过 updateRegistration() 完成了注册观察者的操作后,setUser() 的流程还没结束:
ActivityMainBindingImpl.java
public void setUser(@Nullable com.example.demo.User User) {
updateRegistration(0, User);
this.mUser = User;
synchronized(this) {
mDirtyFlags |= 0x1L;
}
notifyPropertyChanged(BR.user);
super.requestRebind();
}
紧接着修改了 mDirtyFlags,并且调用了 super.requestRebind():
protected void requestRebind() {
...
if (USE_CHOREOGRAPHER) {
mChoreographer.postFrameCallback(mFrameCallback);
} else {
mUIThreadHandler.post(mRebindRunnable);
}
}
protected ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount) {
...
if (USE_CHOREOGRAPHER) {
// 通过编舞者每 16.6ms 接收一个 VSYNC 信号刷新 UI
mChoreographer = Choreographer.getInstance();
mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
mRebindRunnable.run();
}
};
} else {
mFrameCallback = null;
mUIThreadHandler = new Handler(Looper.myLooper());
}
}
private final Runnable mRebindRunnable = new Runnable() {
@Override
public void run() {
...
executePendingBindings();
}
};
public void executePendingBindings() {
if (mContainingBinding == null) {
executeBindingsInternal();
} else {
mContainingBinding.executePendingBindings();
}
}
private void executeBindingsInternal() {
...
executeBindings();
...
}
ActivityMainBindingImpl.java
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
java.lang.String userName = null;
com.example.demo.User user = mUser;
// 判断 dirtyFlags 是否有修改
if ((dirtyFlags & 0x7L) != 0) {
if (user != null) {
// read user.name
userName = user.getName();
}
}
// batch finished
if ((dirtyFlags & 0x7L) != 0) {
// 更新 UI
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.textView, userName);
}
}
DataBinding 刷新 UI 的方式是通过注册编舞者 Choreographer,每 16.6ms 接收一个 VSYNC 信号,再结合 mDirtyFlags 判断数值是否有修改,如果需要更新在下一个 VSYNC 信号到来时刷新 UI。
通知更新 UI 流程
你可能有留意到在 setUser() 时我将 notifyPropertyChanged(BR.user)
忽略跳过了,其实是因为首次注册监听绑定时调用这句代码不会处理更新操作。但是我们数据更新通知刷新 UI 就是使用的该方法:
public class User extends BaseObservable {
private String name;
private String pwd;
public User(String name, String pwd) {
this.name = name;
this.pwd = pwd;
}
@Bindable
public String getName() {
return name;
}
@Bindable
public String getPwd() {
return pwd;
}
public void setName(String name) {
this.name = name;
// 通知更新 UI
notifyPropertyChanged(BR.name);
}
public void setPwd(String pwd) {
this.pwd = pwd;
// 通知更新 UI
notifyPropertyChanged(BR.pwd);
}
}
我们以 setName() 为例子讲解下 notifyPropertyChanged(id) 的原理:
BaseObservable.java
private transient PropertyChangeRegistry mCallbacks;
public void notifyPropertyChanged(int fieldId) {
synchronized (this) {
if (mCallbacks == null) {
return;
}
}
mCallbacks.notifyCallbacks(this, fieldId, null);
}
CallbackRegistry.java
public synchronized void notifyCallbacks(T sender, int arg, A arg2) {
...
// sender 是 User
// arg 是 BR.name
notifyRecurse(sender, arg, arg2);
...
}
private void notifyRecurse(T sender, int arg, A arg2) {
...
// sender 是 User
// arg 是 BR.name
notifyRemainder(sender, arg, arg2, remainderIndex);
...
}
private void notifyRemainder(T sender, int arg, A arg2, int remainderIndex) {
if (remainderIndex < 0) {
// sender 是 User
// arg 是 BR.name
notifyFirst64(sender, arg, arg2);
} else {
...
}
}
private void notifyFirst64(T sender, int arg, A arg2) {
final int maxNotified = Math.min(Long.SIZE, mCallbacks.size());
// sender 是 User
// arg 是 BR.name
notifyCallbacks(sender, arg, arg2, 0, maxNotified, mFirst64Removed);
}
private void notifyCallbacks(T sender, int arg, A arg2, final int startIndex,
final int endIndex, final long bits) {
long bitMask = 1;
for (int i = startIndex; i < endIndex; i++) {
if ((bits & bitMask) == 0) {
// sender 是 User
// arg 是 BR.name
// mCallbacks.get(i) 拿到 BR.name 对应的 WeakPropertyListener
// mNotifier 是 PropertyChangeRegistry 的 NOTIFIER_CALLBACK
mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
}
bitMask <<= 1;
}
}
mNotifier 是 PropertyChangeRegistry 通过构造传入的 NOTIFIER_CALLBACK:
CallbackRegistry.java
private final NotifierCallback<C, T, A> mNotifier;
public CallbackRegistry(NotifierCallback<C, T, A> notifier) {
mNotifier = notifier;
}
public class PropertyChangeRegistry extends
CallbackRegistry<Observable.OnPropertyChangedCallback, Observable, Void> {
private static final CallbackRegistry.NotifierCallback<Observable.OnPropertyChangedCallback, Observable, Void> NOTIFIER_CALLBACK = new CallbackRegistry.NotifierCallback<Observable.OnPropertyChangedCallback, Observable, Void>() {
@Override
public void onNotifyCallback(Observable.OnPropertyChangedCallback callback, Observable sender,
int arg, Void notUsed) {
// callback 是 WeakPropertyListener
// sender 是 User
// arg 是 BR.name
callback.onPropertyChanged(sender, arg);
}
};
public PropertyChangeRegistry() {
super(NOTIFIER_CALLBACK);
}
}
WeakPropertyListener.java
final WeakListener<Observable> mListener;
@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 是 ActivityMainBindingImpl
// sender 是 User
// propertyId 是 BR.name
binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
}
ViewDataBinding.java
private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
...
// mLocalFieldId = 0
// object 是 User
// fieldId 是 BR.name
boolean result = onFieldChange(mLocalFieldId, object, fieldId);
if (result) {
// 等待下一次 VSYNC 信号到来时刷新 UI
requestRebind();
}
}
ActivityMainBindingImpl.java
@Override
protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
switch (localFieldId) {
case 0 :
return onChangeUser((com.example.demo.User) object, fieldId);
}
return false;
}
private boolean onChangeUser(com.example.demo.User User, int fieldId) {
if (fieldId == BR._all) {
synchronized(this) {
mDirtyFlags |= 0x1L;
}
return true;
}
else if (fieldId == BR.name) {
synchronized(this) {
mDirtyFlags |= 0x2L; // 修改数值,下一个 VSYNC 信号到来时刷新 UI
}
return true;
}
return false;
}
根据上面的源码分析,notifyPropertyChanged() 做了以下事情:
-
根据数据对象绑定 id 从 CallbackRegistry 找到要通知的 WeakPropertyListener
-
WeakPropertyListener.onPropertyChanged() 拿着 WeakListener,WeakListener 对应一个数据对象,再根据 BR.name 找到 mDirtyFlags 修改数值
-
如果判断 mDirtyFlags 数据需要更新,则等待下一个 VSYNC 信号到来时执行 UI 刷新
具体流程如下:
总结
该篇文章从布局绑定、注册监听、数据驱动 UI 更新各个流程都做了详细的源码分析,根据上面的内容我们再做对各流程做一个总结梳理。
布局绑定流程 DataBindingUtils.setContentView():
-
通过 Activity 的 setContentView() 加载布局
-
根据布局根节点的 tag 属性 layout/activity_xxx_0 创建对应布局的 ViewDataBinding
-
ViewDataBinding 创建时遍历布局所有的 View 存到数组中,数组的 View 赋值给 ViewDataBinding 的控件变量。实现可以通过 binding 获取控件 View
注册监听流程 updateRegistration():
-
为每一个需要绑定的数据对象提供一个数据对象绑定 id,创建 WeakListener,WeakListener 是 ViewDataBinding、数据对象绑定 id、数据对象 User 的包装类
-
创建 CallbackRegistry,把它理解为 Observable,将 WeakListener 注册到观察者列表,完成观察者和被观察者的绑定
UI 刷新流程 requestRebind():
DataBinding 刷新 UI 的方式是通过注册编舞者 Choreographer,每 16.6ms 接收一个 VSYNC 信号,再结合 mDirtyFlags 判断数值是否有修改,如果需要更新在下一个 VSYNC 信号到来时刷新 UI。
数据驱动 UI 更新流程 notifyPropertyChanged():
-
根据数据对象绑定 id 从 CallbackRegistry 找到要通知的 WeakPropertyListener
-
WeakPropertyListener.onPropertyChanged() 拿着 WeakListener,WeakListener 对应一个数据对象,再根据 propertyId 找到 mDirtyFlags 修改数值
-
如果判断 mDirtyFlags 数据需要更新,则等待下一个 VSYNC 信号到来时执行 UI 刷新
我们将所有流程再梳理成伪代码,方便理解:
public class User extends BaseObservable {
// PropertyChangeRegistry/CallbackRegitry 本质上就是对这个观察者列表的操作
private List<WeakListener> mCallbacks;
// 注册观察者
public void addOnPropertyChangedCallback(WeakListener callback) {
mCallbacks.add(callback);
}
public void setName(String name) {
// 数据更新,通知刷新 UI
notifyPropertyChanged(BR.name);
}
public void notifyPropertyChanged(int propertyId) {
WeakListener listener = mCallbacks.get(0);
int localFieldId = listener.localFieldId;
boolean result = listener.binder.onFieldChanged(localFieldId, propertyId);
if (result) {
// 需要更新 UI,下一个 VSYNC 信号到来时执行 UI 刷新
listener.binder.requestRebind();
}
}
}
public class ActivityMainBindingImpl {
private User mUser;
private int mDirtyFlags;
// 注册监听流程
public void updateRegistration(User user) {
mUser = user;
registerTo(0, user);
}
public void registerTo(int localFieldId, User user) {
WeakListener listener = new WeakListener(this, localFieldId);
listener.setTarget(user);
}
// 收到数据更新需要刷新 UI 通知
public boolean onFieldChanged(int localFieldId, int propertyId) {
switch (localFieldId) {
case 0:
if (propertyId == BR.name) {
// 修改 mDirtyFlags 数值,下一个 VSYNC 信号刷新 UI
mDirtyFlags |= 0x2L;
return true;
}
}
return false;
}
// 编舞者刷新 UI
public void requestRebind() {
mChoreographer.postFrameCallback(mFrameCallback);
}
public ActivityMainBindingImpl() {
mChoreographer = Choreographer.getInstance();
mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
executeBindings();
}
};
}
public void executeBindings() {
if ((mDirtyFlags & 0x2L) != 0) {
textView.setText(mUser.getName());
}
}
}
public class WeakListener {
public ViewDataBinding binder;
public int localFieldId;
public WeakListener(ViewDataBinding binder, int localFieldId) {
this.binder = binder;
this.localFieldId = localFieldId;
}
// 注册监听,将观察者和被观察者绑定
public void setTarget(User user) {
user.addOnPropertyChangedCallback(this);
}
}