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图: