MVVM架构篇之databinding源码解析

简介: databinding是google去年发布的一个库,它支持在xml中写表达式使得viewModel中的数据能够绑定到view中,目前已经支持双向绑定,也就是说数据的改变能够反馈到界面上,界面的数据也能够主动传到viewModel中。

databinding是google去年发布的一个库,它支持在xml中写表达式使得viewModel中的数据能够绑定到view中,目前已经支持双向绑定,也就是说数据的改变能够反馈到界面上,界面的数据也能够主动传到viewModel中。虽然我已经用这个库已经有4个月了,但是它的内在机制我一直没有去探寻,所以本片博客就是来深究databinding的运行机制。

1.了解 DataBindingUtil和DataBinderMapper##

这是一个工具类主要就是帮助我们获取和生成View所对应的ViewDataBinding类。其中的方法分为以下几个种类

  • 1.inflate(LayoutInflater inflater, int layoutId,ViewGroup parent, boolean attachToParent)
    这个方法是将布局转化成一个ViewDataBinding,inflater和layoutId就不用说了。attachToParent表示是否将layoutId生成的View放入parent中。
  • 2.bind(View root)
    若我们已经得到了一个view并且其是由有databinding的xml文件生成的,那么我们就可以通过这个方法来获得ViewDataBinding,相当于上面一个方法的简化版。
  • 3.findBinding(View view)
    若一个view是一个使用过databinding的view的子view,那么我们就可以通过这个函数来寻找到其父view的ViewDataBinding。大家进入源代码中查看会发现,其内部只不过是通过while来不断的遍历当前view的父view来判断该父view是否绑定了ViewDataBinding。注意这个和前面的两个不同,这个方法的ViewDataBinding已经生成,本方法只不过是去找寻找而已
  • 4.getBinding(View view)
    这个方法和3一样也是去寻找已经生成的ViewDataBinding,不过这里的view就是已经使用过databing的view。
  • 5.setContentView(Activity activity, int layoutId)
    这个方法是将一个使用了databinding的view绑定到一个activity中去。其源码也很简单,先调用了该activity的setContentView(layoutId)。然后获取activity的decorView中的bodyView(我想了解activity绘制机制的同学应该了解这东西),将其作为该view的parent view。最终调用前面的bind()方法生成ViewDataBinding。
  • 6.getDataBinder(DataBindingComponent bindingComponent, View view, int layoutId)
    这个方法在自动生成的DataBinderMapper中,前面DataBindingUtil需要生成ViewDataBinding的方法最终都是调用了这个方法。这个方法通过layoutId找到需要生成的具体的ViewDataBinding类,然后调用ViewDataBinding的bind(view, bindingComponent)来获取ViewDataBinding实体。

2.了解ViewDataBinding

首先大家都知道每个使用了databiding的xml文件经过编译之后都会自动生成一个继承于ViewDataBinding的文件,这个类是该xml文件view的管理类。我们可以通过操作这个文件来对xml文件生成的view进行操作。我也介绍几个该类中主要的方法:

  • 1.setVariable(int variableId, Object variable)
    这个方法是设置我们在xml文件中绑定的viewModel用的,variableId是BR文件中的id,variable是我们要绑定的实体。除此之外,该类中还会生成对应字段的set方法,比如我xml文件中字段名为viewModel,那么其会生成一个setViewModel(MainActivityViewModel viewModel)方法。每一次设置这些字段都会调用父类的requestRebind()方法来异步绑定我们设置的数据。这个我们在后面会进行源码解析。
  • 2.executeBindings()
    这个方法就是为xml文件中的view设置我们绑定数据的具体方法了,该方法在父类中是抽象方法,具体实现每一个xml对应的类都是不同的。该方法是通过requestRebind()方法被异步在主线程中调用的,所以在实际运用过程中,无论你在什么线程中对被观察的字段赋值(也就是Observable化的字段),都不会报错。
  • 3.invalidateAll()
    这个方法在父类中也是抽象方法,具体实现就是强行调用requestRebind()来进行view的异步刷新,如果你有个字段没有Observable化,那么在重新赋值之后,可以调用这个方法进行view的刷新。
  • 4.addOnRebindCallback(OnRebindCallback listener)
    这个方法是设置绑定周期的监听器的方法,也就是说在整个绑定周期中,OnRebindCallback中的方法会被调用。比如说onPreBind(ViewDataBinding binding)会在executeBindings()调用之前被调用。onCanceled(T binding) 会在绑定取消的时候调用。
  • 5.executePendingBindings()
    这个方法在父类中实现,对executeBindings()之前和之后进行处理,比如说我们4中说的监听器的方法就是在这个类中调用的。
  • 6.hasPendingBindings()
    这个方法在子类中实现,返回一个boolean数值。其内部是判断是否有Observable化的字段数据被更新。

