Jetpack源码解析四之Data Binding Data Binding(数据绑定库)是一种支持库,借助该库,您可以使用声明性格式 (而非程序化地)将布局中的界面组件绑定到应用中的数据源。
所谓声明式UI,就是你在代码中做出的任何改变,都会实时的在界面中展示出来。与之对应的是命令式UI,当你想要改变界面时,必须调用XX.setText()之类的代码,才能使界面做出改变。
声明式/命令式 用传统的命令式UI,当要改变数据时,要如下操作:
1 2 3 findViewById<TextView>(R.id.sample_text).apply { text = viewModel.userName }
而用数据绑定后,只需在xml中声明如下,无需任何逻辑代码,当username变化时,组件自动发生变化。
1 2 <TextView android:text ="@{viewmodel.userName}" />
Data Binding并不仅仅是为了取代findViewById()
。如果你的主要目的是取代 findViewById()
调用,请考虑改用视图绑定 【View Binding】 或者 直接用Kotlin。
Data Binding 使用 当然,如果直接在界面上这样写是没用的,得配置下。app:build.gradle中开启:
1 2 3 dataBinding { enabled = true }
xml界面更改为如下样式即可:
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 <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" xmlns:tools ="http://schemas.android.com/tools" > <data > <variable name ="msg" type ="String" /> </data > <androidx.constraintlayout.widget.ConstraintLayout android:layout_width ="match_parent" android:layout_height ="match_parent" tools:context =".MainActivity" > <TextView android:id ="@+id/text" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="@{msg,default = `Hello World`}" app:layout_constraintBottom_toBottomOf ="parent" app:layout_constraintLeft_toLeftOf ="parent" app:layout_constraintRight_toRightOf ="parent" app:layout_constraintTop_toTopOf ="parent" /> </androidx.constraintlayout.widget.ConstraintLayout > </layout >
在Activity界面中获取binding,直接操作即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class MainActivity : AppCompatActivity () { override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) val binding: ActivityMainBinding = DataBindingUtil.setContentView( this , R.layout.activity_main) Thread{ var i = 0 while (true ){ Thread.sleep(1000 ) binding.msg = i++.toString() } }.start() } }
原理 主要是用到了APT(Annotation Processing Tool/注解处理工具)技术。在编译时对注解进行解析,自动生成代码,并编译代码生成class文件。
在项目的 build 目录下,可以看到生成的代码文件:
build/generated/source
build/intermediates/data_binding_xx
先看我们自己写的xml文件。像这种layout标签,不开启dataBinding的情况下编译器是铁定不识别的。所以我们有理由相信dataBinding后面肯定是将这个xml文件处理成了传统的xml布局格式。在build/intermediates/incremental/mergeDebugResources/stripped.dir/layout目录下,可以找到一个activity_main.xml文件,这个文件就是处理完后的xml文件了。
activity_main.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width ="match_parent" android:layout_height ="match_parent" tools:context =".MainActivity" android:tag ="layout/activity_main_0" xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" xmlns:tools ="http://schemas.android.com/tools" > <TextView android:id ="@+id/text" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="Hello World" app:layout_constraintBottom_toBottomOf ="parent" app:layout_constraintLeft_toLeftOf ="parent" app:layout_constraintRight_toRightOf ="parent" app:layout_constraintTop_toTopOf ="parent" android:tag ="binding_1" /> </androidx.constraintlayout.widget.ConstraintLayout >
这个文件和我们自己写的布局文件后半部分很像,就只有一点不一样:在有用到dataBinding的地方,多了个tag标签 –android:tag="binding_1"
。那前半部分标签去哪了呢?在第二张截图所展示目录下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?xml version="1.0" encoding="utf-8" standalone="yes"?> <Layout layout ="activity_main" modulePackage ="com.cy.myapplication" filePath ="app\src\main\res\layout\activity_main.xml" directory ="layout" isMerge ="false" isBindingData ="true" rootNodeType ="androidx.constraintlayout.widget.ConstraintLayout" > <Variables declared ="true" type ="String" name ="msg" > <location startLine ="6" startOffset ="8" endLine ="8" endOffset ="27" /> </Variables > <Targets > <Target tag ="layout/activity_main_0" view ="androidx.constraintlayout.widget.ConstraintLayout" > <Expressions /> <location startLine ="11" startOffset ="4" endLine ="26" endOffset ="55" /> </Target > <Target id ="@+id/text" tag ="binding_1" view ="TextView" > <Expressions > <Expression text ="msg,default = `Hello World`" attribute ="android:text" > <Location startLine ="20" startOffset ="12" endLine ="20" endOffset ="56" /> <TwoWay > false</TwoWay > <ValueLocation startLine ="20" startOffset ="28" endLine ="20" endOffset ="54" /> </Expression > </Expressions > <location startLine ="16" startOffset ="8" endLine ="24" endOffset ="55" /> </Target > </Targets > </Layout >
通过和我们自己写的xml对比着来看,可以看到中的信息是完全转移到了这个xml中的;原布局文件中用到了哪些布局,布局中又引入了哪些variable中的变量,也可以在xml中的Target–Expression下找得到。所以这两份文件可以包含我们所写的xml中的所有信息了。
DataBindingUtil 再看我们的Java/Kotlin代码。DataBindingUtil.setContentView():
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 private static DataBindingComponent sDefaultComponent = null ;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(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 ) { final View childView = parent.getChildAt(endChildren - 1 ); return bind(component, childView, layoutId); } else { final View[] children = new View[childrenAdded]; for (int i = 0 ; i < childrenAdded; i++) { children[i] = parent.getChildAt(i + startChildren); } return bind(component, children, layoutId); } }
先调用activity.setContentView(layoutId);
;然后获取decorView中的contentView,将contentView的子view传入bind方法中去【如果多个子view就用数组】。这个contentView中包裹的就是我们自己写的布局,所以第一次进入childCount为1,走第一个方法。 不过后面我还是把传view和传view[]这两个方法代码都贴上来了,注意区分。
1 2 3 4 5 6 7 8 9 10 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); } static <T extends ViewDataBinding> T bind (DataBindingComponent bindingComponent, View root, int layoutId) { return (T) sMapper.getDataBinder(bindingComponent, root, layoutId); }
bind方法又转到了sMapper的getDataBinder方法中去。sMapper是个DataBinderMapper对象。
DataBinderMapper 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class DataBinderMapperImpl extends MergedDataBinderMapper { DataBinderMapperImpl() { addMapper(new com.cy.myapplication.DataBinderMapperImpl()); } } public class MergedDataBinderMapper extends DataBinderMapper { public void addMapper (DataBinderMapper mapper) { Class<? extends DataBinderMapper> mapperClass = mapper.getClass(); if (mExistingMappers.add(mapperClass)) { mMappers.add(mapper); final List<DataBinderMapper> dependencies = mapper.collectDependencies(); for (DataBinderMapper dependency : dependencies) { addMapper(dependency); } } } }
DataBinderMapperImpl
继承MergedDataBinderMapper
继承DataBinderMapper
。DataBinderMapperImpl
就一个构造方法,getDataBinder方法的实现在MergedDataBinderMapper
中。
MergedDataBinderMapper
中又持有一个DataBinderMapper的数组:mMappers。该对象在new DataBinderMapperImpl的时候被填充进去数据,这个数据就是APT为我们生成的类。
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 public class MergedDataBinderMapper extends DataBinderMapper { private List<DataBinderMapper> mMappers = new CopyOnWriteArrayList<>(); @Override public ViewDataBinding getDataBinder (DataBindingComponent bindingComponent, View view, int layoutId) { for (DataBinderMapper mapper : mMappers) { ViewDataBinding result = mapper.getDataBinder(bindingComponent, view, layoutId); if (result != null ) { return result; } } if (loadFeatures()) { return getDataBinder(bindingComponent, view, layoutId); } return null ; } @Override public ViewDataBinding getDataBinder (DataBindingComponent bindingComponent, View[] view, int layoutId) { for (DataBinderMapper mapper : mMappers) { ViewDataBinding result = mapper.getDataBinder(bindingComponent, view, layoutId); if (result != null ) { return result; } } if (loadFeatures()) { return getDataBinder(bindingComponent, view, layoutId); } return null ; } }
所以不出所料的,MergedDataBinderMapper中的getDataBinder方法最终转交给了APT生成的Mapper中的getDataBinder。
APT-MergedDataBinderMapper 所以来到APT生成的MergedDataBinderMapper中:
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 37 38 39 40 41 42 43 44 45 46 47 private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(1 );static { INTERNAL_LAYOUT_ID_LOOKUP.put(com.cy.myapplication.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: { if ("layout/activity_main_0" .equals(tag)) { return new ActivityMainBindingImpl(component, view); } throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag); } } } return null ; } @Override public ViewDataBinding getDataBinder (DataBindingComponent component, View[] views, int layoutId) { if (views == null || views.length == 0 ) { return null ; } int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId); if (localizedLayoutId > 0 ) { final Object tag = views[0 ].getTag(); if (tag == null ) { throw new RuntimeException("view must have a tag" ); } switch (localizedLayoutId) { } } return null ; }
到这里第一次进入传来的view就是带Tag的activity_main.xml,所以tag能对的上,返回的就是ActivityMainBindingImpl(component, view);
对了,到这的时候,component = null,view是Activity_main中的最外层view。
APT-ActivityMainBindingImpl 首先,这个类也是APT帮我们生成的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Nullable private static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes;@Nullable private static final android.util.SparseIntArray sViewsWithIds;static { sIncludes = null ; sViewsWithIds = null ; } public ActivityMainBindingImpl (@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) { this (bindingComponent, root, mapBindings(bindingComponent, root, 2 , sIncludes, sViewsWithIds)); } private ActivityMainBindingImpl (androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) { super (bindingComponent, root, 0 , (android.widget.TextView) bindings[1 ] ); this .mboundView0 = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0 ]; this .mboundView0.setTag(null ); this .text.setTag(null ); setRootTag(root); invalidateAll(); }
mapBindings 这里用到了一个方法生成Object[]:由于mapBinding方法略长,所以没打算贴出来。
这个方法相当于是对我们传入的这个view进行一次解析,解析完成后返回的Object[]数组里面包含的就是这个view中所有的view对象。
然后在初始化方法中setTag中将数组中的对象打上标签tag并存起来。比如this.text就是我们布局文件中的textView,其id叫text
。所以我们能通过binding.text获得该控件。
APT-setMsg() 1 2 3 4 5 6 7 8 9 10 11 12 13 public void setMsg (@Nullable java.lang.String Msg) { this .mMsg = Msg; synchronized (this ) { mDirtyFlags |= 0x1L ; } notifyPropertyChanged(BR.msg); super .requestRebind(); } private long mDirtyFlags = 0xffffffffffffffffL ;
这个mDirtyFlags值是后面用作判断修改哪个值用的。然后跟着notifyPropertyChanged()方法;
1 2 3 4 5 6 7 8 public void notifyPropertyChanged (int fieldId) { synchronized (this ) { if (mCallbacks == null ) { return ; } } mCallbacks.notifyCallbacks(this , fieldId, null ); }
CallbackRegistry.notifyCallbacks() 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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 public synchronized void notifyCallbacks (T sender, int arg, A arg2) { mNotificationLevel++; notifyRecurse(sender, arg, arg2); mNotificationLevel--; 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 ; } } } 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 ); } 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); } } private void notifyFirst64 (T sender, int arg, A arg2) { final int maxNotified = Math.min(Long.SIZE, mCallbacks.size()); 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 ) { mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2); } bitMask <<= 1 ; } }
PropertyChangeRegistry.NotifierCallback.onNotifyCallback 1 2 3 4 5 private static final NotifierCallback<OnPropertyChangedCallback, Observable, Void> NOTIFIER_CALLBACK = new NotifierCallback<OnPropertyChangedCallback, Observable, Void>() { public void onNotifyCallback (OnPropertyChangedCallback callback, Observable sender, int arg, Void notUsed) { callback.onPropertyChanged(sender, arg); } };
ViewBinding.onPropertyChanged 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 public void onPropertyChanged (Observable sender, int propertyId) { ViewDataBinding binder = mListener.getBinder(); if (binder == null ) { return ; } Observable obj = mListener.getTarget(); if (obj != sender) { return ; } binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId); } private void handleFieldChange (int mLocalFieldId, Object object, int fieldId) { if (mInLiveDataRegisterObserver) { return ; } boolean result = onFieldChange(mLocalFieldId, object, fieldId); if (result) { requestRebind(); } }
这里会调用到onFieldChange方法,这个方法实现在imp类中:
1 2 3 4 5 6 @Override protected boolean onFieldChange (int localFieldId, Object object, int fieldId) { switch (localFieldId) { } return false ; }
返回true则认为数据发生了改变,走requestRebind方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 protected void requestRebind () { if (mContainingBinding != null ) { mContainingBinding.requestRebind(); } else { final LifecycleOwner owner = this .mLifecycleOwner; if (owner != null ) { Lifecycle.State state = owner.getLifecycle().getCurrentState(); if (!state.isAtLeast(Lifecycle.State.STARTED)) { return ; } } synchronized (this ) { if (mPendingRebind) { return ; } mPendingRebind = true ; } if (USE_CHOREOGRAPHER) { mChoreographer.postFrameCallback(mFrameCallback); } else { mUIThreadHandler.post(mRebindRunnable); } } }
最后要么走postFrameCallback,要么走mUIThreadHandler.post方法。其实两个方法是一样的,最终都是走的post方法中来,所以这里直接看mRebindRunnable。
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 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(); } }; public void executePendingBindings () { if (mContainingBinding == null ) { executeBindingsInternal(); } else { mContainingBinding.executePendingBindings(); } }
跟着executePendingBindings()
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 ; }
其中的executeBindings是抽象方法,实现在ActivityMainbindingImpl中:
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 @Override protected void executeBindings () { long dirtyFlags = 0 ; synchronized (this ) { dirtyFlags = mDirtyFlags; mDirtyFlags = 0 ; } com.cy.myapplication.Girl girl = mGirl; java.lang.String girlName = null ; java.lang.String msg = mMsg; if ((dirtyFlags & 0x5L ) != 0 ) { if (girl != null ) { girlName = girl.getName(); } } if ((dirtyFlags & 0x6L ) != 0 ) { } if ((dirtyFlags & 0x6L ) != 0 ) { androidx.databinding.adapters.TextViewBindingAdapter.setText(this .text, msg); } if ((dirtyFlags & 0x5L ) != 0 ) { androidx.databinding.adapters.TextViewBindingAdapter.setText(this .text2, girlName); } }
看到这里便真相大白了。我们所苦苦寻找的更改界面的方法就在这里了。
最后附上一副整体的UML图: