Jetpack学习之--DataBinding

一、介绍

官方学习文档

DataBinding 是一个支持库,可以使用声明性格式将布局中的界面组件绑定到应用程序的数据源。

简言之:

可以直接将数据源和XML布局文件中的View视图进行关联。

当数据源是被观察者的话,那么在数据源发生变化的时候,在布局文件绑定的视图也会跟着变化。

特点

  1. 简化界面控制器中的界面框架的调用(也就是不需要使用findViewById来创建view的对象然后进行赋值了)
  2. 防止空指针异常
  3. 界面控制器的代码维护起来更简单
  4. 提高性能

二、基本使用

1、添加DataBinding支持

android{
   dataBinding{
        enabled=true
    }
}

2、定义数据源

继承自BaseObservable ,这样数据源就是一个被观察者

需要和视图绑定的Field ,使用@Bindable注解,

修改属性值的方法中,使用notifyPropertyChanged发出通知(该方法是BaseObservable的方法)

//数据源是一个被观察者
public class DataBean extends BaseObservable {

    //此处是表示该属性支持绑定
    @Bindable
    public String getResultDesc() {
        return resultDesc;
    }

    public void setResultDesc(String resultDesc) {
        this.resultDesc = resultDesc;
        //在修改属性值时,需要发出属性修改的通知
        notifyPropertyChanged(BR.resultDesc);
    }

    @Bindable
    public int getResultCode() {
        return resultCode;
    }

    public void setResultCode(int resultCode) {
        this.resultCode = resultCode;
        notifyPropertyChanged(BR.resultCode);
    }

    private String resultDesc;
    private int resultCode;
}

3、布局文件添加Layout根布局

<?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>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".databinding.DataBindingMainActivity">

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

4、绑定数据源

在data标签中 添加<variable>标签,可以指定变量的名称,类型就是我们的数据源的类型,这样就将数据源类型和布局绑定成功了;

在data标签中,使用<import> 标签 ,可以引用一些我们需要使用的工具类

    <data>
        <import type="android.view.View"/>
        <import type="android.text.TextUtils"/>

        <variable
            name="dataSource"
            type="com.leon.jetpack.databinding.DataBean" />

    </data>

5、数据源与视图属性的绑定

绑定数据源,使用@{}的格式来进行关联绑定

在花括号内部需要使用引号的地方,使用``来代替

 <TextView
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     app:layout_constraintTop_toTopOf="parent"
     android:text="@{dataSource.resultDesc}"/>

6、获取dataBinding实例

每一个支持DataBinding的布局文件,添加了Layout根布局后,都会编译生成一个”布局文件名+Binding.java“文件,例如下面的布局文件名为:activity_data_binding_main.xml ,生成的对应的java文件是:ActivityDataBindingMainBinding.java

public class DataBindingMainActivity extends AppCompatActivity {

   ActivityDataBindingMainBinding dataBindingMainBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        dataBindingMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_data_binding_main);

        DataBean dataBean=new DataBean();
        dataBean.setResultDesc("helloworld");
        dataBindingMainBinding.setDataSource(dataBean);
    }
}

三、原理分析

原理分析的思路:

1)通过APT技术同使用了databinding的布局文件中的view添加tag ,用于后续使用的时候直接通过tag来查询对应的视图

2)通过APT技术将使用了@Bindable注解的数据Field,编译成BR中的常量

由于数据源是一个被观察者,在创建dataBinding对象时,给数据源中使用@Bindable注解的字段添加一个监听器,当数据发生变化时,通过该监听器来回调到ViewDataBinding的实现类ActivityDataBindingMainBindingImpl中去;然后再次执行rebind()方法进行UI视图值的更新.

编译生成的文件目录:

dataBinding编译生成的文件目录.JPG

DataBinding对象创建的类关系调用图:

jetpack_databinding.png

1、setContentView过程

1) DataBindingUtil类的setContentView()方法

    public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
            int layoutId) {
        return setContentView(activity, layoutId, sDefaultComponent);//此处的第三个参数默认为NULL
    }

    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);
        /*
        * 第一个参数为null 
        * 第二个参数为上面获取到contentView(关于这一点可以参考XML布局的加载过程即容易理解)
        * 第三个参数为0     
        * 第四个参数:布局文件的id
        */
        return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
    }


    //下面的方法我们可以先不去关注处理逻辑,直接从return 语句开始
    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);
            
            //todo 关注点 
            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);
            }
             //todo 关注点 
            return bind(component, children, layoutId);
        }
    }

   static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
            int layoutId) {
       //这里的sMapper就是我们第二步的Mapper容器,
        return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
    }