3.databinding的初始化绑定和数据动态更新机制解析

我们前面了解了databinding的几个重要类的方法,那么现在我们就能通过源码分析很容易的了解到其初始化和数据动态更新的机制了。

  • 1.databinding的初始化
    • 1.将一个绑定一个view有以下两种方法:第一种方法实际上就是用了第二个方法,所以我们重点解析第一个方法。
      • 1.调用DataBindingUtil.setContentView()来设置Activity的视图。
      • 2.调用DataBindingUtil.inflate()来设置fragment和listView的item或者RecycleView的item。
    • 2.大家可以打开AS跟着我进入源码查看该方法到底是怎么调用的。
      • 1.该方法调用了一个重载方法setContentView(activity, layoutId, sDefaultComponent)们,前两个参数不变,最后一个参数是静态实例子,会自动加载。
      • 2.再点击进入
        • 1.发现第一行代码就是我们不是使用databinding时候,activity初始化view的方式。
        • 2.2,3行代码是获取我们xml文件生成的view的父view的父view,大家如果不清楚可以去了解一下activity界面的初始化过程。最后获取到的contentView是一个FrameLayout,其中有一个LinnearLayout这个View就是用来放置我们xml文件生成的view的。
        • 3.最后则是调用了这个方法bindToAddedViews(bindingComponent, contentView, 0, layoutId)
      • 3.再点击进入
        • 1.1,2行我们会得到childrenAdded=1
        • 2.进入第一个if之后4行我们获取到了真正的xml生成的view的父view了。
        • 3.然后调用了bind(component, childView, layoutId)
      • 4.进入之后我们会发现调用了sMapper.getDataBinder(bindingComponent, root, layoutId)。
        • 1.sMapper这个实例是在我们编译完成之后生成的。大家可以去项目下面被编译的文件夹中招,我的目录是D:\AndroidStudio\project\TestRecycleView\app\build\intermediates\classes\debug\android\databinding。
        • 2.然后大家就可以看见getDataBinder()其实就是一个switch方法,里面会根据layoutId调用对应的ViewDataBinding子类的inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot)。
        • 3.进入之后会调用ViewDataBinding对应子类的构造函数。每个xml生成的ViewDataBinding子类的构造函数都是不同的,但是里面一定会调用mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds)方法来获取xml文件中所有使用了databinding的view。
      • 5.进入mapBindings()之后先构造了一个Object[]用来存放view。
        • 1.然后进入了重载函数,mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true),这个函数很复杂,我们只要知道该函数实例化了xml中所有使用了databinding的view,并将整个xml对应的view添加到了root中,也就是我们从activity中获取的view,此外注意如果xml文件中使用了layout标签,并且也使用了databinding,那么该标签会对应成为一个ViewDataBinding子类,并且将其对应的view添加到当前的view之中,如此。
        • 2.返回Object[]给ViewDataBinding子类。
      • 6.我们回到ViewDataBinding对应子类的构造函数中。
        • 1.后面的几行代码会根据返回的Object[]来设置ViewDataBinding子类的成员view,注意如果有view设置了id,那么其访问权限是public,否则则是private。
        • 2.倒数第二行会调用setRootTag(root),给root这个view设置tag,而这个tag就是ViewDataBinding,所以我们有时候可以通过绑定的view来获取ViewDataBinding。
        • 3.最后一行会调用invalidateAll(),这个方法我们在前面说过,现在我们来具体讲讲。
      • 7.invalidateAll()中先判断是否有字段发生了改变,然后会调用requestRebind()。进入这个方法。
      • 8.requestRebind()
        • 1.进入之后会先同步mPendingRebind这个变量,在当前的情况下其为false。
        • 2.然后进入一个if选择项,当我们的sdk大于16(即android4.1以上)会进入第一个选项。我们就以第一个为例。mChoreographer.postFrameCallback(mFrameCallback)这个函数调用就是异步的开始,所以我们只要吧目光集中在mFrameCallback中就可以了。
      • 9.mFrameCallback在ViewDataBinding的构造函数中初始化,注意不是其子类,其仅有一个doFrame(long frameTimeNanos)的方法。该方法调用了mRebindRunnable.run()。
      • 10.mRebindRunnable是一个Runnable类,这时候已经运行在了主线程。
        • 1.run()中先同步了一下mPendingRebind = false,我们结合前面可以知道,此时是禁止调用requestRebind()的。
        • 2.然后是一个if选择,这一段我们不用关心。
        • 3.最后一行我们看见了熟悉的函数executePendingBindings(),这个方法我们前面说过是处理executeBindings()前后的函数,我们继续进入。
      • 11.进入了之后
        • 1.会有一个变量mIsExecutingPendingBindings,此时该变量是false,所以我们跳过。
        • 2.然后是hasPendingBindings(),我们前面解释过这个函数,所以这里返回的是true,也跳过。
        • 3.然后跳过两个赋值,我们看见了mRebindCallbacks,这个回调就是我们前面说过的监听整个绑定流程的监听器的回调。在executeBindings()调用的前,取消,后。这三个阶段,分别会调用监听器的三个函数。
        • 4.最后就是调用executeBindings()进行view的数据刷新了,这个方法的实现是在ViewDataBinding的子类中。大体上就是一些赋值之类和判断之类的操作。这个函数结束了,整个databinding的初始化流程就结束了。
      • 12.这里还有一些额外的内容,有一些函数也会requestRebind(),比如说我们前面说的setVariable(),还有就是我们set自定义字段的时候,如我前面说的setViewModel()。
      • 13.再加点额外的内容,可能有些同学不清楚如何进入一个方法。快捷键是ctrl-B,然后查找某个方法和字段在那些地方使用过也是这个快捷键。另外可能有些源码中的方法和字段使用ctrl-B查找不到使用过的地方,这时候就可以用ctrl-alt-F7,在全局查找。
  • 2.databinding的Observable字段自动更新要实现字段的自动更新有两种办法,我比较亲睐于使用ObservableBoolean和ObservableField等封装类来实现,这样代码的侵入性不强。那么我们就来像刚刚一样一步步深入源码了解自动更新的机制。
    • 1.我们调用某个Observable实例的set方法,进入查看。发现其调用了父类BaseObservable的notifyChange()。再进入
    • 2.发现调用了mCallbacks.notifyCallbacks()方法,该回调是在databinding初始化时executeBindings()中的updateRegistration()中设置的。
    • 3.我们再进入,发现notifyCallbacks()中非常复杂,所以我就直接跳到大家能看的明白的层次中。这个方法最终会进入到ViewDataBinding的内部类WeakPropertyListener的onPropertyChanged(Observable sender, int propertyId)方法中。
    • 4.WeakPropertyListener是一个弱引用监听器
      • 1.第一行先获取了Observabel实例所在的ViewDataBinding。
      • 2.然后调用了ViewDataBinding的handleFieldChange(mListener.mLocalFieldId, sender, propertyId)
    • 5.进入这个方法中大家可以看见熟悉的requestRebind(),接下来的流程就和我们前面讲的databinding的初始化流程的后半段一样了。
    • 6.还有点额外的内容,就是我们前面说的只是某个字段的动态更新机制,但是其实map和List的机制也都差不多,我们可以在ViewDataBinding找到List对应的内部类WeakListListener和map对应的内部类WeakMapListener其实都是一样的。

