Jetpack源码解析(四)之Data Binding

Data Binding(数据绑定库)是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。

所谓声明式UI,就是你在代码中做出的任何改变,都会实时的在界面中展示出来。与之对应的是命令式UI,当你想要改变界面时,必须调用XX.setText()之类的代码,才能使界面做出改变。

声明式/命令式

用传统的命令式UI,当要改变数据时,要如下操作:

    findViewById(R.id.sample_text).apply {
        text = viewModel.userName
    }

而用数据绑定后,只需在xml中声明如下,无需任何逻辑代码,当username变化时,组件自动发生变化。


Data Binding并不仅仅是为了取代findViewById()。如果你的主要目的是取代 findViewById() 调用,请考虑改用视图绑定【View Binding】 或者 直接用Kotlin。

Data Binding 使用

当然,如果直接在界面上这样写是没用的,得配置下。app:build.gradle中开启:

dataBinding {
    enabled = true
}

xml界面更改为如下样式即可:




    
        
    

    

        

    

在Activity界面中获取binding,直接操作即可:

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)
                //听说异步线程不能更新UI?sorry,我可以。因为我只改变了值,没有调用`更新UI`代码~
                binding.msg = i++.toString()
            }
        }.start()

    }
}

原理

主要是用到了APT(Annotation Processing Tool/注解处理工具)技术。在编译时对注解进行解析,自动生成代码,并编译代码生成class文件。

在项目的 build 目录下,可以看到生成的代码文件:

build/generated/source build/intermediates/data_binding_xx
image-20210101133756494.png
image-20210101134221268.png

先看我们自己写的xml文件。像这种layout标签,不开启dataBinding的情况下编译器是铁定不识别的。所以我们有理由相信dataBinding后面肯定是将这个xml文件处理成了传统的xml布局格式。在build/intermediates/incremental/mergeDebugResources/stripped.dir/layout目录下,可以找到一个activity_main.xml文件,这个文件就是处理完后的xml文件了。

activity_main.xml




    


         

这个文件和我们自己写的布局文件后半部分很像,就只有一点不一样:在有用到dataBinding的地方,多了个tag标签--android:tag="binding_1"。那前半部分标签去哪了呢?在第二张截图所展示目录下:



    
        
    
    
        
            
            
        
        
            
                
                    
                    false
                    
                
            
            
        
    

通过和我们自己写的xml对比着来看,可以看到中的信息是完全转移到了这个xml中的;原布局文件中用到了哪些布局,布局中又引入了哪些variable中的变量,也可以在xml中的Target--Expression下找得到。所以这两份文件可以包含我们所写的xml中的所有信息了。

DataBindingUtil

再看我们的Java/Kotlin代码。DataBindingUtil.setContentView():

private static DataBindingComponent sDefaultComponent = null;

public static  T setContentView(@NonNull Activity activity, int layoutId) {
    return setContentView(activity, layoutId, sDefaultComponent);
}
public static  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 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[]这两个方法代码都贴上来了,注意区分。

private static DataBinderMapper sMapper = new DataBinderMapperImpl();
static  T bind(DataBindingComponent bindingComponent, View[] roots,
        int layoutId) {
    return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
}

static  T bind(DataBindingComponent bindingComponent, View root,
        int layoutId) {
    return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
}

bind方法又转到了sMapper的getDataBinder方法中去。sMapper是个DataBinderMapper对象。

DataBinderMapper

public class DataBinderMapperImpl extends MergedDataBinderMapper {
  DataBinderMapperImpl() {
    addMapper(new com.cy.myapplication.DataBinderMapperImpl());
  }
}

public class MergedDataBinderMapper extends DataBinderMapper {
    public void addMapper(DataBinderMapper mapper) {
        Class mapperClass = mapper.getClass();
        if (mExistingMappers.add(mapperClass)) {
            mMappers.add(mapper);
            final List dependencies = mapper.collectDependencies();
            for(DataBinderMapper dependency : dependencies) {
                addMapper(dependency);
            }
        }
    }
}

DataBinderMapperImpl继承MergedDataBinderMapper继承DataBinderMapperDataBinderMapperImpl就一个构造方法,getDataBinder方法的实现在MergedDataBinderMapper中。

MergedDataBinderMapper中又持有一个DataBinderMapper的数组:mMappers。该对象在new DataBinderMapperImpl的时候被填充进去数据,这个数据就是APT为我们生成的类。

public class MergedDataBinderMapper extends DataBinderMapper {
    private List 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中:

//setContentView(activity, layoutId, sDefaultComponent);
//bindToAddedViews(bindingComponent, contentView, 0, layoutId);
//bind(component, contentView‘s children, layoutId);
//上面是之前一路走来的用到的方法,写这里免得大家忘了。

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帮我们生成的。

@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);
    // listeners
    invalidateAll();
}

mapBindings

这里用到了一个方法生成Object[]:由于mapBinding方法略长,所以没打算贴出来。

这个方法相当于是对我们传入的这个view进行一次解析,解析完成后返回的Object[]数组里面包含的就是这个view中所有的view对象。