2、Mapper容器的创建过程

1) DataBindingUtil.java类

说明:

1) com.leon.jetpack.DataBinderMapperImpl 该类是DataBinderMapper抽象类的实现类

2)MergedDataBinderMapper 该类也是DataBinderMapper抽象类的实现类,他是对DataBinderMapper子类对象的一个包装类

3)DataBinderMapperImpl 类又是MergedDataBinderMapper 的子类,他的实现其实就是将com.leon.jetpack.DataBinderMapperImpl类的实例包装后保存到MergedDataBinderMapper的List中去。

public class DataBindingUtil {
    private static DataBinderMapper sMapper = new DataBinderMapperImpl();
    }


//下面这个类是编译生成的MergedDataBinderMapper的实现类
public class DataBinderMapperImpl extends MergedDataBinderMapper {
  DataBinderMapperImpl() {
    addMapper(new com.leon.jetpack.DataBinderMapperImpl());
  }
}

2)com.leon.jetpack.DataBinderMapperImpl类的实现

public class DataBinderMapperImpl extends DataBinderMapper {
  private static final int LAYOUT_ACTIVITYDATABINDINGMAIN = 1;

  private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(1);

  static {
      //此处就是将我们自己的布局文件ID保存在一个数组中
    INTERNAL_LAYOUT_ID_LOOKUP.put(com.leon.jetpack.R.layout.activity_data_binding_main, LAYOUT_ACTIVITYDATABINDINGMAIN);
  }

  @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_ACTIVITYDATABINDINGMAIN: {
          if ("layout/activity_data_binding_main_0".equals(tag)) {
              //这里会创建一个布局文件绑定实现类的实例对象
            return new ActivityDataBindingMainBindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for activity_data_binding_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;
  }


}

3、sMapper容器的getDataBinder

由于DataBinderMapperImpl 类继承MergedDataBinderMapper,并没有重写getDataBinder方法,那么就可以进入MergedDataBinderMapper来看看getDataBinder()方法的实现.

说明:

1、该方法是一个重载方法,唯一的区别是第二个参数不同(一个是单个的view ,一个是view数组)

2、该方法是通过遍历mMappers容器内的mapper ,来让每一个mapper调用各自的getDataBinder()方法