以上就是这篇博客的全部内,databinding也用了几个月了,也写用它为基础基于MVVM写了两个商业项目+一个比赛的app,对MVVM架构下的app开发,还是颇有心得的,所以过一阵子我会给大家分享一篇MVVM+databinding+RxJava2+Retrofit2的项目架构博客。在此之前的会写一些项目架构中库的解析,让大家知其然更知其所以然,今天的这篇博客就是开篇。希望大家能多多关注!

相关文章
|
6月前
|
人工智能 安全 Java
智慧工地源码,Java语言开发,微服务架构,支持分布式和集群部署,多端覆盖
智慧工地是“互联网+建筑工地”的创新模式,基于物联网、移动互联网、BIM、大数据、人工智能等技术,实现对施工现场人员、设备、材料、安全等环节的智能化管理。其解决方案涵盖数据大屏、移动APP和PC管理端,采用高性能Java微服务架构,支持分布式与集群部署,结合Redis、消息队列等技术确保系统稳定高效。通过大数据驱动决策、物联网实时监测预警及AI智能视频监控,消除数据孤岛,提升项目可控性与安全性。智慧工地提供专家级远程管理服务,助力施工质量和安全管理升级,同时依托可扩展平台、多端应用和丰富设备接口,满足多样化需求,推动建筑行业数字化转型。
207 5
|
6月前
|
机器学习/深度学习 文字识别 监控
安全监控系统:技术架构与应用解析
该系统采用模块化设计,集成了行为识别、视频监控、人脸识别、危险区域检测、异常事件检测、日志追溯及消息推送等功能,并可选配OCR识别模块。基于深度学习与开源技术栈(如TensorFlow、OpenCV),系统具备高精度、低延迟特点,支持实时分析儿童行为、监测危险区域、识别异常事件,并将结果推送给教师或家长。同时兼容主流硬件,支持本地化推理与分布式处理,确保可靠性与扩展性,为幼儿园安全管理提供全面解决方案。
254 3
|
3月前
|
运维 安全 数据可视化
采用PHP+Vue技术架构的不良事件管理系统(源码)
本系统为医院安全(不良)事件管理工具,支持快速上报、流程化处理与多维度分析,助力识别风险、优化管理。采用PHP+Vue技术架构,功能涵盖事件上报、追踪整改、数据统计及PDCA改进等。
122 0
|
7月前
|
存储 机器学习/深度学习 应用服务中间件
阿里云服务器架构解析:从X86到高性能计算、异构计算等不同架构性能、适用场景及选择参考
当我们准备选购阿里云服务器时,阿里云提供了X86计算、ARM计算、GPU/FPGA/ASIC、弹性裸金属服务器以及高性能计算等多种架构,每种架构都有其独特的特点和适用场景。本文将详细解析这些架构的区别,探讨它们的主要特点和适用场景,并为用户提供选择云服务器架构的全面指南。
796 18
|
7月前
|
运维 供应链 前端开发
中小医院云HIS系统源码,系统融合HIS与EMR功能,采用B/S架构与SaaS模式,快速交付并简化运维
这是一套专为中小医院和乡镇卫生院设计的云HIS系统源码,基于云端部署,采用B/S架构与SaaS模式,快速交付并简化运维。系统融合HIS与EMR功能,涵盖门诊挂号、预约管理、一体化电子病历、医生护士工作站、收费财务、药品进销存及统计分析等模块。技术栈包括前端Angular+Nginx,后端Java+Spring系列框架,数据库使用MySQL+MyCat。该系统实现患者管理、医嘱处理、费用结算、药品管控等核心业务全流程数字化,助力医疗机构提升效率和服务质量。
366 4
|
7月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
187 4
|
7月前
|
算法 前端开发 定位技术
地铁站内导航系统解决方案:技术架构与核心功能设计解析
本文旨在分享一套地铁站内导航系统技术方案,通过蓝牙Beacon技术与AI算法的结合,解决传统导航定位不准确、路径规划不合理等问题,提升乘客出行体验,同时为地铁运营商提供数据支持与增值服务。 如需获取校地铁站内智能导航系统方案文档可前往文章最下方获取,如有项目合作及技术交流欢迎私信我们哦~
392 1
|
7月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
7月前
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
7月前
|
消息中间件 安全 NoSQL
布谷直播系统源码开发实战:从架构设计到性能优化
作为山东布谷科技的一名技术研发人员,我参与了多个直播系统平台从0到1的开发和搭建,也见证了直播行业从萌芽到爆发的全过程。今天,我想从研发角度,分享一些直播系统软件开发的经验和心得,希望能对大家有所帮助。

推荐镜像

更多
  • DNS