ViewModel 是什么
ViewModel 类旨在以注重生命周期的方式 存储和管理 界面相关的数据。ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。
为什么ViewModel
类中的数据可在发生屏幕旋转等配置更改后继续留存?
因为ViewModel
的生命周期长于组件(Activity/Fragment)
的生命周期
下图是左侧给出了Activity
经历屏幕旋转而后结束的过程,所处的各种生命周期状态。而右侧是ViewModel
的生命周期。
从中可以看出ViewModel
的生命周期是长于组件(Activity)
的生命周期。
屏幕旋转后数据得以保存仅是ViewModel
的其中一个优势,生命周期比组件长的优势还可以:
- 减少资源浪费:避免因配置更改而重新创建对象
- 避免内存泄露:界面控制器经常需要做异步调用,而异步调用需要在一段时间后才返回结果。如果界面关闭之后数据在还没返回,且其中的一些引用还存在,那么内存泄露就不可避免了。通常我们会在页面关闭的时候手动清除引用。在生命周期中我们可以看到
ViewModel
还有一个方法onCleared()
,我们可在该方法手动清除引用。
谷歌在生命感知组件的最佳做法中,推荐我们使用ViewModel+LiveData
的组合。
既然如此,那我们先来了解何为LiveData
。
LiveData 是什么
LiveData
是一种 可观察的数据存储器类。与常规的可观察类不同,LiveData
具有生命周期感知能力,所以它能在不同生命周期处理不同的操作。
由于LiveData
是可观察的数据存储类且有生命周期感知能力,因此它有具备如下优势:
- 确保界面符合数据状态:LiveData 遵循观察者模式。当生命周期改变数据也会刷新
- 不会发生内存泄露:观察者会绑定到 Lifecycle 对象,并在其关联的生命周期遭到销毁后进行自我清理。
- 不会因 Activity 停止而导致崩溃:如果观察者的生命周期处于非活跃状态,则它不会接收任何 LiveData 事件。
- 不再需要手动处理生命周期:LiveData 将自动管理所有这些操作
- 数据始终保持最新状态:如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。
- 适当的配置更改:如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。
- 共享数据:数据存储在
ViewModel
中,需要相应资源的任何(Activity/Fragment)等
观察者只需观察LiveData
对象
ViewModel+LiveData 使用步骤
示例:
(1)实现倒计时功能
(2)ViewModel传参获取数据库的数据展示
-
Module -> build.gradle
的引入
版本依赖查看:https://developer.android.google.cn/jetpack/androidx/releases/lifecycle#declaring_dependencies
def lifecycle_version = "2.2.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// 还有一些可选项,可查看上面的版本依赖链接
- 创建
Viewmodel
(推荐创建BaseViewModel,之后再创建具体的ViewModel
继承BaseViewModel
)
/**
* ViewModel 基类
*/
open class BaseViewModel : ViewModel() {
override fun onCleared() {
super.onCleared()
Log.e("CommonViewModel","onCleared")
}
}
(示例1:倒计时功能)
class MineViewModel : BaseViewModel() {
private val periodTime = 1000L
private val mElapsedRealTime = MutableLiveData<Long>()
private var mInitialTime: Long = 0
// Create a LiveData with a Long
val countDownTime: MutableLiveData<Long> by lazy {
mElapsedRealTime
}
/**
* 开启倒计时
*/
fun getTime(){
mInitialTime = SystemClock.elapsedRealtime()
val timer = Timer()
val timeTask = object : TimerTask() {
override fun run() {
val newValue = (SystemClock.elapsedRealtime() - mInitialTime) / 1000
if (newValue < 10){
mElapsedRealTime.postValue(newValue)
}else{
timer.cancel()
}
}
}
timer.scheduleAtFixedRate(timeTask,periodTime,periodTime)
}
}
(示例2:ViewModel传参获取数据库的数据展示)
class HomeViewModel(context: Context): BaseViewModel() {
companion object {
private const val PAGE_SIZE = 15
private const val ENABLE_PLACEHOLDERS = false
}
val mContext = context
val dao = StudentDb.get(mContext).studentDao()
val allStudents = LivePagedListBuilder(dao.getAllStudent(), PagedList.Config.Builder()
.setPageSize(PAGE_SIZE) //配置分页加载的数量
.setEnablePlaceholders(ENABLE_PLACEHOLDERS) //配置是否启动PlaceHolders
.setInitialLoadSizeHint(PAGE_SIZE) //初始化加载的数量
.build()).build()
}
- 创建
Activity
(推荐Fragment
)中使用
(示例1:倒计时功能)
class MineFragment : BaseFragment(),MineContract.View {
private lateinit var viewModel: MineViewModel
override fun initData() {
// 创建实例
viewModel = ViewModelProviders.of(this).get(MineViewModel::class.java)
// 观察数据
viewModel.countDownTime.observe(this, Observer<Long> { aLong ->
//Update UI
tv_name.text = "time = " + aLong!!
})
}
override fun initView() {
// 订阅事件
lifecycle.addObserver(minePresenter)
// 获取数据
btn_get_data.setOnClickListener {
viewModel.getTime()
}
}
// 省略部分代码,具体可看下方源码链接
}
(示例2:ViewModel传参获取数据库的数据展示)
class HomeJetpackFragment : BaseFragment() {
private val TAG = HomeJetpackFragment::class.java.simpleName
private val viewModel by lazy(LazyThreadSafetyMode.NONE) {
ViewModelProviders.of(this, object : ViewModelProvider.Factory {
// 传递 context
override fun <T : ViewModel?> create(modelClass: Class<T>): T = HomeViewModel(
BaseApplication.context as Application
) as T
}).get(HomeViewModel::class.java)
}
override fun initView() {
val adapter = StudentAdapter()
val layoutManager = LinearLayoutManager(activity)
rv_list.layoutManager = layoutManager
rv_list.adapter = adapter
// 将数据的变化反映到UI上
viewModel.allStudents.observe(this, Observer {
adapter.submitList(it)
})
}
// 省略部分代码,具体可看下方源码链接
}
Tip:
initData
和 initView
执行顺序如下:
详细使用代码请参见:YGragon/FrameDemo
总结
使用ViewModel+LiveData
的模式,可以很好的在组件的不同生命周期处理数据。这是由于LiveData
是生命周期感知型类所带来的特性。ViewModel
中专注于处理数据,和UI
组件彻底解耦。
而且在使用AndroidStudio
创建Fragment
的时候提供了创建ViewModel
选项,极大的方便了开发。
参考