写点什么

漫谈 MVVM(1)ViewModel_DataBinding 核心原理,kotlin 开发安卓游戏

用户头像
Android架构
关注
发布于: 36 分钟前

这个 get 函数有两个参数,其一,key,字符串类型。用于做标记,使用的是一个定死的字符串常量 DEFAULT_KEY 拼接上 modelClass 的全类名,其二,modelClass 的 class 对象,内部代码会使用 class 进行反射,最终创建出 ViewModel 对象。


上面提到了一个重点:Store 仓库,创建出来的 ViewModel 都会被存入 owner 所在的仓库。那么,阅读仓库的源码:



那么一个 Activity,它作为 ViewModelStoreOwner,他自己的 viewModelStore 何时清理?



答案是:onDestroy() . 但是这里有一个特例,配置改变,比如屏幕旋转时,ViewModelStore 并不会被清理。并且,Fragment 的源码中也有类似的调用:


总结

ViewModel 的核心,是自动清理资源。我们可以重写 onCleared 函数,这个函数将会被 ViewModel 所在的 Activity/Fragment 执行 onDestory 的时候被调用,但是当屏幕旋转的时候,并不会清理。在 ViewModel 的架构中,有几个关键类,


  • ViewModelProvider 用于获取 ViewModel

  • ViewModelStore 用于存储 ViewModel

  • ViewModelStoreOwner 用于提供 ViewModelStore 对象,Activity 和 Fragment 都是 ViewModelStoreOwner 的实现

  • ViewModelProvider 的内部类 Factory,用于支持 ViewModel 的有参构造函数,毕竟 ViewModel 对象是通过 class 反射创建出来的,需要支持默认无参,以及手动定义有参构造函数



DataBinding

DataBinding,单词意思: 数据绑定,用于降低布局和逻辑的耦合性,使代码逻辑更加清晰。MVVM 相对于 MVP,其实就是将 Presenter 层替换成了 ViewModel 层。DataBinding 能够省去我们一直以来的 findViewById() 步骤,大量减少 Activity 内的代码,数据能够单向或双向绑定到 layout 文件中,有助于防止内存泄漏,而且能自动进行空检测以避免空指针异常.


DataBinding


  • 支持在 java 代码中不用 findViewById 来获取控件,而直接通过 DataBinding 对象的引用即可拿到所有的控件 id

  • 进行数据绑定,使得 ViewModel(数据)变化时,View 控件的属性随之改变

  • 支持 数据的双向绑定,改变 View 控件的属性,那该属性绑定的 ViewModel(数据)随之改变

  • 支持将任何类型的 ViewModel 绑定到 View 控件上,包括系统提供的类型(包括基础类型集合类型),以及自定义的类型

  • 支持特殊的 View 属性,比如 ImageView 的图片源,可以自定义图片加载的具体过程(Glide...)

  • 支持在 xml 中写简单的表达式, 比如函数调用,三元表达式...

  • 支持对 res 资源文件的引用,比如 dimen,string...

  • 支持与 LiveData(下文会解释概念)合作,让数据的变动 关联 Activity / Fragment 的 生命周期