 @Override
    public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view,
            int layoutId) {
        for(DataBinderMapper mapper : mMappers) {
            //循环遍历容器内的mapper
            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) {
        //循环遍历容器内的mapper
        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;
    }

进入com.leon.jetpack.DataBinderMapperImpl类的getDataBinder()方法

public class DataBinderMapperImpl extends DataBinderMapper {
 @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_ACTIVITYDATABINDINGMAIN: {
          if ("layout/activity_data_binding_main_0".equals(tag)) {
              //这里就会得到我们需要的binding对象
            return new ActivityDataBindingMainBindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for activity_data_binding_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;
  }
}

4、binding对象的创建过程

ActivityDataBindingMainBindingImpl.java文件

//构造方法
public ActivityDataBindingMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
        //重点关注一下mapBindings()方法
        this(bindingComponent, root, mapBindings(bindingComponent, root, 1, sIncludes, sViewsWithIds));
    }
    

ViewDataBinding.java 该类的mapBindings()方法 是通过从布局文件的根布局开始遍历每一个视图,将所有include ,tag , id标注的视图全部添加到一个视图数组中去,这些视图就是binding的视图,就可以在Activity等界面控制器中通过binding实例对象来直接使用。

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


private static void mapBindings(DataBindingComponent bindingComponent, View view,
            Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
            boolean isRoot) {
        final int indexInIncludes;
        final ViewDataBinding existingBinding = getBinding(view);
        if (existingBinding != null) {
            return;
        }
        Object objTag = view.getTag();
        final String tag = (objTag instanceof String) ? (String) objTag : null;
        boolean isBound = false;
        if (isRoot && tag != null && tag.startsWith("layout")) {
            final int underscoreIndex = tag.lastIndexOf('_');
            if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
                final int index = parseTagInt(tag, underscoreIndex + 1);
                if (bindings[index] == null) {
                    bindings[index] = view;
                }
                indexInIncludes = includes == null ? -1 : index;
                isBound = true;
            } else {
                indexInIncludes = -1;
            }
        } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
            int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
            if (bindings[tagIndex] == null) {
                bindings[tagIndex] = view;
            }
            isBound = true;
            indexInIncludes = includes == null ? -1 : tagIndex;
        } else {
            // Not a bound view
            indexInIncludes = -1;
        }
        if (!isBound) {
            final int id = view.getId();
            if (id > 0) {
                int index;
                if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 &&
                        bindings[index] == null) {
                    bindings[index] = view;
                }
            }
        }

        if (view instanceof  ViewGroup) {
            final ViewGroup viewGroup = (ViewGroup) view;
            final int count = viewGroup.getChildCount();
            int minInclude = 0;
            for (int i = 0; i < count; i++) {
                final View child = viewGroup.getChildAt(i);
                boolean isInclude = false;
                if (indexInIncludes >= 0 && child.getTag() instanceof String) {
                    String childTag = (String) child.getTag();
                    if (childTag.endsWith("_0") &&
                            childTag.startsWith("layout") && childTag.indexOf('/') > 0) {
                        // This *could* be an include. Test against the expected includes.
                        int includeIndex = findIncludeIndex(childTag, minInclude,
                                includes, indexInIncludes);
                        if (includeIndex >= 0) {
                            isInclude = true;
                            minInclude = includeIndex + 1;
                            final int index = includes.indexes[indexInIncludes][includeIndex];
                            final int layoutId = includes.layoutIds[indexInIncludes][includeIndex];
                            int lastMatchingIndex = findLastMatching(viewGroup, i);
                            if (lastMatchingIndex == i) {
                                bindings[index] = DataBindingUtil.bind(bindingComponent, child,
                                        layoutId);
                            } else {
                                final int includeCount =  lastMatchingIndex - i + 1;
                                final View[] included = new View[includeCount];
                                for (int j = 0; j < includeCount; j++) {
                                    included[j] = viewGroup.getChildAt(i + j);
                                }
                                bindings[index] = DataBindingUtil.bind(bindingComponent, included,
                                        layoutId);
                                i += includeCount - 1;
                            }
                        }
                    }
                }
                if (!isInclude) {
                    mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
                }
            }
        }
    }

5、binding.setXXX()或setVariable() 的过程

当在使用binding对象设置数据源或者修改数据的某一个Field的时,会完成如下的两个过程:

从dataBindingMainBinding.setDataSource(dataBean);开始,找到ActivityDataBindingMainBinding的实现类:ActivityDataBindingMainBindingImpl,进入方法setDataSource()

    @Override
    public boolean setVariable(int variableId, @Nullable Object variable)  {
        boolean variableSet = true;
        if (BR.dataSource == variableId) {
            setDataSource((com.leon.jetpack.databinding.DataBean) variable);
        }
        else {
            variableSet = false;
        }
            return variableSet;
    }


//参数是传递进来的databean   
public void setDataSource(@Nullable com.leon.jetpack.databinding.DataBean DataSource) {
        updateRegistration(0, DataSource);
        this.mDataSource = DataSource;//将传递进来的数据保存下来
        synchronized(this) {
            mDirtyFlags |= 0x1L;
        }
        notifyPropertyChanged(BR.dataSource);
        super.requestRebind();
    }

1) 给Field绑定监听的过程

该过程的主要思路就是根据用户设置需要修改的Field来到监听器数组中去找,

如果找了该Field的监听器,并且被观察者也相同,则直接进行后面的通知更新流程;

如果找到该Field的监听器的被观察者不是当前的被观察者,先将该Field的监听器注销掉,并将其被观察者修改为null ,然后重新创建该Field的监听器,并将当前的被观察者设为新建的监听器的Target ,然后进行后续的通知更新流程;

如果没有找到,就直接创建该Field的监听器,并设置Target为当前的被观察者(observable),然后执行通知更新;

下面来从源码角度分析该过程

1) updateRegistration(0, DataSource);更新监听

    /**
     * @hide
     */
    protected boolean updateRegistration(int localFieldId, Observable observable) {
        return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
    }

