DataBinding运行机制+源码解析

435 阅读8分钟

一、DataBinding编译流程

想要在项目中使用DataBinding,只需要在模块中的build.gradle文件中,添加对DataBinding的支持:

android {
    buildFeatures{
        dataBinding true
    }
}

这样,编译器会自动导入DataBinding的依赖,同时解析所有被<layout></layout>标签所包裹的xml布局文件,并生成对应的DataBinding类。

具体流程如下:

1、预处理xml布局文件

当项目引入了DataBinding,你是否有以下疑问:

<layout></layout>标签Android能够正确解析?

答案是否定的,我们可以通过查看apk里最终的xml文件得出答案。

image.png

由上图可见,编译器对xml布局进行了如下处理:

  • 1、提取出<layout></layout>标签里的布局内容,并移除掉控件里,所有引用到了<variable>的属性。

这一步只是为了让Android正确解析xml文件。

  • 2、为感兴趣的view添加tag属性:android:tag="<layout_name>_<index>"

其中<index>是以深度优先遍历xml文件,对所有感兴趣view的索引,从0开始。

这里感兴趣的view,包括以下几种:

  1. 第一个view,用于确定布局信息。
  2. 拥有id的view,该控件一般会被用户直接调用。
  3. 属性里使用了variable的控件,DataBinding框架后续会为该控件设置对应属性。
  4. 拥有tag的view,该控件可能会被用户调用,并且由于DataBinding覆盖了该tag,后续需要进行恢复。

这里为啥需要为感兴趣的view添加tag呢?

这是为了防止动态删除View后,再通过bind获取的DataBinding对象中,造成控件混乱。

一、创建DataBinding对象

创建DataBinding对象,无非2种方式:

  1. 调用XXXBinding.inflate()方法或DataBindingUtil.inflate()方法。
  2. 调用XXXBinding.bind()方法或DataBindingUtil.bind()方法。

无论是以上哪种方式,最终都会调用到DataBindingUtil.bind()方法之中,具体流程可以自行阅读生成的源码。

public class DataBindingUtil {
    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中获取到我们所需的Binding对象,其中DataBinderMapperImpl是在编译过程中,通过解析xml文件动态生成的。

在解析DataBinderMapperImpl源码之前,我们先介绍一下我们的项目结构,便于大家理解代码生成的逻辑。

image.png

其中,app模块中有activity_main.xmlactivity_home.xml2个布局文件,common模块只有activity_common.xml1个布局文件,其中app依赖于common。

经过编译后,会生成3个DataBinderMapperImpl类,分别为:

  • androidx.databinding.DataBinderMapperImpl
  • com.pujh.app.DataBinderMapperImpl
  • com.pujh.common.DataBinderMapperImpl

而DataBindingUtil调用的sMapper对象,为androidx.databinding.DataBinderMapperImpl。内容如下:

public class DataBinderMapperImpl extends MergedDataBinderMapper {
  DataBinderMapperImpl() {
    addMapper(new com.pujh.app.DataBinderMapperImpl());
  }
}

这里可以发现,androidx.databinding.DataBinderMapperImpl继承至MergedDataBinderMapper,顾名思义,它的作用是将多个DataBinderMapper对象进行合并。而它的构造方法,也反映了它的这个特性。

可是为什么它的构造方法里,只添加了com.pujh.app.DataBinderMapperImpl,而没有添加com.pujh.common.DataBinderMapperImpl,那是否意味着我们在app module中无法使用common module里的DataBinding对象呢?

肯定不会是这样啊,其实这里DataBinding已经帮我们考虑到了,我们可以查看addMapper方法的内部实现。

public void addMapper(DataBinderMapper mapper) {
    Class<? extends DataBinderMapper> mapperClass = mapper.getClass();
    if (mExistingMappers.add(mapperClass)) {
        mMappers.add(mapper);
        //获取当前module所依赖module的的DataBinderMapper
        final List<DataBinderMapper> dependencies = mapper.collectDependencies();
        for(DataBinderMapper dependency : dependencies) {
            addMapper(dependency); //递归添加依赖module中的DataBinderMapper
        }
    }
}

这里我们虽然只添加了com.pujh.app.DataBinderMapperImpl,但它内部的collectDependencies方法,却返回了com.pujh.common.DataBinderMapperImpl

public class DataBinderMapperImpl extends DataBinderMapper {
  @Override
  public List<DataBinderMapper> collectDependencies() {
    ArrayList<DataBinderMapper> result = new ArrayList<DataBinderMapper>(2);
    result.add(new androidx.databinding.library.baseAdapters.DataBinderMapperImpl());
    result.add(new com.pujh.common.DataBinderMapperImpl());
    return result;
  }

至此,DataBindingUtil已经添加了当前运行module和所有依赖module所生成的DataBinderMapperImpl对象。

现在我们可以开始分析,如何从module里的DataBinderMapperImpl对象中,获取到我们所需要的DataBinding对象,即getDataBinder方法。

这里我们直接看com.pujh.app.DataBinderMapperImpl源码:

public class DataBinderMapperImpl extends DataBinderMapper {
  private static final int LAYOUT_ACTIVITYHOME = 1;
  private static final int LAYOUT_ACTIVITYMAIN = 2;
  private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(2);