ViewModel 的注释中我们得知,DataBinding 是向 View 层暴露 ViewModel 的一种方式。但是事实上并非如此,DataBinding 只是数据绑定,它和 ViewModel 抽象类没有半毛钱关系。DataBinding 绑定的双方:是 数据(别多想,就是纯粹的数据,不涉及到生命周期视图。而 MVVM 的核心是 ViewModel 抽象类,核心功能是感知持有者 Activity/Fragment 的生命周期来释放资源,防止泄露。我们使用 DataBinding,创建封装数据类型,也不用继承 ViewModel 抽象类。至于 ViewModel 抽象类的注释上为什么这么说,我也是很费解。但是看了许多 DataBinding 的资料,项目,包括在自己的项目中使用 DataBinding 之后,它给我的感受就是:很糟糕。没错,糟透了,也许是因为时代进步了,也许是因为我的代码洁癖,DataBinding 放入我的代码,我总感觉有一种黏乎乎的感觉,就和最早的 JSP 一样,一个 HTML 文件中,混入了 HTML 标签,js 代码,以及 java 代码,尽管我承认 DataBinding 的功能很强大,但是使用起来确实不舒服。有一些老代码如果大量使用了这种写法,我们了解一些 DataBinding 核心原理也是有必要的。

核心功能

DataBinding 的核心功能是:支持 View 和数据的单向或者双向绑定关系,并且最新版源码支持 setLifecycleOwner 设置生命周期持有者

基本用法

在所在 module 的 build.gradle 文件中,找到 androd 节点:插入以下代码来开启 DataBinding


dataBinding{enabled true}


改造布局 xml,使用**<layout></layout>**标签包裹原来的布局,并且插入<data>节点


<?xml version="1.0" encoding="utf-8"?><layout><data><import type="androidx.databinding.ObservableMap" /><import type="androidx.databinding.ObservableList" />


<variablename="userBean"type="com.zhou.databinding.UserBean" /><variablename="map"type="ObservableMap<String, Object>" />


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".MainActivity">


这里支持 import 操作,类似 java 的 import 导包,导入之后就能在**@{}** 中使用引入之后的函数和类. 如果想双向绑定,就使用**@={}。Varilable 标签是用来定义数据**的,name 随意,字符串即可。type 必须是封装类型的全类名,支持泛型实例。


Java/kotlin 代码层面:


数据的绑定支持几乎所有类型,包括 jdk,sdk 提供的类,或者可以自定义类:


class UserModel {


val user = User("hank001", "man")


}


对,这里命名为 UserModel,但是它和 androidX 里面的抽象类 ViewModel 没有半毛钱关系。


Activity 中,需要使用 DataBindingUtil 将当前 activity 与布局文件绑定。


class MainActivity : AppCompatActivity() {private lateinit var binding: ActivityMainBindingoverride fun onCreat


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


e(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = DataBindingUtil.setContentView(this, R.layout.activity_main)binding.lifecycleOwner = thisbinding.title = "asfdsaf"


val map = ObservableArrayMap<String, Any>().apply { put("count", 0) }binding.map = map


binding.userBean = UserBean()


Thread {for (i in 0..100) {binding.title = "asfdsafi"


Thread.sleep(10)}}.start()


}}


上面的代码,如果运行起来,



可以看到我并未主动去使用 textview 的引用去操控它的 text 属性。这些工作都是在 databinding 框架中完成的。至于更具体更复杂的用法,本文不再赘述。网上很多骚操作。

核心原理

核心功能是 数据绑定,也就是说,只要知道了 databinding 是如何在数据变化时,通知到 view 让它改变属性的,databinding 的秘密就算揭开。直接从代码进入源码。这一切的源头,都是由于我们使用了 DataBindingUtil 来进行绑定引起的。那么就从它开始。


binding = DataBindingUtil.setContentView(this, R.layout.activity_main)binding.title = "asfdsaf"



注释的大意是:将 Activity 的内容 View 设置给 指定 layout 布局,并且返回一个关联之后的 binding 对象。指定的 layout 资源文件不能是 merge 布局。


随后该函数调用到了:



这里首先使用 activity.setContentView,将 layoutId 设置进去,常规操作。然后,拿到 activity decorView,进而拿到 contentView,随后调用 bindToAddViews



继续追踪 bind 函数:



目标转移到了 sMapper.getDataBinder(),进去看了之后发现是抽象类,找到他的实现类:



结果发现了我自己的包名,看到这里应该有些明白了,我并没有写这个 DataBindingMapperImpl 类,它只能是 as 帮我们自动生成的。



所以,绑定 View 和数据的具体代码应该在这个类里面有答案,经过追踪,发现代码走到了这一行:



回到一开始,


binding = DataBindingUtil.setContentView(this, R.layout.activity_main)


这句话,饶了一大圈,最终得到了一个 ActivityMainBindingImpl 对象,随后我们用这个对象去操作 view 引用来绑定数据


binding.title = "asfdsaf"


那就直接从这个 title 看起,上面是 kotlin 的 setTitle 写法,直接看 setTitle 方法:



其实它就是把 xml 布局文件中的 title 属性值设置为 传入的形参值。然后 notifyPropertyChanged(BR.title): 通知 ID 为 BR.title 的属性值发生了改变。



直接进到了观察者模式 Observable 接口的一个实现类 BaseObservable,由于 as 的原因,代码无法继续索引(它会直接跳到 xml 文件),但是经过 debug,我发现,当 title 发生变化时,



从上面的命名可以看出,DataBinding 框架应该是给每一个 xml 中定义的变量 variable 都建立了一个独立的监听器,在 variable 发生变化时,这个监听器会在 variable 发生改变时,通知界面元素发生属性变更。,查找这个监听器的调用位置 executeBinding()函数,结果有了意外发现,“双向数据绑定”的原理也被揭开。



这里传了 3 个参数,BeforeTextChanged,OnTextChanged,AfterTextChanged,刚好对应了 TextWatcher 接口中的 3 个方法。进入看一眼上面的 setTextWatcher():



在 textView 的内容发生变更的时候,也会执行到监听器的 onChange 函数,进行数据变更。


上面的追踪仅仅是针对


<variablename="title"type="java.lang.String" />


这一种定义 databinding 变量的方式。如果是 map 类型,map 的内部元素发生变更,UI 也是可以随之更新的,又是怎么回事呢?


<variablename="map"type="ObservableMap<String, Object>" />


经过一番追踪,发现有点不同。map 的用法有点不同:


<TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{String.valueOf(map.count)}"android:textSize="30sp"tools:text="userBean:" />


当我在 activity 中变更 map 的元素值的时候,



会执行到 setMap 方法:



map 的某一个元素值发生变化时,会执行到 handlerFieldChange



随后 onFieldChange 函数,



如果确认发生变更,就会 requestRebind()重新去绑定最新的 map 对象。


补充说明一点:


binding.lifecycleOwner = this


这句代码,



如果一个 DataBinding 对象的 mLifeCycleOwner 不是空,那么:



在绑定数据的时候,就会去判定当前 mLifeCycleOwner 是不是 STARTED 之后,如果不是,数据的绑定都不会执行

总结

综合以上所有小结论,总结一下:


  • 在 layout 中定义的每一个被使用的 variable 都会建立独立的监听器,没有被用到的,并不会有监听

  • 数据的单项绑定:数据变化,通知 View 设置属性,是通过观察者模式设置监听器来实现,具体的监听方式和 variable 的类型有关

  • 数据的双向绑定:其实是通过 textWatch 接口来实现,当 view 的 text 属性发生改变,会通知到该 view 已经绑定的监听器更新数据

  • DataBinding 提供了很多可观察的类型, 如 ObservableMap,ObservableList 等,如果是集合类型,必须用这个,才能实现内部元素的变动对接 UI 的变动。自定义类型可以继承 BaseObservable 并且重写 get set 方法来实现同样的绑定效果(具体如何做,这里不赘述)。

  • DataBinding 的 mLifeCycleOwner 属性可以让数据绑定感应 Activity/Fragment 的生命周期,防止内存泄漏.


未完待续....

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
漫谈MVVM(1)ViewModel_DataBinding核心原理,kotlin开发安卓游戏