- 一、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: ObservableField4、在xml的data标签中,引入数据类by lazy { ObservableField() } val age: ObservableField by lazy { ObservableField() } }
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代码量还是非常多的,在看源码时,要抓住主线流程,不然就会迷失在源码的海洋里。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)