//进入updateRegistration()方法  直到ViewDataBinding.java文件的下面的方法中

    private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
        @Override
        public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
            //这里创建针对Field的监听,但是这里并不是真正的创建监听,而是一个创建监听器的方法对象
            return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();
        }
    };


    private boolean updateRegistration(int localFieldId, Object observable,
            CreateWeakListener listenerCreator) {
        if (observable == null) {
            return unregisterFrom(localFieldId);
        }
        //从数组中找是否已经有了该Field的监听
        WeakListener listener = mLocalFieldObservers[localFieldId];
        if (listener == null) {
            //如果没有找到该Field的监听者,就把新创建的该Field的监听器注册进去
            registerTo(localFieldId, observable, listenerCreator);
            return true;
        }
        
        //如果已经存在该Field的监听者并且被观察者是同一个,就直接返回
        if (listener.getTarget() == observable) {
            return false;//nothing to do, same object
        }
        
        //如果找到的该Field的监听器与被观察者不同,则先注销掉该Field的监听,将先创建的监听器进行注册绑定
        unregisterFrom(localFieldId);
        registerTo(localFieldId, observable, listenerCreator);
        return true;
    }



2)注销监听
protected boolean unregisterFrom(int localFieldId) {
        WeakListener listener = mLocalFieldObservers[localFieldId];
        if (listener != null) {
            return listener.unregister();
        }
        return false;
    }

 public boolean unregister() {
            boolean unregistered = false;
            if (mTarget != null) {
                mObservable.removeListener(mTarget);
                unregistered = true;
            }
            mTarget = null;
            return unregistered;
        }
3)注册监听
 protected void registerTo(int localFieldId, Object observable,
            CreateWeakListener listenerCreator) {
        if (observable == null) {
            return;
        }
     //先从Field数组中查找localFieldId的监听
        WeakListener listener = mLocalFieldObservers[localFieldId];
        if (listener == null) {
            //没找到就创建一个监听
            listener = listenerCreator.create(this, localFieldId);
            //将创建好的监听添加mLocalFieldObservers数组中
            mLocalFieldObservers[localFieldId] = listener;
            if (mLifecycleOwner != null) {
                listener.setLifecycleOwner(mLifecycleOwner);
            }
        }
        //如果找到了,就给监听设置Target ,这个过程和注销的时候相反,注册是将监听器的target设置为null
        listener.setTarget(observable);
    }

2)通知更新的流程

单Field更新:

public class DataBean extends BaseObservable {

    @Bindable
    public String getResultDesc() {
        return resultDesc;
    }

    public void setResultDesc(String resultDesc) {
        this.resultDesc = resultDesc;
        notifyPropertyChanged(BR.resultDesc);
    }

    @Bindable
    public int getResultCode() {
        return resultCode;
    }

    public void setResultCode(int resultCode) {
        this.resultCode = resultCode;
        //这里被观察者要发出变更的通知
        notifyPropertyChanged(BR.resultCode);
    }

    private String resultDesc;
    private int resultCode;

}

整个数据源更新:

 public void setDataSource(@Nullable com.leon.jetpack.databinding.DataBean DataSource) {
        updateRegistration(0, DataSource);
        this.mDataSource = DataSource;
        synchronized(this) {
            mDirtyFlags |= 0x1L;
        }
        //通知更新的流程
        notifyPropertyChanged(BR.dataSource);
        super.requestRebind();
    }
1)mCallbacks对象溯源

BaseObservable.java 的addOnPropertyChangedCallback,NofifyPropertyChanged()

mCallbacks变量中存放的是一个实现了OnPropertyChangedCallback接口的callback对象;

mCallbacks变量是以List数据结构来存放对象的;

public class BaseObservable implements Observable {
    private transient PropertyChangeRegistry mCallbacks;

    public BaseObservable() {
    }

    //这里mCallBacks里面存放的是一个实现了OnPropertyChangedCallback接口的对象
    //mCallBacks里面是用一个List数据结构来存放对象的
    @Override
    public void addOnPropertyChangedCallback(@NonNull OnPropertyChangedCallback callback) {
        synchronized (this) {
            if (mCallbacks == null) {
                mCallbacks = new PropertyChangeRegistry();
            }
        }
        mCallbacks.add(callback);
    }

