JetPack DataBinding原理分析3

248 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情

三.DataBinding原理分析

      DataBinding实现了数据与UI绑定,那是如何绑定的呢?数据变化后是如何对应UI进行更新的呢?一起看一下实现过程。

      使用AndroidStudio进行开发时,对应用编译后,会在对应目录下生成额外的文件:

      在build/generated/ap_generated_sources/debug/out目录下会生成对应的文件:DataBinderMapperImpl.java(两个),KeyboardLayoutBindingImpl.java文件;

      在build/generated/data_binding_base_class_source_out/debug/out目录下会生成对应的文件:KeyboardLayoutBinding.java;

      在build/intermediates/incremental/mergeDebugResources/stripped.dir/layout目录下会生成对应的布局文件:keyboard_layout.xml;

      接下来会一一进行分析。

      上面我们看到,在KeyBoardFragment内部的onCreateView()内部执行的逻辑,再一起看一下:

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    KeyboardLayoutBinding binding = DataBindingUtil.inflate(inflater, R.layout.keyboard_layout, container, false);
    View view = binding.getRoot();
    mViewModel = new ViewModel(mContext);
    binding.setGuide(mViewModel);
    return view;
}
a.DataBindingUtil.java

      在Fragement的onCreateView()内部通过DataBindingUtil.inflate()来创建KeyboardLayoutBinding对象binding,先看一下DataBindingUtil的inflate()实现:

private static DataBinderMapper sMapper = new DataBinderMapperImpl();

public static <T extends ViewDataBinding> T inflate(@NonNull LayoutInflater inflater,
            int layoutId, @Nullable ViewGroup parent, boolean attachToParent) {
    return inflate(inflater, layoutId, parent, attachToParent, sDefaultComponent);
}

public static <T extends ViewDataBinding> T inflate(
            @NonNull LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent,
            boolean attachToParent, @Nullable DataBindingComponent bindingComponent) {
     final boolean useChildren = parent != null && attachToParent;
     final int startChildren = useChildren ? parent.getChildCount() : 0;
     final View view = inflater.inflate(layoutId, parent, attachToParent);
     if (useChildren) {
         return bindToAddedViews(bindingComponent, parent, startChildren, layoutId);
     } else {
         return bind(bindingComponent, view, layoutId);
     }
}

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

      inflate()内部会通过inflater,inflate()来加载对应layoutId的view,由于attachToParent为false,所以在inflate()内部直接执行bind()方法,在bind()内部会通过sMapper.getDataBinder()来返回ViewDataBinding的实现类对象,sMapper是DataBinderMapperImpl对象,接下来看一下DataBinderMapperImpl实现。

b.DataBinderMapperImpl.java

      前面说到,DataBinderMapperImpl.java是编译生成的文件,继承了MergedDataBinderMapper类,内部就一个构造方法内执行了addMapper()方法:

package androidx.databinding;

public class DataBinderMapperImpl extends MergedDataBinderMapper {
  DataBinderMapperImpl() {
    addMapper(new com.xxx.learn.DataBinderMapperImpl());
  }
}

      在创建DataBinderMapperImpl对象的时候,在构造方法内执行addMapper()将创建com.xxx.learn.DataBinderMapperImpl()对象作为参数传入,后续通过sMapper.getDataBinder()来获取对应的ViewDataBinding的实现类时,会最终调用到com.xxx.learn.DataBinderMapperImpl.java中的getDataBinder()方法,接下来看一下com.xxx.learn.DataBinderMapperImpl.java这个类的逻辑实现:

c.DataBinderMapperImpl.java

      此DataBinderMapperImpl.java不同于b步的DataBinderMapperImpl.java,注意区分,该类是主要的实现类:

public class DataBinderMapperImpl extends DataBinderMapper {
    private static final int LAYOUT_KEYBOARDLAYOUT = 1;
    private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(1);
    static {
        INTERNAL_LAYOUT_ID_LOOKUP.put(com.xxx.learn.R.layout.keyboard_layout, LAYOUT_KEYBOARDLAYOUT);
    }

  @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_KEYBOARDLAYOUT: {
          if ("layout/keyboard_layout_0".equals(tag)) {
            return new KeyboardLayoutBindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for keyboard_layout is invalid. Received: " + tag);
        }
      }
    }
    return null;
  }
  ......
}

      DataBinderMapperImpl实现了DataBinderMapper类,定义了静态变量LAYOUT_KEYBOARDLAYOUT和静态数组INTERNAL_LAYOUT_ID_LOOKUP(可能有多个使用databinding的layout),在静态代码块内将R.layout.keyboard_layout与LAYOUT_KEYBOARDLAYOUT建立映射关系存入INTERNAL_LAYOUT_ID_LOOKUP内,在执行getDataBinder时,通过创建时传入的layoutId从INTERNAL_LAYOUT_ID_LOOKUP找到对应的localizedLayoutId,然后根据view的tag关系来创建KeyboardLayoutBindingImpl对象,该tag体现在对应生成的布局文件:

<?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent" android:tag="layout/keyboard_layout_0" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">

        <TextView
            android:id="@+id/name"
            .....
            android:tag="binding_1"  />

        <ImageView
            .......
            android:tag="binding_2" />

        <TextView
            .......
            android:tag="binding_3" />

        <TextView
            .......
            android:tag="binding_4"           
            ....../>

        <TextView
            ........
            android:tag="binding_5"             
            ......./>

        <TextView
            .........
            android:tag="binding_6"  />

    </RelativeLayout>

      简单总结一下:在onCreateView()内部执行DataBindingUtil.inflate(),通过一步一步的调用后,最终返回的是KeyboardLayoutBindingImpl对象,该类是KeyboardLayoutBinding的实现类。