- 一、DataBinding应用场景
- 二、DataBinding使用
- 1、配置gradle来开启DataBinding
- 2、将布局转换为DataBinding布局
- 3、数据类
- 4、在xml的data标签中,引入数据类
- 5、双向绑定
- 三、DataBinding原理分析
- 1、拆布局
- 2、DataBindingUtil.setContentView
- 3、设置数据
在Android开发初期,基本都会使用MVC模式,但随着业务的增加,Activity会变得很臃肿,业务代码与界面显示全部耦合在一起,扩展性差,维护起来很不方便,于是MVP模式就应运而生了。
MVP模式实现了V和M的解耦,分层清晰,把业务处理搬到P层,通过接口实现V层和P层之间通信。MVP模式也存在一些问题,随着项目的增大,接口类会越来越多,而且每一个小的改动,都需要通过接口来通信,有时候一个简单的功能,都需要写好几个接口,会造成接口爆炸。这时候MVVM模式就出现了。
MVVM模式同样也是解耦,将业务代码放到VM层,V层与VM层通过双向绑定来相互通知变化,不再需要那么多的接口来通信了。双向绑定,指的是将数据与界面绑定起来,当数据发生变化时会体现在界面上,反过来界面内容变化也会同步更新到数据上。
DataBinding可以轻松的实现MVVM,但并不能说DataBinding就只是属于MVVM的。DataBinding只是谷歌推出的一个支持双向绑定的库,它是工具集,能大大减少绑定app逻辑与layout文件的“胶水代码”,例如setText、findViewById等代码,大多数情况下,MVVM都使用DataBinding,但其实小部分情况下,MVP也会使用DataBinding。我们先来简单使用一下DataBinding,再对它的工作流程做一个大概的了解。
二、DataBinding使用 1、配置gradle来开启DataBindingandroid {
...
buildFeatures {
dataBinding true
}
}
2、将布局转换为DataBinding布局
在xml布局中,点击小黄灯,选择Convert to data binding layout
此时会自动帮我们包裹一层
3、数据类
创建一个数据类来模拟网络请求返回的数据类,所有字段都定义为ObservableField类型,并把实际类型通过泛型传入,这是kotlin的用法,和Java直接继承baseObserver的写法稍有区别。
如果想要实现双向绑定,必须要使用ObservableField,这样才能收到数据变化的回调,从而实现M—>V一向的绑定,否则就只有V—>M单向的绑定了。
class User {
val name: ObservableField by lazy { ObservableField() }
val age: ObservableField by lazy { ObservableField() }
}
4、在xml的data标签中,引入数据类
5、双向绑定
当我们使用了DataBinding布局后,DataBinding会自动为我们生成一个以我们xml文件名字为依据创建的文件,比如我的xml文件名为activity_data_binding,于是在Activity中我就可以直接使用DataBinding为我们生成的类ActivityDataBindingBinding,如果没有的话,需要重新build一下,因为DataBinding采用的是APT技术,只有在编译期间才会生成代码。另外,还需要给DataBinding设置我们的数据类,这样DataBinding才可以监听数据的变化
class DataBindingActivity : AppCompatActivity() {
private lateinit var mBinding: ActivityDataBindingBinding
private var mUser = User()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_data_binding)
mBinding.user = mUser
mUser.name.set("阿三")
mUser.age.set("30")
}
}
在xml中,定义几个TextView和Edittext来测试效果,我们使用两个TextView来分别显示name和age,两个Edittext分别可以输入来改变name和age
mBinding.user = mUser
mUser.name.set("阿三")
android:text="@{user.name}"
这些是实现了Model—>View层一向的绑定,数据类的值发生变化时,View上显示的值会马上改变
android:text="@={user.name}"
@={}实现了View—>Model层一向的绑定,当View上的值我们通过输入来改变以后,数据类的值会被相应修改
以上就实现了双向绑定。
DataBinding用起来很简单,越简单表示框架帮我们完成了越多的东西,在我们使用DataBinding时,编译后可以看到它帮我们生成了很多代码,毫无疑问自动生成的代码是通过APT技术来完成的。
1、拆布局在布局转化为DataBinding布局时,DataBinding为根布局外再包裹了一层
第一个文件build/intermediates/data_binding_layout_info_type_merge/debug/out/activity_data_binding-layout.xml
false false true true
可以看到每个Target节点都记录了布局中的控件的id以及tag,通过这些tag就可以读出布局,再来看一下拆过的第二个文件build/intermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_data_binding.xml
这个文件其实就是我们自己编写的布局文件,但每个控件都被加上了tag。
2、DataBindingUtil.setContentViewDataBinding源码很庞大,但使用代码很少,我们来看一下最关键的这个方法,点进去可以看到其实也调用了activity.setContentView(layoutId);
public staticT 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); }
设置布局以后,拿到decorView和contentView,最后返回的是bindToAddedViews,我们来继续跟进一下
private staticT 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); } }
可以看到bindToAddedViews方法是在遍历所有子View,并最终调用bind方法,跟进bind方法再来看一下
staticT 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); }
这里的sMapper是一个DataBinderMapper对象,其实现类是DataBinderMapperImpl,DataBinderMapperImpl是通过APT注解处理器生成的。这里的sMapper.getDataBinder()其实就是调用的MergedDataBinderMapper的getDataBinder()方法,而sMapper中的数据,其实就是DataBinderMapperImpl的构造器中调用其父类MergedDataBinderMapper的addMapper()方法添加的对象
public class DataBinderMapperImpl extends MergedDataBinderMapper {
DataBinderMapperImpl() {
addMapper(new com.zry.jetpackdemo.DataBinderMapperImpl());
}
}
而MergedDataBinderMapper的getDataBinder()方法,遍历了sMapper,也就是拿到每个DataBinderMapperImpl去执行它的getDataBinder方法
@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;
}
再跟进一下getDataBinder方法,DataBinderMapperImpl是APT注解处理器生成的类。如果是布局的顶层View,比如tag为layout/activity_data_binding_0,那么就会new一个ActivityDataBindingBindingImpl对象。
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_ACTIVITYDATABINDING: {
//根布局tag就是这个
if ("layout/activity_data_binding_0".equals(tag)) {
return new ActivityDataBindingBindingImpl(component, view);
}
throw new IllegalArgumentException("The tag for activity_data_binding is invalid. Received: " + tag);
}
}
}
return null;
}
ActivityDataBindingBindingImpl会进行一些View的绑定操作,将通过tag取出的View与
ActivityDataBindingBindingImpl中对应的View属性进行绑定。
public ActivityDataBindingBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
this(bindingComponent, root, mapBindings(bindingComponent, root, 5, sIncludes, sViewsWithIds));
}
第三个参数为mapBindings方法的返回结果,继续跟进看一下,进入了ViewDataBinding类中
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;
}
这里创建了一个Object数组,数组的大小为传进来的第三个参数,也就是布局的有tag的控件个数,继续跟进一下mapBindings方法
private static void mapBindings(DataBindingComponent bindingComponent, View view,
Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
boolean isRoot) {
final int indexInIncludes;
// 判断View是否已经存在绑定,如果已经绑定,则直接return
final ViewDataBinding existingBinding = getBinding(view);
if (existingBinding != null) {
return;
}
// 获取View的tag标签
Object objTag = view.getTag();
final String tag = (objTag instanceof String) ? (String) objTag : null;
boolean isBound = false;
// 如果tag是根布局,并且是以layout开头的tag
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);
// 将根布局标签对应的View放在bindings数组中
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);
}
}
}
}
可以看出mapBindings方法其实就是去寻找tag是layout_开头的,tag是binding_开头的,拿到这些设置了tag的view,最后都保存到Object[]中,这里其实是比较消耗内存的地方。至此,DataBinding就通过拆解布局,设置tag,将所有的view保存到了一个数组中。
3、设置数据DataBindingUtil.setContentView方法完成后,已经将所有布局交给DataBinding了。接下来再看一下DataBinding.setUser方法,也就是设置数据类。在生成的ActivityDataBindingBindingImpl实现类中可以找到这个方法
public void setUser(@Nullable com.zry.jetpackdemo.databinding.User User) {
this.mUser = User;
synchronized(this) {
mDirtyFlags |= 0x4L;
}
notifyPropertyChanged(BR.user);
super.requestRebind();
}
跟进一下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);
}
}
}
这个方法可以看到,DataBinding同样使用到了Lifecycle中的生命周期状态,最后会调用mChoreographer.postframeCallback(mframeCallback);或mUIThreadHandler.post(mRebindRunnable);,其实mframeCallback最后也会使用mRebindRunnable,mRebindRunnable在ViewDataBinding的构造中可以找到,run方法内部执行代码如下:
synchronized(this) {
ViewDataBinding.this.mPendingRebind = false;
}
ViewDataBinding.processReferenceQueue();
if (VERSION.SDK_INT >= 19 && !ViewDataBinding.this.mRoot.isAttachedToWindow()) { ViewDataBinding.this.mRoot.removeOnAttachStateChangeListener(ViewDataBinding.ROOT_REATTACHED_LISTENER);
ViewDataBinding.this.mRoot.addOnAttachStateChangeListener(ViewDataBinding.ROOT_REATTACHED_LISTENER);
} else {
ViewDataBinding.this.executePendingBindings();
}
excutePendingBindings方法最终会执行子类ActivityDataBindingBindingImpl中的executeBindings()方法
@Override
protected void executeBindings() {
...
// batch finished
if ((dirtyFlags & 0xdL) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etAge, userAgeGet);
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvAge, userAgeGet);
}
if ((dirtyFlags & 0x8L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.etAge, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, etAgeandroidTextAttrChanged);
androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.etName, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, etNameandroidTextAttrChanged);
}
if ((dirtyFlags & 0xeL) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etName, userNameGet);
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvName, userNameGet);
}
}
...
}
到这里我们发现,最终执行的就是setText()以及添加textWatcher的操作,setText就是M—>V一向,textWatcher就是V—>M一向。至此DataBinding实现实现双向绑定的大致流程就分析完了,当然里面还有很多细节没有看,DataBinding代码量还是非常多的,在看源码时,要抓住主线流程,不然就会迷失在源码的海洋里。