    @Override
    public void removeOnPropertyChangedCallback(@NonNull OnPropertyChangedCallback callback) {
        synchronized (this) {
            if (mCallbacks == null) {
                return;
            }
        }
        mCallbacks.remove(callback);
    }

    /**
     * Notifies listeners that all properties of this instance have changed.
     */
    public void notifyChange() {
        synchronized (this) {
            if (mCallbacks == null) {
                return;
            }
        }
        mCallbacks.notifyCallbacks(this, 0, null);
    }

    /**
     * Notifies listeners that a specific property has changed. The getter for the property
     * that changes should be marked with {@link Bindable} to generate a field in
     * <code>BR</code> to be used as <code>fieldId</code>.
     *
     * @param fieldId The generated BR id for the Bindable field.
     */

public void notifyPropertyChanged(int fieldId) {
        synchronized (this) {
            if (mCallbacks == null) {
                return;
            }
        }
      //这里是通过下面的代码来同通知修改的,那么就需要知道mCallBacks的由来
        mCallbacks.notifyCallbacks(this, fieldId, null);
    }
} 
2)ViewDataBinding的静态内部类WeakPropertyListener

该静态内部类实现了接口 Observable.OnPropertyChangedCallback

该静态内部类是用来监听Bindings中的variables的变化,很重要,很重要

这个WeakPropertyListener 又是通过一个方法对象在给Filed添加监听的时候创建的。并且在调用addListener的时候,将当前监听器添加到被观察者observable的mCallBack中去

//ViewDataBinding.java
private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
            implements ObservableReference<Observable> {
        final WeakListener<Observable> mListener;

        public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
            mListener = new WeakListener<Observable>(binder, localFieldId, this);
        }

        @Override
        public WeakListener<Observable> getListener() {
            return mListener;
        }

        @Override
        public void addListener(Observable target) {
            //这里将当前的监听器添加到mCallBack中
            target.addOnPropertyChangedCallback(this);
        }

        @Override
        public void removeListener(Observable target) {
            target.removeOnPropertyChangedCallback(this);
        }

        @Override
        public void setLifecycleOwner(LifecycleOwner lifecycleOwner) {
        }

        
        @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);
        }
    }
3)监听回调

当mCallbacks.notifyCallbacks(this, fieldId, null);这个代码执行时,其实就是调用的监听者WeakPropertyListener的onPropertyChanged()回调方法。

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) {
            //这里实际上就是调用前面的Listener的回调
            callback.onPropertyChanged(sender, arg);
        }
    };

    public PropertyChangeRegistry() {
        super(NOTIFIER_CALLBACK);
    }

    /**
     * Notifies registered callbacks that a specific property has changed.
     *
     * @param observable The Observable that has changed.
     * @param propertyId The BR id of the property that has changed or BR._all if the entire
     *                   Observable has changed.
     */
    public void notifyChange(@NonNull Observable observable, int propertyId) {
        notifyCallbacks(observable, propertyId, null);
    }
}


//上一步就回到WeakPropertyListener的onPropertyChanged()方法中
private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
            implements ObservableReference<Observable> {
        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.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;
        }
        
        //onFieldChange是一个抽象方法,
        boolean result = onFieldChange(mLocalFieldId, object, fieldId);
        if (result) {
            requestRebind();
        }
    }

//ViewDataBinding的实现类ActivityDataBindingMainBindingImpl.java中
  @Override
    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        switch (localFieldId) {
            case 0 :
                return onChangeDataSource((com.leon.jetpack.databinding.DataBean) object, fieldId);
        }
        return false;
    }
    private boolean onChangeDataSource(com.leon.jetpack.databinding.DataBean DataSource, int fieldId) {
        if (fieldId == BR._all) {
            synchronized(this) {
                    mDirtyFlags |= 0x1L;
            }
            return true;
        }
        else if (fieldId == BR.resultDesc) {
            synchronized(this) {
                    mDirtyFlags |= 0x2L;
            }
            return true;
        }
        return false;
    }

6、布局文件解析

编译时,会将布局文件中使用了@{}绑定数据的view视图,添加一个Tag标签,并生成到一个新的文件:布局文件名-layout.xml,

