前言
在开发模式的演进过程中 MVC,MVP,MVVM一一登上舞台。但是并不意味着MVVM一定就比MVC MVP优秀。不同的项目有不同的体量,开发中要根据项目体量选择合适的开发模式。
市面上介绍mvvm的项目不在少数,但是看了很多,都在介绍源码原理,开发中的踩坑过程,而且有的是过时的资料,却很少见到能够直接从项目需求入手帮助不熟悉MVVM的开发者从入门到熟悉原理,再到框架优化的。这个空缺我来补充,如果提及到一些其他原理性的东西不便长篇展示,我会引用链接,或者引用他文的原文加以说明。
特别说明:
以下所有信息都基于截止到 2020年6月2日10点23分的最新官方资料和源码版本。如果存在任何历史版本的差异,本文不会过分纠结。
正文大纲
- 有关MVVM的几个重要组件
- ViewModel
- DataBinding
- LiveData
- LiveDataBus
- 案例实操
- MVVM的优缺点
正文
有关MVVM的几个重要组件
ViewModel
ViewModel 是androidx包中抽象类。它是谷歌开放给全世界开发者用来改善项目架构的一个组件。
既然是探索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.Log
import 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.AppCompatActivity
import android.os.Bundle
import 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()
}
}
就这么简单,运行程序能看到日志:
同时ViewModelProvider也支持两个参数的构造函数,除了上面的owner=this之外,还可以传入另一个Factory参数。
如果不传入这个Factory,源码中会在拿到ViewModel的class对象之后通过无参构造函数进行反射创建对象。但是如果ViewModel要用有参构造函数来创建的话,那就必须借助Factory:
// ViewModel
class UserModel(i: Int, s: String) : ViewModel() {
var i: Int = i
var 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 = $i, s : $s")
}
}
// ViewModelFactory
class 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 : s
06-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对象的引用即可拿到所有的控件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" />
<variable
name="userBean"
type="com.zhou.databinding.UserBean" />
<variable
name="map"
type="ObservableMap<String, Object>" />
<!-- 首先在这里定义,然后才能在代码中使用 -->
<!-- 定义顶层字段 -->
<variable
name="title"
type="java.lang.String" />
</data>
<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">
<!-- 双向绑定 -->
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={title}"
android:textSize="30sp"
tools:text="title:" />
<!-- 单向绑定 -->
<TextView
android:id="@+id/tvTitle2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{title}"
android:textSize="30sp"
tools:text="title:" />
<...></...>
</LinearLayout>
</layout>
这里支持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: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.lifecycleOwner = this
binding.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 = "asfdsaf$i" // 数据变更时,UI会发生变更
map["count"] = "count$i"
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的属性值发生了改变。
从上面的命名可以看出,DataBinding框架应该是给每一个xml中定义的变量variable都建立了一个独立的监听器,在variable发生变化时,这个监听器会在 variable 发生改变时,通知界面元素发生属性变更。,查找这个监听器的调用位置 executeBinding()函数,结果有了意外发现,“双向数据绑定”的原理也被揭开。
这里传了3个参数,BeforeTextChanged,OnTextChanged,AfterTextChanged,刚好对应了TextWatcher接口中的3个方法。进入看一眼上面的setTextWatcher():
在 textView的内容发生变更的时候,也会执行到监听器的onChange函数,进行数据变更。
上面的追踪仅仅是针对
<variable
name="title"
type="java.lang.String" />
这一种定义databinding变量的方式。如果是map类型,map的内部元素发生变更,UI也是可以随之更新的,又是怎么回事呢?
<variable
name="map"
type="ObservableMap<String, Object>" />
经过一番追踪,发现有点不同。map的用法有点不同:
<TextView
android: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的生命周期,防止内存泄漏.
LiveData
时代发展了,DataBinding的替代品来的很快。随着Kotlin的兴起,DataBinding可以减少findViewById的优势不复存在,而LiveData的发布,又为MVVM提供了一种新的可能。
LiveData, 意为: 存活的数据。我们还是从权威的官方注释开始:
大意翻译为:
LiveData是数据的持有者类,它可以被给定的"lifeCycleOwner"来观察。
这意味着,一个"observer"可以连同一个 lifeCycleOwner 被添加到一个pair中, 并且,当且仅当lifeCycleOwner 处于存活状态时,观察者将会被通知到封装数据的变动。当 lifeCycleOwner 的状态时 STARTED或者RESUMED时,它会被认为时存活状态。 通过observerForever(observer)添加进去的观察者,会被认为总是处于存活状态,这样它就会永远被通知数据的变动。对于这样的观察者,我们应该手动调用 removeObserver(observer)
连同Lifecycle被添加进去的观察者,如果相关的lifecycle转移到DESTORY状态时,将会被自动移除。这对于Activity/Fragment将会十分有用,他们可以安全地观察LiveData,并且不用担心泄露。
另外,LiveData拥有 onActive()和onInactive()方法来使得当存活的观察者数量在0和1之间变化时被通知到。这将允许LiveData在它没有任何存活观察者的时候释放重量级资源。
这个类被设计用来持有ViewModel的个别数据字段,但是它也可以被用来实现一个application中多个module之间的解耦的共享数据。
关键词重点:
- LiveData 自身是被观察者,创建它的时候可以指定observer以及lifeCycleOwner ,前者控制数据变化之后的处置,后者控制数据的变化是否需要通知
- lifeCycleOwner 也就是我们通常的Activity/Fragment,当他们处于STARTED和RESUMED状态时,观察者observer可以收到通知,如果是处于其他状态,则不会收到通知。
- 当lifeCycleOwner 变为DESTORY状态时,观察者observer将会被自动移除。
- 一个LiveData可以拥有多个观察者,它提供了onActive和onInactive的方法来绑定和释放重量级资源,避免资源浪费
- LiveData是谷歌提供给我们的一种多个模块之间不耦合的数据共享的方式
之所以选择用LiveData来实现MVVM,是因为 LiveData的代码足够纯粹,纯java/kotlin代码,不再像databinding那样黏乎乎,并且 在ViewModel抽象类的官方注释上,也推荐了LiveData的标准写法,写法足够简单实用。
核心功能
LiveData,其实他本身就是数据,一个可以被观察的活着的数据。它的唯一职责,就是提供观察接口给View,让View可以感知它的变化。
基本用法
LiveData作为给View层暴露ViewModel的一种方式,它一般要配合ViewModel来组成MVVM架构。
但是它也可以单独使用,下面的案例用纯粹的方式(不涉及到MVVM)来展示LiveData的用法:
// javabean
class User(var name: String, var sex: String) {
override fun toString(): String {
return "[name:$name,sex:$sex]"
}
}
// Livedata的管理类,虽然名字也叫xxModel,但是和ViewModel没有任何关系
class UserModel {
val userLiveData = MutableLiveData<User>() // LiveData除了可以发送数据之外,还可以缓存数据(参见setValue getValue)
private var seri = 0
init {
userLiveData.postValue(User("hank$seri", "male"))
}
fun loadUser() {
userLiveData.postValue(User("hank${++seri}", "male"))//post一次之后便会缓存起来
}
}
// View层Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val user = UserModel()
// 让界面元素通过观察者来感知数据的变化
user.userLiveData.observe(this, Observer<User> {
Log.d("hanTag", "MainActivity:侦测到User发生变化$it")
textView.text = it.toString()
})
// 通过textView的点击事件来主动触发数据的变化
textView.setOnClickListener {
Log.d("hanTag", "MainActivity:主动触发User的变化,可能是触发网络请求")
user.loadUser()
}
}
}
以上代码仅作示例。
以上代码有3个部分,一个javaBean,一个定义LiveData的管理类UserModel(虽然名字是xxModel,但是和ViewModel没有任何关系),还有一个是View层Activity.
运行效果:
核心原理
LiveData的核心功能,是把数据的变化通知给观察者,也就是这行代码
val userLiveData = MutableLiveData<User>()
userLiveData.postValue()// 入口代码
那么进入源码:
它实际上调用的是父类的postValue()
这段注释的意思大概是:
发送一个任务给主线程去设置给定的value。如果你在主线程中执行了如下代码:
liveData.postValue("a")
liveData.setValue("b")
这个value值 b 将会被首先设置,并且随后主线程将会用a覆盖它。
如果你在主线程执行post任务之前多次调用这个方法,那么只有最后一个value才会被分发。
接着往下看,方法内容:
这里进行了一系列判定,规避了无需执行任务的情况.
ArchTaskExecutor是一个单例类,用来在主线程中执行任务,细节无需关心。
来看看 mPostValueRunnable
这个runnable,其实就做了两件事,1,传递刚刚更新的mPendingData给newValue,然后还原mPendingData。2,将newValue值继续往下传输。
看setValue做了什么
发现了重点,这个函数的注释上说明,如果存在存活状态的观察者,将会把这个value值分发给他们。
继续观察considerNotify()方法:
可以看到,一个observer本身就有是否存活的状态值 mActive. 如果判定存活,就继续往下走程序,
这里我得到几个关键信息:
观察者本身就有mActive属性
为了防止观察者的mActive状态在通过了第一层判定之后突然改变(因为观察者的状态不可预知),又增加了第二层判定,如果这次判定发现观察者不再存活,也不通知
观察者存在一个mLastVersion属性,影响是否会被通知到(这个有用,下一节LiveDataBus会讲),并且在正式通知观察者之后,mLastVersion会更新
-
最终执行的是观察者的onChange方法,也就是我们在示例代码中所写的
Observer<User>
user.userLiveData.observe(this, Observer<User> { Log.d("hanTag", "MainActivity:侦测到User发生变化$it") textView.text = it.toString() })
接下来的重点就转移到了ObserverWrapper这个类,它是 LiveData的内部抽象类:
这个类,在我们去注册观察者的时候其实就用到了:
那它作为一个观察者,他自己的存活状态mActive是由什么决定的呢?
是ObserverWrapper自己的 activeStateChanged(). 这个函数的调用有4处,但是经过Debug追踪,最终锁定一处:
在LifecycleBoundObserver类的onStateChange函数中,这里已经说的很明白了,上面的代码解读一下:
- mOwner 是 Activity或者Fragment,他们都是LifeCycleOwner接口的实现者
- 判定观察者是否存活的依据,是 mOwner 是否处于至少START状态,但是排除DESTORYED状态
- 如果mOwner的状态被判定为大于START 小于 DESTROYED, 那么观察者会被认定为存活,mActive被设置为true
OK.到此为止,作结论:
总结
LiveData之所以可以仅在观察者存活的时候通知到多个观察者,是因为:
- 借助了LifeCycle,使用它来判定lifeCycleOwner的状态值,大于START小于DESTORY,则判定观察者是mActive是true, 反之 false
- 当LiveData数据发生变化时,轮询所有观察者,逐个判断观察者状态,发现mActive是true,则通知,反之,则不通知
LiveDataBus
LiveDataBus基于LiveData, 是一种在application全局范围内的事件总线的新方案,当然这个并不是谷歌直接给的,在此之前,可能存在EventBus,RxBus这种写法,但是他们都有内存泄漏的问题。而使用基于LiveData的LiveDataBus,将不会有类似的问题。
必须提到的是,原始的MutableLiveData存在多次通知的问题,解决方式也不止一种,后文再细讲。
核心功能
负责Activty/Fragment之间,多个Activity之间,以及Fragment与Activity之间的数据通信。
基本用法
由于它不是谷歌提供的,是开发者根据需求创造的,所以这里我给出参考源码,实际开发中每个人的写法都可能不同:
package com.zhou.baselib.bus
import android.util.Log
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import java.util.concurrent.atomic.AtomicBoolean
class LiveDataBus {
/**
* 只有当前存活的owner才能收到消息, 即,尚未创建成功的owner将不会收到消息 ,
* 即使它进行了监听
*/
private val mapOfAliveOwner: HashMap<String, AliveOwnerMutableLiveData<Any?>> = HashMap()
/**
* 全部owner都能收到消息,就算将要跳转的Activity并没创建成功的情况
*/
private val mapOfAllOwner: HashMap<String, MutableLiveData<Any?>> = HashMap()
/**
*
*/
private val mapOfSingleEventOwner: HashMap<String, SingleLiveData<Any?>> = HashMap()
// 内部类的单例写法,静态成员天生就是线程安全的
class SingleHolder {
companion object {
val DATA_BUS = LiveDataBus()
}
}
companion object {
// 提供给外界一个get方法来获取单例对象
fun get(): LiveDataBus {
return SingleHolder.DATA_BUS
}
}
/**
* 获取消息通道, 仅支持当前存活的 lifeCycleOwner
*
* @key 消息通道的key
*/
fun getAliveOwnerChannel(key: String): MutableLiveData<Any?> {
if (!mapOfAliveOwner.containsKey(key)) {
mapOfAliveOwner[key] = AliveOwnerMutableLiveData()
}
return mapOfAliveOwner[key]!!
}
/**
* 获取默认消息通道, 支持所有lifeCycleOwner,包括目前还没创建成功的
*/
fun getDefaultChannel(key: String): MutableLiveData<Any?> {
if (!mapOfAllOwner.containsKey(key)) {
mapOfAllOwner[key] = MutableLiveData()
}
return mapOfAllOwner[key]!!
}
fun getSingleEventChannel(key: String): SingleLiveData<Any?> {
if (!mapOfSingleEventOwner.containsKey(key)) {
mapOfSingleEventOwner[key] = SingleLiveData()
}
return mapOfSingleEventOwner[key]!!
}
}
/**
* 只有当前存活的lifeCycleOwner才会收到 消息, 重写它的observer的mLastVersion
*/
class AliveOwnerMutableLiveData<T> : MutableLiveData<T>() {
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner, observer)
hook(observer)
}
private fun hook(observer: Observer<in T>) {
val classLiveData: Class<LiveData<*>> = LiveData::class.java
val fieldObservers = classLiveData.getDeclaredField("mObservers")
fieldObservers.isAccessible = true
val mObservers = fieldObservers[this]
val classObservers: Class<*> = mObservers.javaClass
val methodGet = classObservers.getDeclaredMethod("get", Any::class.java)
methodGet.isAccessible = true
val objectWrapperEntry = methodGet.invoke(mObservers, observer)
val objectWrapper =
(objectWrapperEntry as Map.Entry<*, *>).value!!
val classObserverWrapper: Class<*>? = objectWrapper.javaClass.superclass
val fieldLastVersion =
classObserverWrapper!!.getDeclaredField("mLastVersion")
fieldLastVersion.isAccessible = true
val fieldVersion = classLiveData.getDeclaredField("mVersion")
fieldVersion.isAccessible = true
val objectVersion = fieldVersion[this]
fieldLastVersion[objectWrapper] = objectVersion
}
}
/**
* 如果希望创建一个消息通道,只允许通知一次,那就使用SingleLiveEvent
*
* @param <T> the data type
*/
class SingleLiveData<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(true)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.d(
"SingleLiveEvent",
"Multiple observers registered but only one will be notified of changes."
)
}
// Observe the internal MutableLiveData
super.observe(owner, Observer { t ->
if (mPending.compareAndSet(true, false)) {// 获取bool值,并且在获取之后将它的值设置成false
observer.onChanged(t)
}
})
}
}
以Activity之间的通信为例:
-
使用情形1,Activity之间的跳转,从MainActivity跳转到Main2Activity,要进行通信
// 第一个Activity class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) btn.setOnClickListener { startActivity(Intent(this, Main2Activity::class.java)) // 获取通道,发送消息,通道的key是MainActivity LiveDataBus.get().getDefaultChannel("MainActivity2").postValue("发送给第二个Activity的消息") } } } // 第二个Activity class Main2Activity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main2) // 监听事件通道 LiveDataBus.get().getDefaultChannel("Main2Activity").observe(this, Observer { Log.d("MainActivityTag", "$it") }) } }
运行程序,从MainActivity跳转到Main2Activity时,会看到日志:
Main2Activity收到消息: 发送给第二个Activity的消息
消息发送成功,这里的postValue()的参数类型,可以是任意对象,或者基础类型。
-
使用情形2:以上的情况是从一个Activity,跳转到另一个Activity2,其实在发送消息之时,目标Activity2并没有创建成功,它也能在Activity2创建成功之后收到消息(下一节分析原理 )。但是也存在另一种情况,如果我们只希望目标Activity/Fragment存在的时候,才收到消息(而不是等他创建成功)。可以这么用。
// 第一个Activity class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) LiveDataBus.get().getAliveOwnerChannel("MainActivity").observe(this, Observer { Log.d("MainActivityTag", "MainActivity 收到消息: $it") }) btn.setOnClickListener { startActivity(Intent(this, Main2Activity::class.java)) LiveDataBus.get().getAliveOwnerChannel("Main2Activity").postValue("发送给第二个Activity的消息") } } } // 第二个Activity class Main2Activity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main2) LiveDataBus.get().getAliveOwnerChannel("Main2Activity").observe(this, Observer { Log.d("MainActivityTag", "Main2Activity收到消息: $it") }) btn.setOnClickListener { finish() LiveDataBus.get().getAliveOwnerChannel("MainActivity").postValue("回馈给Activity1的消息") } } }
当从MainActivty点按钮跳到Main2Activity时,Main2Activity并收不到消息。
这是因为,消息发送时,Main2Activity对象并不存在(如果Main2Activity已经处于栈内,并且它是栈内唯一的启动模式 SingleInstance,也能收到,亲测 )。
而从Main2Activity点按钮跳回MainActivity时, MainActivity能收到消息。
Fragment的情形也是一样,篇幅有限,这里就不做示例了...
-
使用情形3
有的时候,当数据变化时,只需要通知一次View层即可。此时单独使用
SingleLiveData
(参照LiveData基本用法),也可以使用LiveDataBus的getSingleEventChannel("xxx")取得 单次消息通道即可。//注册监听 LiveDataBus.get().getSingleEventChannel("NoticeTipsFragment").observe(this, Observer { tvMsg.text = it.toString() }) // 发送消息 var seriCode = 1 tvMsg.setOnClickListener {//点击事件 LiveDataBus.get().getSingleEventChannel("NoticeTipsFragment").postValue("哈哈哈${seriCode++}") }
就算点击多次按钮,发送了多次事件,监听器也只会收到一次通知。
核心原理
使用上述LiveDataBus类,只需要做两件事,
其一:获得消息通道 LiveDataBus.get().getXXXChannel("xxxx") 然后 observer(this,Observer{...})进行监听注册。
其二:获得消息通道 LiveDataBus.get().getXXXChannel("xxxx") ** 然后 postValue(xxx) 发送消息
上面的LiveDataBus类提供了3种消息通道,
一个是不支持粘性消息的getAliveOwnerChannel,内部使用的是hook之后的自定义AliveOwnerMutableLiveData对象来当作消息通道.
一个是支持粘性消息的getDefaultChannel,使用的是纯天然的MutableLiveData。
还有一种是支持单次消息通知的 SingleEventLiveData, 也是重写了observer方法, 使用AtomicBoolean 的特性(compareAndSet(true,false)获取当前值,并且使用之后更改为false)完成单次通知。
LiveDataBus本身的设计,就是基于 订阅,发布者模式,和Rxjava如出一辙,但是由于利用到了LiveData,LifeCycle,它纯天然就带有生命周期感知机制,无需我们操心去做更多防止内存泄漏的工作。但是,LiveDataBus与RxJava有一个共同之处,那就是支持粘性消息,即使 订阅者对象尚未创建,待到创建成功之后,也能收到。
进入源码, 本次探索源码,分析的目标是:
为何使用默认的MutableLiveData时,它支持粘性消息呢?
从发布者入手:
LiveDataBus.get().getDefaultChannel("Main2Activity").postValue("发送给第二个Activity的消息")
postValue之后,追踪到LiveData类的mPostValueRunnable...
继续往下,进入到setValue:
observer.mActive值是由 Activity/Fragment的状态决定的(前面LiveData的核心原理已经说明),
如果我想阻止消息的通知,想在这里执行return不现实。所以唯一的解决方案就锁定在 下图中的observer.mLastVersion >= mVersion 的判断上。只要我们能够让判断成立,那么这里就会return。onChange就不会执行,消息也就不会具备粘性。
一向严谨的谷歌工程师居然对这里的 mVersion变量加注释,但是根据代码逻辑可以判定:
只有在消息的版本mVersion 大于 订阅者的版本mLastVersion 时,才执行消息 通知.
这个也好理解,毕竟不能让订阅者收到过时的消息。
LiveData类的mVersion属性,唯一一处和当前情形有关的变动,就是:
而它的初始值是 START_VERSION -1
observer.mLastVersion 的初始值也是 START_VERSION -1
那么就很有意思了。 LiveData的mVersion, 可以理解为消息通道的版本号。而 observer.mLastVersion可以理解为 消息订阅者的版本号。前者,会随着每一次的postValue->调用到 setValue,从而 mVersion++, 那后者呢?
在消息确认要发送之后,把消息的版本号赋值给 订阅者的版本号,防止重复发送。
OK,问题基本确定了。先总结一波:
LiveDataBus基于 发布/订阅者 模型,发布者是 MutableLiveData,订阅者是Observer,两者都存在一个版本号,并且初始值都是-1。
当发布者发布消息的时候,它自身的版本号+1。
此时,判定 发布者的版本号是不是大于订阅者版本号,如果大于,那么消息就是有效消息,进行通知。否则,就认定为过时消息,不通知订阅者。
所以,要解决粘性消息,就只需要针对订阅者的版本号进行hook。hook的思路是:
在订阅者Activity获取了消息通道,然后注册监听的时候,把订阅者Observer自身的版本号设置得和 消息发布者的版本号一样,让 if(observer.mLastVersion>=mVersion){return;} 语句满足判定,执行return.
于是就看到了本节开头LiveDataBus代码中的,hook方法, 在注册监听的时候,把LiveData消息通道的mVersion赋值给observer的mLastVersion:
class AliveOwnerMutableLiveData<T> : MutableLiveData<T>() {
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner, observer)
hook(observer)
}
private fun hook(observer: Observer<in T>) {
// 获得 当前 ObserverWrapper
val classLiveData: Class<LiveData<*>> = LiveData::class.java
val fieldObservers = classLiveData.getDeclaredField("mObservers")
fieldObservers.isAccessible = true
val mObservers = fieldObservers[this]
val classObservers: Class<*> = mObservers.javaClass // 拿到了 map 对象
//
val methodGet = classObservers.getDeclaredMethod("get", Any::class.java)
methodGet.isAccessible = true
val objectWrapperEntry = methodGet.invoke(mObservers, observer)
val objectWrapper =
(objectWrapperEntry as Map.Entry<*, *>).value!!// 拿到ObserverWrapper
val classObserverWrapper: Class<*>? = objectWrapper.javaClass.superclass //
val fieldLastVersion =
classObserverWrapper!!.getDeclaredField("mLastVersion")// 取得ObserverWrapper 的 mLastVersion属性
fieldLastVersion.isAccessible = true
val fieldVersion = classLiveData.getDeclaredField("mVersion")// 拿到 LiveData的mVersion属性
fieldVersion.isAccessible = true
val objectVersion = fieldVersion[this]
fieldLastVersion[objectWrapper] = objectVersion
}
}
而由于有时候粘性消息也同样有作用,比如Activity跳转的数据传递。所以,LiveDataBus中我保留了原有的MutableLiveData通道map. 要不要使用粘性消息,视情况而定。
总结
LiveDataBus作为RxJava或者EventBus的替代品,在防止内存泄漏方面确实给开发者省了不少心,而且 View层内部的数据通信,也完全可以用LiveDataBus来替代传统的 全局变量方式,接口方式,Bundle封装方式等,写法上更加简洁一致,唯一需要管理的可能就是 消息通道的key值,如果用一个统一的类来管理,会更加严谨。
案例实操
Demo地址:https://github.com/18598925736/MvvmStandartDemo
上述有关MVVM的几个重要组件,在MVVM模式开发中会经常用到。但是个人不会去采用DataBinding,读者如果有需求可以自己查阅其他资料。
一般框架的开发设计思路都是,
-
完成核心功能
MVVM开发框架的核心功能,就是分离 M模型层,V视图层 和 VM数据模型层
-
接口/基类抽取
对于MVVM三个层级,分别定义统一的抽象类或者接口,来约束该类的行为。
-
解决Bug,提升兼容性和使用体验
仅仅是做Demo,发现不了隐藏的一些问题,必须在实践中慢慢总结提升优化。
主要业务 : 一个简单的登录逻辑。从简单的案例入手,更容易感受到框架的本质。
完成核心功能
如上图所示,Demo中,存在明显的三层架构,
-
数据层M
只负责数据的获取
-
视图层V
只负责界面UI的调度
-
数据模型层VM
负责联络M和V层。VM层内的方法,由V层调用,方法内部,会执行M层的方法。
M-VM-M 三者之间的引用关系,现在是一条直线.V直接持有VM,VM直接持有M。
相比之前MVP,P和V层的对象之间实际上是存在互相引用的关系,只是用接口隔离了,为了不泄漏内存,还要在适当的时机断开引用。
但是,现在,内存泄漏的问题不用操心了。
详细代码如下:
V层代码, 略微使用接口约束:
interface LoginView {
fun getUserName(): String
fun getPassword(): String
fun showResult(res: String)
}
/**
* MVVM中View层依然是纯粹的视图层,不要涉及到任何业务逻辑,它只负责调用VM层的业务,
* 与MVP相比,P必须持有V的引用,然后驱动UI的更新,最后还要写释放引用的代码。
*
* 但是MVVM中,可以直接用 观察者回调来实现
*
*/
class LoginActivity : AppCompatActivity(), LoginView {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
// 获取ViewModel
val userModel = ViewModelProvider(this).get(LoginActivityViewModel::class.java)
// 点击事件触发,出发VM的业务逻辑
btnLogin.setOnClickListener {
showLoading() // 开始请求数据显示加载中
userModel.doLogin(getUserName(), getPassword())
}
// 定义数据回调逻辑
userModel.userLiveData.observe(this, Observer {
hideLoading() // 得到数据返回,隐藏加载中
showResult(it.toString())// 处理数据
})
}
//...省略N行
}
M层代码:
/**
* 约束业务数据的接口
*/
interface UserModelInterface {
fun login(userName: String, password: String, callback: HttpCallback<UserBean>)
}
/**
* 纯粹的M层, 只有数据获取的代码
*/
class UserModel : UserModelInterface {
override fun login(userName: String, password: String, callback: HttpCallback<UserBean>) {
HttpRequestManager.doLogin(userName, password, callback)
}
}
VM层的代码:
/**
* 业务逻辑转移到这里
*
* 原则上,一个界面(Activity或者Fragment)对应一个 UserModel
*
*/
class LoginActivityViewModel : ViewModel() {
val userLiveData: MutableLiveData<UserBean> by lazy {
MutableLiveData<UserBean>()
}
fun doLogin(userName: String, password: String) {
// 发送网络请求,并且执行回调
UserModel().login(userName, password, object : HttpCallback<UserBean> {
override fun onSuccess(result: UserBean?) {
userLiveData.postValue(result)
}
override fun onFailure(e: Exception?) {
userLiveData.postValue(UserBean())// 失败,则手动构建一个userBean
}
})
}
}
运行效果和MVP没有区别:
接口/基类抽取
设计一个框架,除了面向对象思想的封装继承和多态之外,还需要了解设计模式,注解技术,泛型约束,反射原理,有可能还需要了解一些 数据结构来追求高效。
提取基类,M和V层其实还是一样,需要BaseModel和BaseView来约束基本行为,就算是接口是空,也必须有,空接口可以作为类型标记。
BaseModel
interface BaseModel {
}
后续的Model层子接口以及实现类,都要以它为父类:
BaseView
// View层基类,
interface BaseView {
fun showLoading()// 展示加载中
fun hideLoading()// 隐藏加载中
}
所有View层类都是它的子类:
ViewModel不用提取
至于VM层,个人习惯,一般不用接口约束,因为它不再像MVP的P层一样持有View的引用。提取不提取都问题不大。
BaseActivity/BaseFragment
MVP中,一个V,对应一个P,同样,MVVM中,一个V对应一个VM,所以,BaseActivity中,VM可以用泛型来进行约束。 抽离VM泛型之后,BaseActivity如下:
open abstract class BaseActivity<T : ViewModel> : AppCompatActivity() {
/**
* 布局ID
*/
abstract fun getLayoutId(): Int
private fun getViewModelClz(): Class<T> {
return analysisClassInfo(this)
}
fun getViewModel(): T {
return ViewModelProvider(this).get(getViewModelClz())
}
/**
* 获取对象的参数化类型,并且把第一个参数化类型的class对象返回出去
*
* @param obj
* @return
*/
private fun analysisClassInfo(obj: Any): Class<T> {
val type = obj.javaClass.genericSuperclass //
val param = (type as ParameterizedType?)!!.actualTypeArguments // 获取参数化类型
return param[0] as Class<T>
}
/**
* 界面元素初始化
*/
abstract fun init()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(getLayoutId())
init()
}
}
这里有个难点:
由于通过ViewModelProvider需要传入具体的ViewModel的Class对象,所以要从BaseActivity的泛型参数中将泛型的具体类型解析出来,也就是这里的 analysisClassInfo()方法 . 具体的,这里不是重点,详情就略过了。
在使用了这个BaseActivity之后,LoginActivity就变成了如下模样:
BaseFragment也是类似:
open abstract class BaseFragment<T : ViewModel> : Fragment() {
/**
* 布局ID
*/
abstract fun getLayoutId(): Int
private fun getViewModelClz(): Class<T> {
return analysisClassInfo(this)
}
fun getViewModel(): T {
return ViewModelProvider(this).get(getViewModelClz())
}
/**
* 获取对象的参数化类型,并且把第一个参数化类型的class对象返回出去
*
* @param obj
* @return
*/
private fun analysisClassInfo(obj: Any): Class<T> {
val type = obj.javaClass.genericSuperclass //
val param = (type as ParameterizedType?)!!.actualTypeArguments // 获取参数化类型
return param[0] as Class<T>
}
/**
* 界面元素初始化
*/
abstract fun init()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(getLayoutId(), container, false)
}
/**
* onViewCreated之后,才能用kotlin的viewId去操作view
*/
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
init()
}
}
使用BaseFragment也是一样:
基类统一管理
将基类移动到基础lib模块中,
解决Bug,提升兼容性和使用体验
Demo只是Demo,实际使用MVVM的过程中,还会遇到不可预知的问题。
LiveData成员属性膨胀
一般我们认定,一个Activity/Fragment对应一个ViewModel,但是一个ViewModel中可以认定多个数据源,也就是LiveData, 一旦业务代码积累过多,就可能会出现一个ViewModel中N个liveData成员属性 :
class LoginActivityViewModel : ViewModel() {
private val userLiveData: MutableLiveData<UserBean> by lazy { MutableLiveData<UserBean>() }
private val noticeLiveData: SingleLiveData<String> by lazy { SingleLiveData<String>() }
// 业务积累
private val noticeLiveData: SingleLiveData2<String> by lazy { SingleLiveData<String>() }
private val noticeLiveData2: SingleLiveData3<String> by lazy { SingleLiveData<String>() }
private val noticeLiveData3: SingleLiveData4<String> by lazy { SingleLiveData<String>() }
private val noticeLiveData4: SingleLiveData5<String> by lazy { SingleLiveData<String>() }
private val noticeLiveData5: SingleLiveData6<String> by lazy { SingleLiveData<String>() }
private val noticeLiveData6: SingleLiveData7<String> by lazy { SingleLiveData<String>() }
//.....
}
而且多个ViewModel还有可能公用同一个类型的 LiveData,比如有一个Fragment也在用 NoticeLiveData:
class NoticeFragmentViewModel : ViewModel() {
val noticeLiveData: SingleLiveData<String> by lazy { SingleLiveData<String>() }
}
那每个地方都去写一遍,并不是最理想的写法。
解决方案:提炼出LiveDataHolder类,让每一个ViewModel子类都拥有一个自己的LiveDataHolder对象,从holder中去获取LiveData, 去执行数据的发送和监听.
import androidx.lifecycle.MutableLiveData
// 三种LiveData的类别
enum class EventType {
SINGLE,// 单次通知
ALIVE,// 只通知存活的lifeCycleOwner
DEFAULT// 默认,粘性消息
}
/**
* 这个函数是为了简化一个ViewModel中存在N个LiveData定义的情况,其实只需要一个类,去获取就行了
*
* 每个ViewModel应该有自己的LiveDataHolder,应该是每一个ViewModel都有一个LiveDataHolder, 用来获取LiveData
*/
class LiveDataHolder {
companion object {
fun get(): LiveDataHolder {
return LiveDataHolder()
}
}
private val map: HashMap<Class<out Any?>, MutableList<MutableLiveData<out Any?>>> = HashMap()
fun <T> getLiveData(key: Class<T>): MutableLiveData<T> {
return getLiveData(key, EventType.DEFAULT)
}
/**
*
* 获得一个指定的LiveData
*
* @param key 指定返回值类型
* @param eventType 消息通道的类别
* @see EventType
*/
fun <T> getLiveData(key: Class<T>, eventType: EventType): MutableLiveData<T> {
val liveDataClz: Class<MutableLiveData<T>> = when (eventType) {
EventType.SINGLE -> {
SingleLiveData<T>().javaClass
}
EventType.ALIVE -> {
AliveOwnerMutableLiveData<T>().javaClass
}
else -> {
MutableLiveData<T>().javaClass
}
}
if (map[key] == null) {
map[key] = ArrayList()
}
val currentList = map[key]!!
for (a in currentList) {
if (liveDataClz.isInstance(a)) {
return a as MutableLiveData<T>
}
}
val newLiveData = liveDataClz.getConstructor().newInstance()
currentList.add(newLiveData)
return newLiveData
}
}
为了防止在每一个ViewModel子类中都去定义一次LiveDataHolder,我把它提炼到 ViewModel的基类中去:
// ViewModel基类
abstract class BaseViewModel : ViewModel() {
// 一个ViewModel可以存在多个LiveData,所以使用LiveDataHolder管理所有的LiveData
val liveDataHolder = LiveDataHolder.get()
}
经过此次优化,所有ViewModel子类中不再需要去存放LiveData成员属性,要使用的时候直接如下这样写
class LoginActivityViewModel : BaseViewModel() {
/**
* 触发业务
*/
fun getMsg() {
val noticeLiveData =
liveDataHolder.getLiveData(String::class.java)
noticeLiveData.postValue(NoticeModel().getNotice())
}
/**
* 监听业务
*
* 为了不向外界暴露LiveData成员,提供一个注册监听的函数
*/
fun observerGetMsg(lifecycleOwner: LifecycleOwner, observer: Observer<String>) {
val noticeLiveData =
liveDataHolder.getLiveData(String::class.java)
noticeLiveData.observe(lifecycleOwner, observer)
}
/**
* 触发登录业务
*/
fun doLogin(userName: String, password: String) {
// 发送网络请求,并且执行回调
UserModel().login(userName, password, object : HttpCallback<UserBean> {
override fun onSuccess(result: UserBean?) {
liveDataHolder.getLiveData(UserBean::class.java, EventType.ALIVE).postValue(result)
}
override fun onFailure(e: Exception?) {
liveDataHolder.getLiveData(UserBean::class.java, EventType.ALIVE)
.postValue(UserBean())
}
})
}
/**
* 监听登录业务
*/
fun observerDoLogin(lifecycleOwner: LifecycleOwner, observer: Observer<UserBean>) {
liveDataHolder.getLiveData(UserBean::class.java, EventType.ALIVE)
.observe(lifecycleOwner, observer)
}
}
多个Fragment公用所在Activity的ViewModel
同一个Activity上,可能多个Fragment都会共用Activity的viewModel来达到数据同步的目的。如果按照常规方式,我们只能进行类型强转,然后调用 activity的viewModel的方法。
但是这种写法耦合性很高,不方便后期维护。
解决方案:使用泛型,改变一下BaseFragment的泛型约束,改变之后如下:
之后,使用BaseFragment的地方就应该写成如下这样:
如果存在另外一个相同的Fragment,也在使用父Activity的ViewModel,他们就能够达成数据同步,如下图这样:
效果:
MVVM优缺点
优点
-
屏幕旋转时,界面数据不会重建
在ViewModel出现之前,屏幕的旋转,会导致Activity/Fragment的完全重建,所牵涉到的数据也会重新创建,如果页面复杂,可能会导致卡顿掉帧等问题。但是使用ViewModel作为数据模型层之后,屏幕旋转并不会重新创建数据,而是直接绑定已有ViewModel.
-
MVP的接口膨胀问题解决
MVVM是由MVP进化而来,进化的过程只做了一件事,数据驱动。当然,在ViewModel结合DataBinding时代,也就是LiveData出世之前,就已经形成了由数据驱动UI的机制,甚至可以进行反向驱动,由UI变动来驱动数据变动。在LiveData,LiveCycle出世之后,由数据驱动UI这一点仍然保持,再也不用写MVP那样无限膨胀的P接口了。
-
杜绝内存泄漏
之前MVP的由P层来驱动UI,以及调用业务,P层必须持有Activity/Fragment的引用,可能会导致内存泄露,开发者必须在适当的时机释放引用(比如本文使用LifeCycle注解接口,或者手段)。但是MVVM架构中,DataBinding和LiveData都集成了 lifeCycleOwner监听机制,如果耗时任务执行完毕之时,Activity/Fragment已经不处于STARTED状态,并不会去驱动UI。
缺点
-
DataBinding时代混合代码难看,难以调试
可能曾经一段时间DataBinding兴起时,数据的单向双向绑定,确实让开发体验提升了不少。但是DataBinding所引起的负面效果,xml中的粘性代码,AndroidStudio编译偶尔会由于DataBinding出现故障,程序出错之后调试也更加困难。但是有一说一,使用LiveData之后,这个问题可以不用管,因为我根本就不会再去考虑DataBinding。
-
学习成本较高
就本文所提及的MVVM可能用到的4个组件ViewModel,LiveData,DataBinding,LiveDataBus 而言,要掌握完整,MVVM开发才会畅通无阻,而且后续还可能会存在版本变动,组件升级等。对于某些团队而言,可能不会去花费这个时间去持续学习。而MVP模式,仅仅了解LifeCycle 以及 Contract(统筹MVP三层的思想),就能解决开发中的大部分问题, 在项目管理上维护成本也会更低,对开发团队技术能力要求也更低。
结语
Demo地址:https://github.com/18598925736/MvvmStandartDemo
本文讲述了MVVM开发中可能用到的4个组件以及各自的核心原理,基本用法,并提供了基本的架构思路。时间有限,遇到真正的大型项目,还是要继续提炼基类,使用泛型,注解,反射,甚至数据结构,设计模式,去搭建一个完整项目架构。但是本文可以作为入门MVVM的完整攻略。
本人上一篇 漫谈MVP,结合 本文漫谈MVVM, 目前主流的开发机构模式基础知识应该完整了。
~开发工程师活到老学到老,共勉!