一起养成写作习惯!这是我参与「掘金日新计划 · 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的实现类。