例如:(app/build/intermediates/data_binding_layout_info+type_merge/debug/out/activity_data_binding_main-layout.xml)

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout directory="layout" filePath="app\src\main\res\layout\activity_data_binding_main.xml"
    isBindingData="true" isMerge="false"
    layout="activity_data_binding_main" modulePackage="com.leon.jetpack"
    rootNodeType="androidx.constraintlayout.widget.ConstraintLayout">

    <Variables name="dataSource" declared="true" type="com.leon.jetpack.databinding.DataBean">

        <location endLine="9" endOffset="58" startLine="7" startOffset="8" />
    </Variables>

    <Targets>
        <Target tag="layout/activity_data_binding_main_0"
            view="androidx.constraintlayout.widget.ConstraintLayout">
            <Expressions />

            <location endLine="35" endOffset="55" startLine="13" startOffset="4" />
        </Target>

        <!--这里就是将使用了dataBinding的视图节点添加了一个tag标签-->
        <Target tag="binding_1" view="TextView">
            <Expressions>
                <Expression attribute="android:text" text="dataSource.resultDesc">
                    <Location endLine="30" endOffset="50" startLine="30" startOffset="12" />
                    <TwoWay>false</TwoWay>
                    <ValueLocation endLine="30" endOffset="48" startLine="30" startOffset="28" />
                </Expression>
            </Expressions>
            <location endLine="33" endOffset="63" startLine="26" startOffset="8" />
        </Target>

        <Target id="@+id/change" view="Button">
            <Expressions />
            <location endLine="24" endOffset="55" startLine="18" startOffset="8" />
        </Target>
    </Targets>
</Layout>

在DataBindingUtil.setContentView()会一直进入到ViewDataBinding的静态方法mapBindings(),该方法主要是找到标签了tag的视图,

 private static void mapBindings(DataBindingComponent bindingComponent, View view,
            Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
            boolean isRoot) {
        final int indexInIncludes;
        final ViewDataBinding existingBinding = getBinding(view);
        if (existingBinding != null) {
            return;
        }
        Object objTag = view.getTag();
        final String tag = (objTag instanceof String) ? (String) objTag : null;
        boolean isBound = false;
        if (isRoot && tag != null && tag.startsWith("layout")) {
            final int underscoreIndex = tag.lastIndexOf('_');
            if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
                final int index = parseTagInt(tag, underscoreIndex + 1);
                if (bindings[index] == null) {
                    bindings[index] = view;
                }
                indexInIncludes = includes == null ? -1 : index;
                isBound = true;
            } else {
                indexInIncludes = -1;
            }
        } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
            int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
            if (bindings[tagIndex] == null) {
                bindings[tagIndex] = view;
            }
            isBound = true;
            indexInIncludes = includes == null ? -1 : tagIndex;
        } else {
            // Not a bound view
            indexInIncludes = -1;
        }
        if (!isBound) {
            final int id = view.getId();
            if (id > 0) {
                int index;
                if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 &&
                        bindings[index] == null) {
                    bindings[index] = view;
                }
            }
        }

        if (view instanceof  ViewGroup) {
            final ViewGroup viewGroup = (ViewGroup) view;
            final int count = viewGroup.getChildCount();
            int minInclude = 0;
            for (int i = 0; i < count; i++) {
                final View child = viewGroup.getChildAt(i);
                boolean isInclude = false;
                if (indexInIncludes >= 0 && child.getTag() instanceof String) {
                    String childTag = (String) child.getTag();
                    if (childTag.endsWith("_0") &&
                            childTag.startsWith("layout") && childTag.indexOf('/') > 0) {
                        // This *could* be an include. Test against the expected includes.
                        int includeIndex = findIncludeIndex(childTag, minInclude,
                                includes, indexInIncludes);
                        if (includeIndex >= 0) {
                            isInclude = true;
                            minInclude = includeIndex + 1;
                            final int index = includes.indexes[indexInIncludes][includeIndex];
                            final int layoutId = includes.layoutIds[indexInIncludes][includeIndex];
                            int lastMatchingIndex = findLastMatching(viewGroup, i);
                            if (lastMatchingIndex == i) {
                                bindings[index] = DataBindingUtil.bind(bindingComponent, child,
                                        layoutId);
                            } else {
                                final int includeCount =  lastMatchingIndex - i + 1;
                                final View[] included = new View[includeCount];
                                for (int j = 0; j < includeCount; j++) {
                                    included[j] = viewGroup.getChildAt(i + j);
                                }
                                bindings[index] = DataBindingUtil.bind(bindingComponent, included,
                                        layoutId);
                                i += includeCount - 1;
                            }
                        }
                    }
                }
                if (!isInclude) {
                    mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
                }
            }
        }
    }