image-20210101210706060.png

然后在初始化方法中setTag中将数组中的对象打上标签tag并存起来。比如this.text就是我们布局文件中的textView,其id叫text。所以我们能通过binding.text获得该控件。

APT-setMsg()

public void setMsg(@Nullable java.lang.String Msg) {
    this.mMsg = Msg;
    synchronized(this) {
        mDirtyFlags |= 0x1L;
    }
    notifyPropertyChanged(BR.msg);
    super.requestRebind();
}
private  long mDirtyFlags = 0xffffffffffffffffL;
/* flag mapping
   flag 0 (0x1L): msg
   flag 1 (0x2L): null
flag mapping end*/

这个mDirtyFlags值是后面用作判断修改哪个值用的。然后跟着notifyPropertyChanged()方法;

public void notifyPropertyChanged(int fieldId) {
    synchronized (this) {
        if (mCallbacks == null) {
            return;
        }
    }
    mCallbacks.notifyCallbacks(this, fieldId, null);
}

CallbackRegistry.notifyCallbacks()

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;

    // Now we've got all callbakcs that have no mRemainderRemoved value, so notify the
    // others.
    //----------------------------看这个方法!!!-------------
    notifyRemainder(sender, arg, arg2, remainderIndex);

    // notifyRemainder notifies all at maxIndex, so we'd normally start at maxIndex + 1
    // However, we must also keep track of those in mFirst64Removed, so we add 2 instead:
    final int startCallbackIndex = (remainderIndex + 2) * Long.SIZE;

    // The remaining have no bit set
    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

private static final NotifierCallback NOTIFIER_CALLBACK = new NotifierCallback() {
    public void onNotifyCallback(OnPropertyChangedCallback callback, Observable sender, int arg, Void notUsed) {
        callback.onPropertyChanged(sender, arg);
    }
};

ViewBinding.onPropertyChanged

@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);
}
private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
    if (mInLiveDataRegisterObserver) {
        // We're in LiveData registration, which always results in a field change
        // that we can ignore. The value will be read immediately after anyway, so
        // there is no need to be dirty.
        return;
    }
    boolean result = onFieldChange(mLocalFieldId, object, fieldId);
    if (result) {
        requestRebind();
    }
}

这里会调用到onFieldChange方法,这个方法实现在imp类中:

@Override
protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
    switch (localFieldId) {
    }
    return false;
}

返回true则认为数据发生了改变,走requestRebind方法:

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; // wait until lifecycle owner is started
            }
        }
        synchronized (this) {
            if (mPendingRebind) {
                return;
            }
            mPendingRebind = true;
        }
        if (USE_CHOREOGRAPHER) {
            mChoreographer.postFrameCallback(mFrameCallback);
        } else {
            mUIThreadHandler.post(mRebindRunnable);
        }
    }
}

最后要么走postFrameCallback,要么走mUIThreadHandler.post方法。其实两个方法是一样的,最终都是走的post方法中来,所以这里直接看mRebindRunnable。

private final Runnable mRebindRunnable = new Runnable() {
    @Override
    public void run() {
        synchronized (this) {
            mPendingRebind = false;
        }
        processReferenceQueue();

        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();
    }
};

public void executePendingBindings() {
    if (mContainingBinding == null) {
        executeBindingsInternal();
    } else {
        mContainingBinding.executePendingBindings();
    }
}

跟着executePendingBindings()

private void executeBindingsInternal() {
    if (mIsExecutingPendingBindings) {
        requestRebind();
        return;
    }
    if (!hasPendingBindings()) {
        return;
    }
    mIsExecutingPendingBindings = true;
    mRebindHalted = false;
    if (mRebindCallbacks != null) {
        mRebindCallbacks.notifyCallbacks(this, REBIND, null);

        // The onRebindListeners will change mPendingHalted
        if (mRebindHalted) {
            mRebindCallbacks.notifyCallbacks(this, HALTED, null);
        }
    }
    if (!mRebindHalted) {
        //----------------看这里!!!!!!!!!
        executeBindings();
        if (mRebindCallbacks != null) {
            mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
        }
    }
    mIsExecutingPendingBindings = false;
}

其中的executeBindings是抽象方法,实现在ActivityMainbindingImpl中:

@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) {
                // read girl.name
                girlName = girl.getName();
            }
    }
    if ((dirtyFlags & 0x6L) != 0) {
    }
    // batch finished
    if ((dirtyFlags & 0x6L) != 0) {
        // api target 1

        androidx.databinding.adapters.TextViewBindingAdapter.setText(this.text, msg);
    }
    if ((dirtyFlags & 0x5L) != 0) {
        // api target 1

        androidx.databinding.adapters.TextViewBindingAdapter.setText(this.text2, girlName);
    }
}

看到这里便真相大白了。我们所苦苦寻找的更改界面的方法就在这里了。

最后附上一副整体的UML图:

image-20210103190946285.png

你可能感兴趣的:(Jetpack源码解析(四)之Data Binding)