Jetpack之ViewModel
ViewModel
ViewModel 的一个重要作用就是可以帮助Activity分担一部分工作,它是专门用于存放与界面相关的数据的。也就是说,只要是界面上能看得到的数据,它的相关变量都应该存放在ViewModel中,而不是Activity中,这样可以在一定程度上减少Activity中的逻辑
ViewModel的生命周期和Activity不同,它可以保证在手机屏幕发生旋转的时候不会被重新创建,只有当Activity退出的时候才会跟着Activity一起销毁。因此,将与界面相关的变量存放在ViewModel当中,这样即使旋转手机屏幕,界面上显示的数据也不会丢失
viewmodel-lifecycle.png
ViewModel的基本用法
添加依赖
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
创建Activity
比较好的编程规范是给每一个Activity和Fragment都创建一个对应的ViewModel,因此这里我们就先创建MyActivity,之后再为其创建ViewModel。实现的功能是点击按钮对界面上的数据加1
activity_my.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".viewmodel_study.ViewModelActivity">
<Button
android:id="@+id/plus_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/num_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/plus_btn" />
</androidx.constraintlayout.widget.ConstraintLayout>
MyActivity
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my)
}
}
创建 ViewModel
新建一个对应的MyViewModel类,并让它继承自ViewModel,在ViewModel中加入一个counter变量用于计数
class MyViewModel : ViewModel() {
//加入一个counter变量用于计数
var counter = 0
}
实现计数器的逻辑
修改MyActivity
class MyActivity : AppCompatActivity() {
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my)
// 不可以直接去创建ViewModel的实例,一定要通过ViewModelProvider来获取ViewModel的实例
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
// 按钮点击监听
plus_btn.setOnClickListener {
// 使用viewModel修改数据
viewModel.counter++
refreshCounter()
}
refreshCounter()
}
/**
* 获取viewModel的数据刷新界面
*/
private fun refreshCounter() {
num_text.text = viewModel.counter.toString()
}
}
绝对不可以直接去创建ViewModel的实例,而是一定要通过ViewModelProvider来获取ViewModel的实例,因为ViewModel有其独立的生命周期,并且其生命周期要长于Activity。如果我们在onCreate()方法中直接去创建ViewModel的实例,那么每次onCreate()方法执行的时候,ViewModel都会创建一个新的实例,这样当手机屏幕发生旋转的时候,就无法保留其中的数据了
运行后旋转一下模拟器的屏幕,就会发现Activity虽然被重新创建了,但是计数器的数据却没有丢失
向ViewModel传递参数
如果需要向ViewModel传递参数,可以借助ViewModelProvider.Factory
添加功能:退出程序后又重新打开的情况下,数据仍然不会丢失
给MyViewModel的构造函数添加countReserved参数
给MyViewModel的构造函数添加一个countReserved参数,这个参数用于记录之前保存的计数值,并在初始化的时候赋值给counter变量
class MyViewModel(countReserved: Int) : ViewModel() {
// counter变量用于计数
var counter = countReserved
}
新建一个MyViewModelFactory类
新建一个MyViewModelFactory类,并让它实现ViewModelProvider.Factory接口
class MyViewModelFactory(private val countReserved: Int) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MyViewModel(countReserved) as T
}
}
MyViewModelFactory 的构造函数中也接收了一个countReserved参数。另外ViewModelProvider.Factory接口要求我们必须实现create()方法,因此这里在create()方法中我们创建了MainViewModel的实例,并将countReserved参数传了进去。为什么这里就可以创建MainViewModel的实例了呢?因为create()方法的执行时机和Activity的生命周期无关,所以不会产生之前提到的问题
修改MyActivity中的代码
class MyActivity : AppCompatActivity() {
private lateinit var viewModel: MyViewModel
private lateinit var preferences: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my)
// 获取退出app前保存的数据
preferences = getPreferences(Context.MODE_PRIVATE)
val countReserved = preferences.getInt("count_reserved", 0)
// 不可以直接去创建ViewModel的实例,一定要通过ViewModelProvider来获取ViewModel的实例
viewModel =
ViewModelProvider(this, MyViewModelFactory(countReserved)).get(MyViewModel::class.java)
......
}
/**
* 获取viewModel的数据刷新界面
*/
private fun refreshCounter() {
num_text.text = viewModel.counter.toString()
}
override fun onDestroy() {
super.onDestroy()
preferences.edit().putInt("count_reserved", viewModel.counter).apply()
}
}
在ViewModelProvider中,额外传入了一个MyViewModelFactory参数,将读取到的计数值传给了MyViewModelFactory的构造函数,这样就完成了向ViewModel传递数据
注意
不能把Activity的实例传给ViewModel,因为ViewModel的生命周期是长于Activity的,如果把Activity的实例传给ViewModel,就很有可能会因为Activity无法释放而造成内存泄漏