在更新通知流程的最后一步会调用super.requestRebind();

    public void setDataSource(@Nullable com.leon.jetpack.databinding.DataBean DataSource) {
        updateRegistration(0, DataSource);
        this.mDataSource = DataSource;
        synchronized(this) {
            mDirtyFlags |= 0x1L;
        }
        notifyPropertyChanged(BR.dataSource);
        super.requestRebind();
    }

进入ViewDataBinding.java的requestRebind(),看到下面会有mUIThreadHandler会执行一个mRebindRunnable子线程来执行更新

 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 {
                //这里执行一个子线程mRebindRunnable
                mUIThreadHandler.post(mRebindRunnable);
            }
        }
    }

 /**
     * Runnable executed on animation heartbeat to rebind the dirty Views.
     */
    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();
        }
    };



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

进入

 protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        com.leon.jetpack.databinding.DataBean dataSource = mDataSource;
        java.lang.String dataSourceResultDesc = null;

        if ((dirtyFlags & 0x7L) != 0) {



                if (dataSource != null) {
                    // read dataSource.resultDesc
                    dataSourceResultDesc = dataSource.getResultDesc();
                }
        }
        // batch finished
        if ((dirtyFlags & 0x7L) != 0) {
            // api target 1
            //这里还是调用的setText来完成更新的
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, dataSourceResultDesc);
        }
    }

7、DataBinding的适配事项

ViewDataBinding.java文件中对对DataBinding库支持的SDK版本做了限制(如下的静态代码块),

 static {
        if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {//需要大于Android 19
            ROOT_REATTACHED_LISTENER = null;
        } else {
            ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
                @TargetApi(VERSION_CODES.KITKAT)
                @Override
                public void onViewAttachedToWindow(View v) {
                    // execute the pending bindings.
                    final ViewDataBinding binding = getBinding(v);
                    binding.mRebindRunnable.run();
                    v.removeOnAttachStateChangeListener(this);
                }

                @Override
                public void onViewDetachedFromWindow(View v) {
                }
            };
        }
    }

四、BindingAdapter

@Target(ElementType.METHOD)
public @interface BindingAdapter {

    /**
     * @return The attributes associated with this binding adapter.
     */
    String[] value();

    /**
     * Whether every attribute must be assigned a binding expression or if some
     * can be absent. When this is false, the BindingAdapter will be called
     * when at least one associated attribute has a binding expression. The attributes
     * for which there was no binding expression (even a normal XML value) will
     * cause the associated parameter receive the Java default value. Care must be
     * taken to ensure that a default value is not confused with a valid XML value.
     *
     * @return whether or not every attribute must be assigned a binding expression. The default
     *         value is true.
     */
    boolean requireAll() default true;
}

BindingAdapter 是一个作用在方法上的注解,作用就是:如何将有表达式的值设置给视图。

说明:

1)参数value 可以是单个值,也可以是多个值。多个值时使用{}花括号,用逗号隔开

2)requireAll表示是否必须要所有的value值全部使用BindingAdapter才生效,该参数主要是针对多个value值时,默认是true

  1. 注解的方法是public static 修饰的,方法的第一个参数是视图View ,后面的参数是对应的value参数中的值。

业务场景:

1)当需要经过自定义的处理逻辑后才将结果赋值给某个属性时

2)自定义视图属性

public class ImageViewAdapter{

    //单个属性
    @BindingAdapter("imageurl")//这里是属性名
    public static void setImageUrl(ImageView imageView ,String Url){
        //这里可以完成Glide加载图片等工作
    }
    
    //多个属性
    public static void configImage(ImageView imageView,String url,Drawable error){
        
    }
}

五、总结

通过本文学习DataBinding的简单使用,与工作原理的源码分析,学习到DataBinding库其实也是使用APT技术+数据结构+观察者模式三个技术点的组合来完成数据的双向绑定,这样我们只需要更新数据源(Observable)就会同步更新视图对应的属性值,这样我们就可以把重心放在数据的处理逻辑上了。

技术点:APT技术+数据结构+观察者模式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容