前言
这里把ViewModel和LiveData放在一起来讲是因为这两者关联性比较强,当然LiveData也可以跟很多其他组件使用,就比如说我们前面介绍的Room组件。而ViewModel和LiveData两者又和MVVM框架又很大的相关性,这里我们也要复习一下MVVM框架。
MVVM框架介绍
因为ViewModel和LiveData是MVVM框架中的重要角色,所以我们先来复习一下MVVM框架。
MVVM框架的定义
MVVM框架是MVC框架的加强版,和MVC相比,MVVM多了一个VM
,也就是ViewModel
。MVVM框架主要解决的是MVC框架Controller
中数据和业务逻辑耦合的问题,所以ViewModel
其实是Model
和Controller
中间交互的媒介。
MVVM架构结构图:
从结构图我们可以看出MVVM的主要三点是:
- View: 对应
Activity
和XML
,负责View
的绘制以及使用交互; - Model: 数据实体模型;
- ViewModel: 负责完成
View
与Model
间的交互,负责业务逻辑。
MVVM框架的优缺点
优点:
- 低耦合度:数据和业务逻辑都在对应的
ViewModel
中,ViewModel
只关注数据和业务逻辑,不用和UI耦合; - 可复用性高:一个
ViewModel
可以复用到多个View
中,对于UI的单元测试会更方便; - 开发解耦:业务逻辑和UI界面开发可以同时分开给不同的开发者做,两者并不会有太大的耦合。
缺点:
- 代码上肯定会比
MVC
的更多,因为在View
和Model
之间多了一个交互的桥梁,但是会比MVP
简洁,用过MVP
都知道那接口多得不要不要的; - 如果使用类似
DataBinding
来进行数据和View
的双向绑定会造成调试比较麻烦,View
的状态根据数据改变较多,ViewModel
的构建和维护的成本都会比较高。
MVVM框架使用选择总结
由上面分析可见,MVVM
框架并不能带来比MVC
和MVP
更少的代码量,但UI和业务逻辑的分离得更好,开发逻辑上会更清晰,对于在多页面且交互逻辑相对复杂的应用开发时会能更好让团队协作开发。
LiveData
简介
LiveData是一种可观察的数据存储器类。与常规的可观察类不同,LiveData具有生命周期感知能力,意指它遵循其他应用组件(如Activity
、Fragment
或Service
的生命周期。这种感知能力可确保LiveData仅更新处于活跃生命周期状态的应用组件观察者。
如果观察者(由Observer
类表示)的生命周期处于STARTED
或RESUMED
状态,则LiveData会认为该观察者处于活跃状态。LiveData只会将更新通知给活跃的观察者。为观察LiveData对象而注册的非活跃观察者不会收到更改通知。
LiveData和ViewModel的关系
LiveData的作用就是让数据具有生命周期感知能力,并对数据的变化进行实时监听,在Activity/Fragment
等处在活跃状态时,自动把数据回调给观察者进行数据更新。而 ViewModel 的作用则是可以对LiveData进行正确的保存和恢复。所以LiveData和ViewModel在整个MVVM架构中是担当一个数据驱动的职责。
使用LiveData观察数据的优势
- 数据变更时自动通知UI更新;
- 可避免内存泄漏,这是因为观察者都绑定到
Lifecycle
上; - 避免
Activity/Fragment
因销毁而操作的崩溃,因为处于不活跃的界面不会接收到任何LiveData事件; - 不再需要手动处理生命周期,因为LiveData观察数据的操作是可以感知相关的生命周期状态变化的;
- 数据总是会处于最新的状态,
Lifecycle
主要从非活跃状态到活跃状态变化就会更新数据; - 当系统配置变化时会对数据进行保存和恢复,及UI状态的恢复,如屏幕旋转后的恢复;
- 资源共享,通过单例来控制LiveData,这样可以把当前LiveData的事件发给所有观察者。
LiveData的使用
LiveData最佳的实践方法就是和ViewModel一起配合使用,因为它们两者的功能是具有互补性的,但是LiveData也可以单独使用,下面LiveData使用的介绍我们先用单独使用来说明先,后面学习ViewModel再介绍两者结合使用的案例。
依赖LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.0-beta01"
LiveData使用三步骤
- 创建
LiveData
实例以存储某种类型的数据,可以是基本类型的String
,也可以是自定义的数据实体UserModel
,一般情况下这些操作都在ViewModel
中完成。 - 创建可定义
onChanged()
方法的Observer
对象,该方法可以控制当LiveData
对象存储的数据更改时会发生什么。通常情况下,您可以在界面控制器(如Activity
或Fragment
)中创建Observer
对象。 - 使用
observe()
方法将Observer
对象附加到LiveData
对象。observe()
方法会采用LifecycleOwner
对象。这样会使Observer
对象订阅LiveData
对象,以使其收到有关更改的通知。通常情况下,您可以在界面控制器(如Activity
或Fragment
)中附加Observer
对象。
创建一个数据实体:
data class UserModel(
var name: String,
var age: Int
)
创建一个LiveData存储UserModel
类型数据:
private var mutableLiveData = MutableLiveData<UserModel>()
模拟一个加载数据赋值的过程:
private fun loadUserInfo(){
rlLoading?.visibility = View.VISIBLE
Handler(Looper.getMainLooper()).postDelayed(Runnable {
val userModel: UserModel = if(count % 2 == 0){
UserModel("张三",24)
}else{
UserModel("李美美",24)
}
count++
mutableLiveData.postValue(userModel)
rlLoading?.visibility = View.GONE
},2000)
}
最后是通过LiveData的observe(...)
方法把this
传进去,因为AppCompatActivity
默认是实现了Lifecycle
的,这个这个this
就是lifecycleOwner
,那么就绑定了观察者啦:
mutableLiveData.observe(this, object :Observer<UserModel>{
override fun onChanged(it: UserModel?) {
it?.let {
tvName?.text = it.name
tvAge?.text = "${it.age} 岁"
}
}
})
通过button
点击调用:
btnLoad?.setOnClickListener {
loadUserInfo()
}
效果如下:
LiveData和Room结合使用
我们拿之前介绍Room组件的demo来改一下实现LiveData和Room结合使用,文章链接:juejin.cn/post/709022…
首先改一下接口WordDao
,把查询数据的方法返回改成LiveData类型:
//获取所有数据
@Query("SELECT * FROM word_table")
fun queryAll(): LiveData<MutableList<WordEntity>>
如果你要对一个数据这样观察也可以改成这样。然后在Activity
中的监听使用:
val liveDataWord = wordDao.queryAll()
liveDataWord.observe(this,object :Observer<MutableList<WordEntity>>{
override fun onChanged(t: MutableList<WordEntity>?) {
if (t != null) {
mList.clear()
mList.addAll(t)
}
wordAdapter.notifyDataSetChanged()
}
})
将协程与 LiveData 一起使用
当使用LiveData时进行异步任务是,我们可以使用liveData
构建器函数调用 suspend
函数,并将结果作为LiveData对象传送。这个liveData
的功能其实跟我们之前学习Kotlin时flow
很类似,但是flow
会优于liveData
,所以谷歌官方才会建议使用flow
代替liveData
,当然flow
是Kotlin语言特性,使用Java的时候就比较尴尬了,所以liveData
还是有很大的存在意义,而且它也满足一般项目的开发情况。
声明一个LiveData对象:
private var liveDataUser: LiveData<UserModel>? = null
调用:
liveDataUser = liveData {
delay(2000)
val userModel = UserModel("张三",23)
emit(userModel)
}
liveDataUser?.observe(this,object :Observer<UserModel>{
override fun onChanged(t: UserModel?) {
Log.e("liveDataUser",t.toString())
}
})
我们也可以写成这样:
liveData {
delay(2000)
val userModel = UserModel("张三",23)
emit(userModel)
}.observe(this,object :Observer<UserModel>{
override fun onChanged(t: UserModel?) {
Log.e("liveDataUser",t.toString())
}
})
扩展LiveData
如果观察者的生命周期处于STARTED
或 RESUMED
状态,则LiveData会认为该观察者处于活跃状态,我们需要在观察者的lifecycle
处于active
状态做处理时需要继承LiveData或MutableLiveData并覆写它的onActive()
和onInactive()
方法。
下面我们完善一下官方举的案例来说明一下LiveData扩展,案例介绍的是监听股票价格变化,在界面活跃的时候监听,不活跃的时候移除监听,减少不必要的开支。
LiveData包裹的数据实体:
data class StockInfo(
val price :String,
val gain :String
)
扩展的LiveData类中监听数据的接口:
interface StockInfoListener {
fun sendStockInfo(stockInfo: StockInfo)
}
数据管理类:StockManager
object StockManager {
private var stockInfoListener: StockInfoListener? = null
private var flag: Boolean = false
private var gain: Int = 0
fun stockInfoUpdate(stockInfoListener: StockInfoListener?) {
this.stockInfoListener = stockInfoListener
flag = true
updateStockInfo()
}
fun removeUpdates() {
flag = false
this.stockInfoListener = null
}
private fun updateStockInfo() {
Thread {
while (flag) {
Thread.sleep(2000)
gain++
stockInfoListener?.sendStockInfo(StockInfo("20.$gain", "$gain%"))
}
}.start()
}
}
这里提供添加监听的方法,添加监听后就更新数据,这里模拟的是每两秒更新一次,那对于真实开发环境就是请求后台的接口得到数据在发送回LiveData扩展类里面。
LiveData扩展类:
class StockLiveData: LiveData<StockInfo>() {
private val TAG = StockLiveData::class.java.simpleName
private val stockInfoListener = object : StockInfoListener {
override fun sendStockInfo(stockInfo: StockInfo) {
Log.i(TAG,stockInfo.toString())
postValue(stockInfo)
}
}
override fun onActive() {
super.onActive()
Log.i(TAG,"onActive")
StockManager.stockInfoUpdate(stockInfoListener)
}
override fun onInactive() {
super.onInactive()
StockManager.removeUpdates()
Log.i(TAG,"onInactive")
}
companion object{
private var instance: StockLiveData? = null
@MainThread
fun get(): StockLiveData?{
if(instance == null){
synchronized(StockLiveData::class.java){
if(instance == null){
instance = StockLiveData()
}
}
}
return instance
}
}
}
Activity
中对StockLiveData
的观察监听:
StockLiveData.get()?.observe(this, object : Observer<StockInfo> {
@SuppressLint("SetTextI18n")
override fun onChanged(t: StockInfo?) {
Log.i("MainActivity",t.toString())
tvPrice?.text = "限价:${t?.price}"
tvGain?.text = "涨幅:${t?.gain}"
}
})
效果图如下:
LiveData转换
当LiveData
在给观察者发送数据之前要对存储在其中的值进行更改,或者可能需要根据另一个实例的值返回不同的LiveData
实例时,我们需要使用Lifecycle
软件包提供的Transformations
类进行LiveData转换。
Transformations.map()
该方法主要是对存储在LiveData
对象中的值通过方法转换成其他类型,并将结果传播到下游,例如在实际开发中数据实体放回给一个用户的年龄age
是Int类型,所以我们不能直接使用TextView
设置它,并且我们还要加上数字年龄后面加个岁
字,如果有多个地方都要显示,那我们每次都要拼接一下,用Transformations.map()
对LiveData转换只需要一次即可。看下面代码:
private val userLiveData = MutableLiveData<UserModel>()
private fun getUserLiveData() {
Handler(Looper.getMainLooper()).postDelayed({
val userModel = UserModel("张三", 24)
userLiveData.postValue(userModel)
}, 2000)
}
声明一个UserModel
的LiveData
,并模拟获取UserLiveData
的方法。接着在Activity
中对LiveData监听:
val userNameLiveData: LiveData<String> = Transformations.map(userLiveData) {
"${it.age} 岁"
}
userNameLiveData.observe(this, Observer {
tvAge?.text = it
})
getUserLiveData()
也可以连着写:
Transformations.map(userLiveData) {
"${it.age} 岁"
}.observe(this, Observer {
tvAge?.text = it
})
Transformations.switchMap()
Transformations.switchMap()
方法和Transformations.map()
方法类似,不同的是Transformations.switchMap()
方法是必须返回一个LiveData
对象。在实际开发中的情况就是我们有时候既要维护用户userId
的LiveData,又要维护userId
对应UserModel
的LiveData对象,这个时候就可以通过Transformations.switchMap()
方法根据userId
的LiveData来获取到UserModel
。上面我们使用的UserModel
增加一个参数id
,我们通过这个id
去查找对应的User
。看下面代码:
data class UserModel(
var id: Int,
var name: String,
var age: Int
)
制造一些数据:
private val userList = ArrayList<UserModel>()
userList.add(UserModel(123,"张三", 23))
userList.add(UserModel(129,"李四", 25))
userList.add(UserModel(132,"王五", 27))
设置一个返回LiveData<UserModel>
的方法:
private fun getUserLiveData(id: Int): LiveData<UserModel>{
var userModel: UserModel? = null
for (e in userList){
if(id == e.id){
userModel = e
}
}
return MutableLiveData(userModel)
}
设置观察监听:
val idLiveData: LiveData<Int> = MutableLiveData(123)
Transformations.switchMap(idLiveData){
getUserLiveData(it)
}.observe(this,object: Observer<UserModel>{
override fun onChanged(t: UserModel?) {
Log.i("MainActivity",t.toString())
}
})
合并多个LiveData数据源
合并LiveData数据源使用的MediatorLiveData,它继承自mutableLiveData,可将多个LiveData数据源集合起来,可以达到一个组件监听多个LiveData数据变化的目的。在实际开发场景中,当我们要本地数据库或网络更新的LiveData
对象时就可以用MediatorLiveData对两个LiveData源进行合并。看下面代码:
val mutableLiveData1 = MutableLiveData<String>()
val mutableLiveData2 = MutableLiveData<String>()
val liveDataManger = MediatorLiveData<String>()
liveDataManger.addSource(mutableLiveData1) {
Log.i(TAG,"mutableLiveData1 -> $it")
liveDataManger.value = it
}
liveDataManger.addSource(mutableLiveData2) {
Log.i(TAG,"mutableLiveData2 -> $it")
liveDataManger.value = it
}
liveDataManger.observe(this) {
Log.i(TAG,"liveDataMerger -> $it")
}
mutableLiveData2.postValue("合并LiveData数据源")
打印结果: com.qisan.livedata I/MainActivity: mutableLiveData2 -> 合并LiveData数据源 com.qisan.livedata I/MainActivity: liveDataMerger -> 合并LiveData数据源
ViewModel
ViewModel是什么
ViewModel是一个注重生命周期的方式存储和管理界面相关数据的类,而且它和配合LiveData可以让数据在发生屏幕旋转等配置更改后继续留存。
Android框架可以管理界面控制器(如Activity Fragment)的生命周期。Android 框架可能会决定销毁或重新创建界面控制器,以响应完全不受您控制的某些用户操作或设备事件。如果系统销毁或重新创建界面控制器,则存储在其中的任何瞬态界面相关数据都会丢失。例如,应用可能会在它的某个 Activity 中包含用户列表。为配置更改重新创建 Activity 后,新 Activity 必须重新提取用户列表。对于简单的数据,Activity可以使用onSaveInstanceState()
方法从onCreate()
中的捆绑包恢复其数据,但此方法仅适合可以序列化再反序列化的少量数据,而不适合数量可能较大的数据,如用户列表或位图。
ViewModel和LiveData的结合使用
创建ViewModel:
class UserViewModel: ViewModel() {
private val userModel: MutableLiveData<UserModel> by lazy {
MutableLiveData<UserModel>().also {
it.postValue(loadUser())
}
}
fun getUser(): LiveData<UserModel>{
return userModel
}
fun loadUser(): UserModel{
return UserModel(123,"小明",23)
}
}
这里加载数据如果是在真实的开发环境中是一个异步任务,我们可以使用协程来实现。
Activity中调用:
val userVm: UserViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
userVm.getUser().observe(this){
tvName?.text = it.name
tvAge?.text = "${it.age} 岁"
}
ViewModel通过ViewModelProvider
来创建,当Activity
发生旋转之类的配置后进行重建时,onCreate()
方法会重新走一遍,但userVm
实例还是第一次创建的实例,这是因为在ViewModelProvider(this).get(UserViewModel::class.java)
中的get
方法中进行了缓存操作。当Activity
调用finish()
方法后,框架会调用ViewModel
对象的onCleared()
方法,以便对它进行数据的清理。ViewModel的生命周期如下图:
来自官方文档提供的结构图
我们通常在应用首次调用Activity
对象的onCreate()
方法时请求ViewModel
。应用可能会在activity
的整个生命周期内多次调用onCreate()
,如在旋转设备屏幕时。ViewModel
存在的时间范围是从首次请求ViewModel
直到activity
完成并销毁。
在Fragment之间共享数据
Fragment
之间的通信在以往我们的做法基本都是EventBus
用得比较多,而ViewModel
可以让我们的通信更简单。用法也很监听,一个发送数据,一个监听接受就行:
发送数据的Fragment:
class FragmentA : Fragment() {
val tv_a_b by bindView<TextView>(R.id.tv_a_b)
companion object {
fun newInstance() : FragmentA{
val bundle = Bundle()
val fragment = FragmentA()
fragment.arguments = bundle
return fragment
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_a,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val userVm: UserViewModel = ViewModelProvider(requireActivity()).get(UserViewModel::class.java)
tv_a_b?.setOnClickListener {
userVm.sendMsg(UserModel(123,"李美美",23))
}
}
}
接受数据的Fragment:
class FragmentB : Fragment() {
val tv_b_a by bindView<TextView>(R.id.tv_b_a)
companion object {
fun newInstance() : FragmentB{
val bundle = Bundle()
val fragment = FragmentB()
fragment.arguments = bundle
return fragment
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_b,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val userVm: UserViewModel = ViewModelProvider(requireActivity()).get(UserViewModel::class.java)
userVm.getUser().observe(viewLifecycleOwner, Observer {
tv_b_a?.text = "${it.name} ${it.age}岁"
})
}
}
这里要注意的是ViewModelProvider(requireActivity()).get(UserViewModel::class.java)
方法中要传requireActivity()
,别用this
或者activity
,这两个Fragment
都会检索包含它们的Activity
。这样,当这两个Fragment
各自获取ViewModelProvider
时,它们会收到相同的UserViewModel
实例,其范围限定为该Activity
。
这种数据通信方式还具有以下优势:
Activity
不需要执行任何操作,也不需要对此通信有任何了解。- 除了
UserViewModel
约定之外,Fragment
之间不需要相互了解。如果其中一个Fragment
消失,另一个Fragment
将继续照常工作。 - 每个
Fragment
都有自己的生命周期,而不受另一个Fragment
的生命周期的影响。如果一个Fragment
替换另一个Fragment
,界面将继续工作而没有任何问题。
将协程与ViewModel一起使用
在androidx.lifecycle
系列库中为每个ViewModel
定义了 ViewModelScope
,在ViewModel
中调用viewModelScope.launch {}
实现协程的异步任务,如果ViewModel
已清除,则在此范围内启动的协程都会自动取消。如果您具有仅在ViewModel
处于活动状态时才需要完成的工作,此时协程就非常适用。
class UserViewModel: ViewModel() {
init {
viewModelScope.launch {
// Coroutine that will be canceled when the ViewModel is cleared.
}
}
}
总结
LiveData && ViewModel的使用介绍就讲完了,后续文章会继续分享一下目前项目中使用ViewModel时如何加入Activity的封装。谷歌推出的LiveData和ViewModel是建议两者结合使用的,因为ViewModel可以在系统配置更改的情况下保存和恢复LiveData,而LiveData则可以在生命周期方法内对数据的变化进行监听。
两者的使用通过上面的介绍其实也并不复杂,但是对于代码的稳定性却是带来很大的提高,所以是时候把公司的项目都换成MVVM
啦。