写点什么

漫谈 MVVM(1)ViewModel_DataBinding 核心原理

用户头像
Android架构
关注
发布于: 6 小时前

ViewModel 是 androidx 包中抽象类。它是谷歌开放给全世界开发者用来改善项目架构的一个组件。


<img src="漫谈 MVVM.assets/image-20200530155155198.png" alt="image-20200530155155198" style="zoom:67%;" />


既然是探索 ViewModel 的本源,那就从它的官方注解开始吧。



这段话的大意是:


ViewModel 是一个准备和管理 Activity 和 Fragment 的数据的类。它也可以掌控 Activity、Fragment 和应用中其他部分的通讯。

一个 ViewModel 总是关联到一个域(Activity 或 Fragment)被创建。并且只要域是存活的,ViewModel 就会一直被保留。比如。如果域是一个 Activity,ViewModel 就会存活,直到 Activity 被 finish。

换句话说,这意味着,如果它的持有者由于配置改变而被销毁时(比如屏幕旋转),ViewModel 并不会被销毁。新的持有者实例,将会仅仅重新连接到已经存在的 ViewModel。

ViewMode 存在的目的,就是为 Activity/Fragment 获得以及保留 必要信息。 Activity / Fragment 应该可以观察到 VIewModel 的变化,ViewModel 通常通过 LiveData 或者 DataBinding 暴露信息。你也可以你自己喜欢的使用可观察的结构框架。

ViewModel 仅有的职责,就是为 UI 管理数据,它不应该访问到你任何的 View 层级 或者 持有 Activity 、Fragment 的引用。


谷歌爸爸其实已经把意思讲的很明白,上面一段话中有几个重点:


  • ViewModel 唯一的职责 就是 在内存中保留数据

  • 多个 Activity 或者 Fragment 可以共用一个 ViewModel

  • 在屏幕旋转时,ViewModel 不会被重建,而只会连接到重新创建的 Fragment/Activity

  • 使用 ViewModel 有两种方式,LiveData 或者 DataBinding(或者你可以自定义观察者模式框架),用他们来暴露 ViewModel V

核心功能

ViewModel 的核心功能:在适当的时机执行回收动作,也就是 onCleared() 函数释放资源。而这个合适的时机,可以理解为 Activity 销毁,或者 Fragment 解绑。


借用一张图来解释,就是:



在整个 Activity 还处于存活状态时,ViewModel 都会存在。而当 Activity 被 finish 的时候,ViewModel 的 onCleared 函数将会被执行,我们可以自己定义函数内容,清理我们自己的资源,在 Activity 被销毁之后。该 ViewModel 也不再被任何对象持有,下次 GC 时它将被 GC 回收。

基本用法

创建一个新的项目,定义我们自己的 UserModel 类,继承 ViewModel:


import android.util.Logimport androidx.lifecycle.ViewModel


class UserModel : ViewModel() {


init {Log.d("hankTag", "执行 ViewModel 必要的初始化")}override fun onCleared() {super.onCleared()Log.d("hankTag", "执行 ViewModel 清理资源")}


fun doAction() {Log.d("hankTag", "执行 ViewModel doAction")}}


在 View 层使用定义好的 ViewModel:


import androidx.appcompat.app.AppCompatActivityimport android.os.Bundleimport androidx.lifecycle.ViewModelProvider


class MainActivity : AppCompatActivity() {


override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)


// 获取 ViewModel 对象 val userModel = ViewModelProvider(this).get(UserModel::class.java)// 使用 ViewModel 对象的函数 userModel.doAction()}}


就这么简单,运行程序能看到日志:


<img src="漫谈 MVVM.assets/image-20200602104022542.png" alt="image-20200602104022542" style="zoom: 100%;" />


同时 ViewModelProvider 也支持两个参数的构造函数,除了上面的 owner=this 之外,还可以传入另一个 Factory 参数。


如果不传入这个 Factory,源码中会在拿到 ViewModel 的 class 对象之后通过无参构造函数进行反射创建对象。但是如果 ViewModel 要用有参构造函数来创建的话,那就必须借助 Factory:


// ViewModelclass UserModel(i: Int, s: String) : ViewModel() {


var i: Int = ivar s: String = s


init {Log.d("hankTag", "执行 ViewModel 必要的初始化")}


override fun onCleared() {super.onCleared()Log.d("hankTag", "执行 ViewModel 清理资源")}


fun doAction() {Log.d("hankTag", "执行 ViewModel doAction: i = s")}


}


// ViewModelFactoryclass UserModelFactory(val i: Int, val s: String) : ViewModelProvider.Factory {override fun <T : ViewModel?> create(modelClass: Class<T>): T {return modelClass.getConstructor(Int::class.java, String::class.java).newInstance(i, s)}}// View 层 class MainActivity : AppCompatActivity() {


override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)


// 获取 ViewModel 对象 val userModel = ViewModelProvider(this, UserModelFactory(1, "s")).get(UserModel::class.java)// 使用 ViewModel 对象的函数 userModel.doAction()}}


运行结果:


06-02 11:20:53.196 32569-32569/com.zhou.viewmodeldemo D/hankTag: 执行 ViewModel 必要的初始化 06-02 11:20:53.196 32569-32569/com.zhou.viewmodeldemo D/hankTag: 执行 ViewModel doAction: i = 1, s : s06-02 11:20:57.836 32569-32569/com.zhou.viewmodeldemo D/hankTag: 执行 ViewModel 清理资源

核心原理

源码探索的目标是,ViewModel 是如何感知 Activity 的生命周期清理自身资源的。其实也就是看 onCleared 函数是如何被调用的。


  • ViewModelProvider(this).get(UserModel::class.java)


上面这句代码是是用来获得 ViewModel 对象的。这里分为 2 个部分,其一,ViewModelProvider 的构造函数:



上面标重点的注释的意思是:创建一个 ViewModelProvider,这将会创建 ViewModel 并且把他们保存到给定的 owner 所在的仓库中。这个函数最终调用了重载的构造函数:



这个构造函数有两个参数,一个 store,是刚才通过 owner 拿到的,一个是,Factory。store 顾名思义,是用来存储 ViewModel 对象的,而 Factory 的意义,是为了通过 class 反射创建对象做准备的。


使用构造函数创建出一个 ViewModelProvider 对象之后,再去get(UserModel::class.java)



通过一个 class 对象,拿到他的 canonicalName 全类名。然后调用重载 get 方法来获取真实的 ViewModel 对象。



这个 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 对象的引用即可拿到


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


所有的控件 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 onCreate(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 布局。


随后该函数调用到了:



用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
漫谈MVVM(1)ViewModel_DataBinding核心原理