在开发中经常用到 DataBinding, 但自己对这一块的了解仅限于用法, 并未深入探究其内部工作原理, 导致踩了许多坑。便花时间研究了下 DataBinding 中核心的源码, 并撰写此文, 以帮助后来的读者能够通过本文快速了解 DataBinding 内部的一些机制。
1 用法回顾 编写继承自 BaseObservable 的 ViewModel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class ViewModel extends BaseObservable { @Bindable private String text; public String getText () { return text; } public void setText (String text) { this .text = text; notifyPropertyChanged(BR.text); } }
界面布局中书写表达式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <layout > <data > <variable name ="vm" type ="top.wuhaojie.databindingdemo.ViewModel" /> </data > <android.support.constraint.ConstraintLayout > <TextView android:id ="@+id/tv" size ="@{100}" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:text ="@{vm.text}" /> <EditText android:id ="@+id/et" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:text ="@={vm.text}" /> </android.support.constraint.ConstraintLayout > </layout >
主逻辑中:
1 2 3 4 binding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.activity_main, null , false ); binding.setVm(viewModel); viewModel.setText("hello" );
2 绑定 View 过程 2.1 编译期布局文件处理 DataBinding 带表达式的布局文件不能直接被 Android 处理,需要在编译期处理处理成普通的 Layout 布局:
原始的布局文件
生成后的布局文件
可以看到增加了 Tag 标签用来唯一标识 View,并且去除了表达式。
同时生成了原始文件的描述信息
2.2 寻找 ViewDataBinding DataBinding 用法中第一步就是调用 DataBindingUtil.inflate 方法,其对 inflate 后的 View 进行 bind,然后返回对应的 ViewDataBinding,通过寻找 Mapper 中的方法实现:
1 2 3 4 static <T extends ViewDataBinding> T bind (DataBindingComponent bindingComponent, View root, int layoutId) { return (T) sMapper.getDataBinder(bindingComponent, root, layoutId); }
sMapper 是个静态变量,在类开始初始化:
1 private static DataBinderMapper sMapper = new DataBinderMapperImpl();
而 DataBinderMapperImpl 是 在编译期生成的 Java 类,看看 getDataBinder() 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Override public ViewDataBinding getDataBinder (DataBindingComponent bindingComponent, View view, int layoutId) { switch (layoutId) { case R.layout.activity_main: { final Object tag = view.getTag(); if (tag == null ) throw new RuntimeException("view must have a tag" ); if ("layout/activity_main_0" .equals(tag)) { return new ActivityMainBinding(bindingComponent, view); } throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag); } } return null ; }
Tag 标签是在布局文件处理中生成的,由两个部分组成:前缀 + 下标序号。
2.3 更高效的 View 赋值 mapBindings 方法通过 RootView 来 find 所有的 View,返回到一个 View 数组:
1 2 3 4 5 6 protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root, int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) { Object[] bindings = new Object[numBindings]; mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true ); return bindings; }
进一步跟进实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 private static void mapBindings (..., View view, Object[] bindings, ... ) { ... if (isRoot && tag != null && tag.startsWith("layout" )) { ... if (bindings[index] == null ) { bindings[index] = view; } } else if (tag != null && tag.startsWith("binding_" )) { int tagIndex = parseTagInt(tag, BINDING_NUMBER_START); if (bindings[tagIndex] == null ) { bindings[tagIndex] = view; } ... } else { indexInIncludes = -1 ; } ... if (view instanceof ViewGroup) { for (int i = 0 ; i < count; i++) { ... final View child = viewGroup.getChildAt(i); if (childTag.endsWith("_0" ) && childTag.startsWith("layout" ) && childTag.indexOf('/' ) > 0 ) { ... } if (!isInclude) { mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false ); } } } }
完成此步后,Object[] bindings 数组中即按序放置好了布局中的 View,ViewDataBinding 中直接可以使用:
1 2 3 4 this .mboundView0 = (ConstraintLayout) bindings[0 ];this .mboundView0.setTag(null );this .tv = (android.widget.TextView) bindings[1 ];this .tv.setTag(null );
为什么说相对于 findViewById 效率更高?
因为 mapBindings 中对 View 寻找只会初次寻找并赋值一次,而 findViewById 会在 每次调用时 都从顶级 ViewGroup 递归遍历寻找 View
2.4 DirtyFlags 机制 DirtyFlags 是一个 64 位的 long 型数值,通过其每一个 bit 来标识一个成员变量数据是否发生了变化:
1 private long mDirtyFlags = 0xffffffffffffffffL ;
比如:
1 2 3 4 5 6 7 public void setVm (@Nullable ViewModel Vm) { this .mVm = Vm; synchronized (this ) { mDirtyFlags |= 0x4L ; } ... }
调用 setVM 函数时,将 mDirtyFlags 和 0x4L 进行 或运算 ,0x4L 的二进制为 00 …. 0000 0100,进行或运算后,即将 mDirtyFlags 的第三位 bit 置为 1
1 2 3 4 5 6 7 8 if ((dirtyFlags & 0x4L ) != 0 ) { if (vm != null ) { vmText = vm.getText(); } } if ((dirtyFlags & 0x4L ) != 0 ) { TextViewBindingAdapter.setText(this .tv, vmText); }
在更新变量的时候,则通过 mDirtyFlags 和 0x4L 进行 与运算 来判断该 bit 是否为 1,为 1 则说明需要刷新数据,再如判断是否有数据更新的 hasPendingBindings 方法:
1 2 3 4 5 6 7 8 9 @Override public boolean hasPendingBindings () { synchronized (this ) { if (mDirtyFlags != 0 ) { return true ; } } return false ; }
既然通过 bit 来标识,而 mDirtyFlags 是一个 64 位的 long 型变量,岂不是只能设置 64 种数据?
其实不然,mDirtyFlags 确实无法容纳大于 64 种数据,但是超过 64 种数据 DataBinding 为为其再分配 mDirtyFlags_1、mDirtyFlags_2 等变量继续扩容,如:
1 2 3 private long mDirtyFlags = 0xffffffffffffffffL ;private long mDirtyFlags_1 = 0xffffffffffffffffL ;private long mDirtyFlags_2 = 0xffffffffffffffffL ;
小于 mDirtyFlags 范围使用 mDirtyFlags,超出 mDirtyFlags 范围使用 mDirtyFlags_1,以此类推:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public void setA11 (@Nullable ViewModel A11) { updateRegistration(63 , A11); this .mA11 = A11; synchronized (this ) { mDirtyFlags |= 0x8000000000000000L ; } ... } public void setA35 (@Nullable ViewModel A35) { updateRegistration(64 , A35); this .mA35 = A35; synchronized (this ) { mDirtyFlags_1 |= 0x1L ; } ... }
2.5 绑定并更新数据 当设置数据后,如何让数据展示到界面,一切从 ViewDataBinding#requestRebind 方法说起:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 protected void requestRebind () { if (mContainingBinding != null ) { mContainingBinding.requestRebind(); } else { synchronized (this ) { if (mPendingRebind) { return ; } mPendingRebind = true ; } ... if (USE_CHOREOGRAPHER) { mChoreographer.postFrameCallback(mFrameCallback); } else { mUIThreadHandler.post(mRebindRunnable); } } }
mPendingRebind 起第一道风门作用,防止过度提交,需要等待 当前绑定数据的帧渲染完毕后 (或在 API < 16 时 主线程再次闲置 )才会再次 Rebind:
对于帧渲染回调,用的是 Choreographer,其在下一帧渲染信号时回调,回调后同样执行 mRebindRunnable 任务:
1 2 3 4 5 6 mFrameCallback = new Choreographer.FrameCallback() { @Override public void doFrame (long frameTimeNanos) { mRebindRunnable.run(); } };
mRebindRunnable 会在根布局 attach 到 window 的情况下执行 executePendingBindings 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private final Runnable mRebindRunnable = new Runnable() { @Override public void run () { synchronized (this ) { mPendingRebind = false ; } processReferenceQueue(); if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { if (!mRoot.isAttachedToWindow()) { mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER); mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER); return ; } } executePendingBindings(); } };
executePendingBindings 方法会再次判断是否存在父容器的 ViewDataBinding,存在则执行父容器的 executePendingBindings 方法,否则,执行执行真正的绑定方法 executeBindingsInternal:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 private void executeBindingsInternal () { if (mIsExecutingPendingBindings) { requestRebind(); return ; } if (!hasPendingBindings()) { return ; } mIsExecutingPendingBindings = true ; mRebindHalted = false ; if (mRebindCallbacks != null ) { mRebindCallbacks.notifyCallbacks(this , REBIND, null ); if (mRebindHalted) { mRebindCallbacks.notifyCallbacks(this , HALTED, null ); } } if (!mRebindHalted) { executeBindings(); if (mRebindCallbacks != null ) { mRebindCallbacks.notifyCallbacks(this , REBOUND, null ); } } mIsExecutingPendingBindings = false ; }
第二道风门由 mIsExecutingPendingBindings 变量控制,存在重复提交时,会将其重新导向 requestRebind,等待下一帧渲染时再执行。
最后由 executeBindings 实现方法完成数据的绑定:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Override protected void executeBindings () { long dirtyFlags = 0 ; synchronized (this ) { dirtyFlags = mDirtyFlags; mDirtyFlags = 0 ; } ViewModel vm = mVm; String vmText = null ; if ((dirtyFlags & 0x7L ) != 0 ) { if (vm != null ) { vmText = vm.getText(); } } if ((dirtyFlags & 0x7L ) != 0 ) { TextViewBindingAdapter.setText(this .tv, vmText); } }
3 VM 触发 View 过程 1 2 3 4 public void setVm (@Nullable ViewModel Vm) { updateRegistration(0 , Vm); ... }
在调用 ViewDataBinding 的 set 变量方法时都会执行 updateRegistration 方法,跟进到 registerTo 方法:
1 2 3 4 5 6 7 8 9 protected void registerTo (int localFieldId, Object observable, CreateWeakListener listenerCreator) { ... listener = listenerCreator.create(this , localFieldId); mLocalFieldObservers[localFieldId] = listener; if (mLifecycleOwner != null ) { listener.setLifecycleOwner(mLifecycleOwner); } listener.setTarget(observable); }
其中 listenerCreator.create 方法的实现为:
1 2 3 public WeakListener create (ViewDataBinding viewDataBinding, int localFieldId) { return new WeakPropertyListener(viewDataBinding, localFieldId).getListener(); }
来看 setTarget 方法,observable 参数即为我们的 ViewModel,即继承 BaseObservable 的类,跟进看它做了什么:
1 2 3 4 5 6 7 public void setTarget (T object) { unregister(); mTarget = object; if (mTarget != null ) { mObservable.addListener(mTarget); } }
来看 addListener 方法:
1 2 3 4 @Override public void addListener (Observable target) { target.addOnPropertyChangedCallback(this ); }
也就是说绕了一圈,为 Observable 类,即我们传入的 ViewModel,设置了属性回调监听,在 ViewModel 的成员属性发生变化时,都会回调 OnPropertyChangedCallback,继续跟进数据变化后会做什么事情:
1 2 3 4 5 6 @Override public void onPropertyChanged (Observable sender, int propertyId) { ViewDataBinding binder = mListener.getBinder(); ... binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId); }
WeakListener 的巧妙之处在于,其既持有了 ViewDataBinding,也持有 ViewModel 数据,这样在 ViewModel 变化时,通知 ViewDataBinding,ViewDataBinding 再触发 View 更新:
继续跟进则来到 ViewDataBinding 的 onFieldChange 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Override protected boolean onFieldChange (int localFieldId, Object object, int fieldId) { switch (localFieldId) { case 0 : return onChangeVm((top.wuhaojie.databindingdemo.ViewModel) object, fieldId); } return false ; } private boolean onChangeVm (top.wuhaojie.databindingdemo.ViewModel Vm, int fieldId) { if (fieldId == BR._all) { synchronized (this ) { mDirtyFlags |= 0x1L ; } return true ; } else if (fieldId == BR.text) { synchronized (this ) { mDirtyFlags |= 0x2L ; } return true ; } return false ; }
判断具体数据的变化,将 mDirtyFlags 置为对应的值,然后依照返回值执行 requestRebind 重新绑定以刷新 View:
1 2 3 4 5 6 7 private void handleFieldChange (int mLocalFieldId, Object object, int fieldId) { ... boolean result = onFieldChange(mLocalFieldId, object, fieldId); if (result) { requestRebind(); } }
4 View 触发 VM 过程 View 变化触发 VM 更新的过程比较简单,就是 DataBinding 帮助开发者自动生成了为 View 控件设置变化监听的代码,以 EditText 控件为例:
1 2 3 4 5 6 7 8 9 10 @Override protected void executeBindings () { ... if ((dirtyFlags & 0x7L ) != 0 ) { TextViewBindingAdapter.setText(this .et, vmText); } if ((dirtyFlags & 0x4L ) != 0 ) { TextViewBindingAdapter.setTextWatcher(this .et, null , null , null , etandroidTextAttrChanged); } }
在控件回调方法中,对 ViewModel 进行更新值的操作:
1 2 3 4 5 6 @Override public void onChange () { String callbackArg_0 = TextViewBindingAdapter.getTextString(et); ... vm.setText(((java.lang.String) (callbackArg_0))); }
5 回调管理 DataBinding 中的回调管理统一采用 CallbackRegistry,其具备 回调时可重入修改 和 延迟自动回收 两大特性。
5.1 标记逻辑 采用一个 long 型变量和一个 long[] 数组变量来对回调器的状态进行标记:
1 2 private long mFirst64Removed = 0x0 ;private long [] mRemainderRemoved;
mFirst64Removed 变量用于对数目小于 64 的回调器进行快速标记:
其中每一个 bit 对应一个回调器的状态,0 表示未被移除,1 表示已被移除。而对于数目大于 64 的情况,采用 mRemainderRemoved 这种 long 型数组的形式:
由此可以计算第 n 个 bit 元素的位置,行数为 n / 64 - 1,列数为 n % 64,故该 bit 的值为 a [ n / 64 - 1 ] & ( 1L << ( n % 64 ) ),明白此一点对后续理解代码有帮助。
明白标记逻辑后,再来看代码中对 isRemoved 方法的实现就很好理解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private boolean isRemoved (int index) { if (index < Long.SIZE) { final long bitMask = 1L << index; return (mFirst64Removed & bitMask) != 0 ; } else if (mRemainderRemoved == null ) { return false ; } else { final int maskIndex = (index / Long.SIZE) - 1 ; if (maskIndex >= mRemainderRemoved.length) { return false ; } else { final long bits = mRemainderRemoved[maskIndex]; final long bitMask = 1L << (index % Long.SIZE); return (bits & bitMask) != 0 ; } } }
再来看设置移除标记位的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private void setRemovalBit (int index) { if (index < Long.SIZE) { final long bitMask = 1L << index; mFirst64Removed |= bitMask; } else { final int remainderIndex = (index / Long.SIZE) - 1 ; if (mRemainderRemoved == null ) { mRemainderRemoved = new long [mCallbacks.size() / Long.SIZE]; } else if (mRemainderRemoved.length < remainderIndex) { long [] newRemainders = new long [mCallbacks.size() / Long.SIZE]; System.arraycopy(mRemainderRemoved, 0 , newRemainders, 0 , mRemainderRemoved.length); mRemainderRemoved = newRemainders; } final long bitMask = 1L << (index % Long.SIZE); mRemainderRemoved[remainderIndex] |= bitMask; } }
mRemainderRemoved 中最后一个 bit 的序号不一定等于 mCallbacks.size,因为 mRemainderRemoved 只会在设置标记移除的时候才会扩容到当前的 mCallbacks.size
5.2 添加回调逻辑 1 private List<C> mCallbacks = new ArrayList<C>();
回调器都被添加到 List 中,List 的下标同时为移除标记中的 bit 位置:
1 2 3 4 5 6 7 8 9 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); } }
5.3 移除逻辑 移除回调器分为两种情况,一种是在闲置状态,可以直接移除回调,另一种是正在进行回调通知状态,此时只能标记移除,即「 假移除 」:
1 2 3 4 5 6 7 8 9 10 11 12 public synchronized void remove (C callback) { if (mNotificationLevel == 0 ) { mCallbacks.remove(callback); } else { int index = mCallbacks.lastIndexOf(callback); if (index >= 0 ) { setRemovalBit(index); } } }
mNotificationLevel 控制可重入的回调通知层级,为 0 时表示回调通知执行完毕。而在每一次回调完成后,才会真正清理 回调通知进行过程中 被「 假移除 」的回调器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public synchronized void notifyCallbacks (T sender, int arg, A arg2) { ... if (mNotificationLevel == 0 ) { if (mRemainderRemoved != null ) { for (int i = mRemainderRemoved.length - 1 ; i >= 0 ; i--) { final long removedBits = mRemainderRemoved[i]; if (removedBits != 0 ) { removeRemovedCallbacks((i + 1 ) * Long.SIZE, removedBits); mRemainderRemoved[i] = 0 ; } } } if (mFirst64Removed != 0 ) { removeRemovedCallbacks(0 , mFirst64Removed); mFirst64Removed = 0 ; } } }
removeRemovedCallbacks 的参数 (i + 1) * Long.SIZE 中 i + 1 的原因是:mRemainderRemoved 的范围是 >= 64,小于 64 的交给 mFirst64Removed 处理,避免 i = 0 的仍进行处理
1 2 3 4 5 6 7 8 9 10 11 12 13 private void removeRemovedCallbacks (int startIndex, long removed) { final int endIndex = startIndex + Long.SIZE; long bitMask = 1L << (Long.SIZE - 1 ); for (int i = endIndex - 1 ; i >= startIndex; i--) { if ((removed & bitMask) != 0 ) { mCallbacks.remove(i); } bitMask >>>= 1 ; } }
5.4 递归回调 1 2 3 4 5 6 public synchronized void notifyCallbacks (T sender, int arg, A arg2) { mNotificationLevel++; notifyRecurse(sender, arg, arg2); mNotificationLevel--; ... }
notifyCallbacks 方法是可重入调用的,mNotificationLevel 则用来控制当前重入的次数,继续跟进 notifyRecurse 方法:
1 2 3 4 5 6 7 8 9 10 11 12 private void notifyRecurse (T sender, int arg, A arg2) { final int callbackCount = mCallbacks.size(); final int remainderIndex = mRemainderRemoved == null ? -1 : mRemainderRemoved.length - 1 ; notifyRemainder(sender, arg, arg2, remainderIndex); final int startCallbackIndex = (remainderIndex + 2 ) * Long.SIZE; notifyCallbacks(sender, arg, arg2, startCallbackIndex, callbackCount, 0 ); }
该方法是将 mCallbacks 分成两个部分进行通知处理:
第一部分,标记有 RemoveBit 的部分,即最大范围为标记 bit 时 mCallbacks 的大小;
第二部分,剩余部分,该部分从未标记过 bit,可以全部进行通知;
1 2 3 4 5 6 7 8 9 10 11 12 13 private void notifyRemainder (T sender, int arg, A arg2, int remainderIndex) { if (remainderIndex < 0 ) { notifyFirst64(sender, arg, arg2); } else { final long bits = mRemainderRemoved[remainderIndex]; final int startIndex = (remainderIndex + 1 ) * Long.SIZE; final int endIndex = Math.min(mCallbacks.size(), startIndex + Long.SIZE); notifyRemainder(sender, arg, arg2, remainderIndex - 1 ); notifyCallbacks(sender, arg, arg2, startIndex, endIndex, bits); } }
最后按照每一行右边低位到左边高位的顺序进行通知:
1 2 3 4 5 6 7 8 9 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 ) { mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2); } bitMask <<= 1 ; } }
6 BindAdapter 工作流程 @ BindAdapter 注解可以让开发者自定义布局属性值完成自定义的 setter 方法,看起来像 Hook 了方法,其实很简单,答案在生成的 ViewDataBinding 的执行绑定方法中:
1 2 3 4 5 6 7 @Override protected void executeBindings () { ... if ((dirtyFlags & 0x4L ) != 0 ) { Adapters.setSize(this .tv, 100 ); } }
7 和 LiveData 协作 跟进 executeBindings 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Override protected void executeBindings () { ... if ((dirtyFlags & 0x7L ) != 0 ) { if (vm != null ) { vmText = vm.text; } updateLiveDataRegistration(0 , vmText); if (vmText != null ) { vmTextGetValue = vmText.getValue(); } } }
原理都一致,在「 桥梁 」中为 ViewModel 设置监听,然后通知 ViewDataBinding,再刷新 View,来看「 桥梁 」:
1 2 3 4 5 6 @Override public void addListener (LiveData<?> target) { if (mLifecycleOwner != null ) { target.observe(mLifecycleOwner, this ); } }
数据变化后回调:
1 2 3 4 5 @Override public void onChanged (@Nullable Object o) { ViewDataBinding binder = mListener.getBinder(); binder.handleFieldChange(mListener.mLocalFieldId, mListener.getTarget(), 0 ); }
8 最后几句 至此,应该能回答以下问题:
1、View 的初始化也是像 ButterKnife 一样用 APT 批量帮我们写了 findViewById 方法吗?
2、BaseObservable#addOnPropertyChangedCallback 可以重复添加吗?remove 时如果正在执行过程会不会影响到界面渲染等其它操作?
3、ViewModel 的数据更新快速变化,如 1s 内数据从 1 递增到 100000,OnPropertyChangedCallback 会被回调 100000 次吗?界面会被刷新 100000 次吗?如果不是,数据如何背压丢弃?
4、@ BindingAdapter 自定义属性对系统方法进行 Hook 了吗?会不会影响性能?
5、View 变化是如何让 ViewModel 对应变化的?