  static {
    INTERNAL_LAYOUT_ID_LOOKUP.put(com.pujh.app.R.layout.activity_home, LAYOUT_ACTIVITYHOME);
    INTERNAL_LAYOUT_ID_LOOKUP.put(com.pujh.app.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_ACTIVITYHOME: {
          if ("layout/activity_home_0".equals(tag)) {
            return new ActivityHomeBindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for activity_home is invalid. Received: " + tag);
        }
        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;
  }

它将布局id和进行模块内编号,根据该映射关系,然后再判断tag,最终创建对应的XXXBingImpl对象。

可是我们好像并没有给view设置tag属性啊,那这个tag又是从何而来?这里我们放到后面再来聊。

三、生成对应的Binding类

编译器通过解析xml布局,自动生成对应的XXXBindingXXXBindingImpl文件。

然后将布局中所有<variable/>标签变量生成到XXXBinding文件中,并使用@Bindable进行注解,这样系统会在BR类中生成名字相同的int字段。

然后将布局中的root_layout和所有的bind_view按index的顺序,声明到XXXBindingImpl文件中。同时找到使用@={xxx}进行双向绑定的类和字段,找到其对应的且被@InverseBindingAdapter解析的方法,在XXXBindingImpl中生成对应的InverseBindingListener监听对象,并在onChange回调中,调用被@InverseBindingAdapter注解的方法,实现从View中获取值,并将该值设置到XXXBinding对应的对象中。

而我们具体的DataBinderMapperImpl中,包含2个静态类

private static class InnerBrLookup {
  static final SparseArray<String> sKeys = new SparseArray<String>(7);

  static {
    sKeys.put(0, "_all");
    sKeys.put(1, "id");
    sKeys.put(2, "name");
    sKeys.put(3, "user");
    sKeys.put(4, "user1");
    sKeys.put(5, "userId");
    sKeys.put(6, "userName");
  }
}

private static class InnerLayoutIdLookup {
  static final HashMap<String, Integer> sKeys = new HashMap<String, Integer>(1);

  static {
    sKeys.put("layout/fragment_main_0", com.demo.jetpack.R.layout.fragment_main);
  }
}

InnerBrLookup用来与BR类进行对应,如果RB的id,找到对应的字段名称。

InnerLayoutIdLookup用来记录所有root_layout的tag对应的layout id。

getDataBinder方法,传入我们的布局id,返回对应的XXXBindingImpl对象。而XXXBindingImpl对象构造函数中,会去恢复原先我们在xml中设置的android:tag,属性在编译时被DataBinding修改,如果不恢复,用户将获取不到正确的tag。同时调用invalidateAll,来添加控件监听,以实现双向绑定。

4、更新数据

XXXBinding.setXXX()方法,也是由编译器自动生成的,根据参数类型的不同,有这不同的逻辑,但无外乎是以下几步:

  • 1、如果是可监听对象,如继承Observable、ObservableList、ObservableMap、LiveData,则重新注册监听(因为原先的对象已经与DataBinding无关了,监听只是为了保证对象内部的数据变化,通知给UI)
  • 2、将该对象整个对象对应的脏标志位置位
  • 3、通知属性改变
  • 4、请求重新绑定

这里需要注意,通过XXXBinding.setXXX()方法修改整个对象,会将整个对象对应的脏标志位置位,而如果是通过设置该对象某个可监听字段,则只会将改字段对应的涨标志位更新,而不是整个对象。

重新绑定时,会通过mChoreographermUIThreadHandler去执行mRebindRunnable,最终执行到XXXBindingImpl.executeBindings(),该方法中,主要执行更新脏数据对应控件,已经添加控件监听事件,用于双向绑定。

在添加监听事件的方法中,传入了InverseBindingListener监听对象,如果控件发生改变,就调用InverseBindingListener.onChange()方法,该方法就会从控件中获取数据,然后设置到对应的变量字段中。

5、可监听对象更新数据

上面介绍的是通过XXXBinding.setXXX()方法更新整个数据,而当这个对象未改变,而只是其中某个字段发生改变,那该如何反映到View上呢。这就靠上文说到的为可监听对象注册监听。

protected boolean updateRegistration(int localFieldId, Observable observable) {
    return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
}

protected boolean updateRegistration(int localFieldId, ObservableList observable) {
    return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER);
}

protected boolean updateRegistration(int localFieldId, ObservableMap observable) {
    return updateRegistration(localFieldId, observable, CREATE_MAP_LISTENER);
}

protected boolean updateLiveDataRegistration(int localFieldId, LiveData<?> observable) {
    mInLiveDataRegisterObserver = true;
    try {
        return updateRegistration(localFieldId, observable, CREATE_LIVE_DATA_LISTENER);
    } finally {
        mInLiveDataRegisterObserver = false;
    }
}

目前DataBinding只支持4种可监听对象,分别Observable、ObservableList、ObservableMap、LiveData,他们会调用不同的方法。但最终都会调用到registerTo

protected void registerTo(int localFieldId, Object observable,
        CreateWeakListener listenerCreator) {
    if (observable == null) {
        return;
    }
    WeakListener listener = mLocalFieldObservers[localFieldId];
    if (listener == null) {
        listener = listenerCreator.create(this, localFieldId, sReferenceQueue);
        mLocalFieldObservers[localFieldId] = listener;
        if (mLifecycleOwner != null) {
            listener.setLifecycleOwner(mLifecycleOwner);
        }
    }
    listener.setTarget(observable);
}

通过不同的listenerCreator创建,不同的listener,然后设置mLifecycleOwner,并调用setTarget注册监听。

class WeakListener<T> extends WeakReference<ViewDataBinding> {
    private final ObservableReference<T> mObservable;
    protected final int mLocalFieldId;
    private T mTarget;

    public WeakListener(
            ViewDataBinding binder,
            int localFieldId,
            ObservableReference<T> observable,
            ReferenceQueue<ViewDataBinding> referenceQueue
    ) {
        super(binder, referenceQueue);
        mLocalFieldId = localFieldId;
        mObservable = observable;
    }

    public void setLifecycleOwner(LifecycleOwner lifecycleOwner) {
        mObservable.setLifecycleOwner(lifecycleOwner);
    }

    public void setTarget(T object) {
        unregister();
        mTarget = object;
        if (mTarget != null) {
            mObservable.addListener(mTarget);
        }
    }

    public boolean unregister() {
        boolean unregistered = false;
        if (mTarget != null) {
            mObservable.removeListener(mTarget);
            unregistered = true;
        }
        mTarget = null;
        return unregistered;
    }

mObservable对象其实就是listenerCreator对象,不同的listenerCreator,针对不同的可观察对象,进行不同的注册监听。当对象内容改变后(不是对象改变),会调用到ViewDataBinding.handleFieldChange()

protected void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
    if (mInLiveDataRegisterObserver || mInStateFlowRegisterObserver) {
        // We're in LiveData or StateFlow 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会根据传入的mLocalField找到对应的变量,然后通过fieldId找到该变量哪个属性发生改变,从而设置脏标志位,并返回true,从而请求重新绑定。

  • Bindable

    设置数据刷新视图. 自动生成BR的ID

  • BindingAdapter

    设置自定义属性. 可以覆盖系统原有属性

  • BindingMethod/BindingMethods

    关联自定义属性到控件原有的setter方法

  • BindingConversion

    如果属性不能匹配类型参数将自动根据类型参数匹配到该注解修饰的方法来转换

  • InverseMethod

    负责实现视图和数据之间的转换

  • InverseBindingAdapter

    视图通知数据刷新的

  • InverseBindingMethod/InverseBindingMethods

    视图通知数据刷新的(如果存在已有getter方法可用的情况下)

  • BindingMethods系优先级高于BindingAdapter系列

  • 所有注解的功能都是基于XML属性值为Databinding表达式才生效(即@{})