ViewModel初识
问题
潜在的挑战是,Android Activity生命周期具有很多状态,并且由于配置更改,单个Activity可能会在这些不同状态之间循环多次。
图片来自官网当“活动”经历所有这些状态时,您可能还需要将临时 UI 数据保存在内存中。我将定义临时UI数据作为UI所需的数据。示例包括用户输入的数据,运行时生成的数据或从数据库加载的数据。这些数据可以是位图图像,RecyclerView
所需的对象列表,或者在这种情况下是篮球得分。
以前,您可能曾经 onRetainCustomNonConfigurationInstance
在配置更改期间保存此数据,然后在另一端将其解析获取。但是,如果您的数据不需要知道或管理“活动”所处于的生命周期状态,那会不会更好呢?除了scoreTeamA
将数据存储在 Activity 内,而不是将其存储在 Activity 内,而是将其存储在Activity之外的其他地方,该怎么办?这是ViewModel
类的目的。
在下图中,您可以看到一个活动的生命周期,该活动经历一个轮换然后最终完成。ViewModel
的生命周期显示在关联的Activity生命周期旁边。请注意,ViewModels
可以轻松地与 Fragment 和 Activity 结合使用,我将它们称为UI控制器。本示例重点介绍活动。
从您首次请求ViewModel
时(通常在onCreate
Activity中)到活动完成并销毁为止,ViewModel
存在。onCreate
在Activity的生命周期内可能会多次调用(例如,旋转应用程序时),但ViewModel
会在整个过程中保留下来。
一个非常简单的例子
设置和使用 ViewModel 分为三个步骤:
-
通过创建扩展 ViewModel 的类,将数据与UI控制器分离
-
设置 ViewModel 和UI控制器之间的通信
-
在 UI 控制器中使用 ViewModel
步骤1:创建一个ViewModel类
注意:要创建 ViewModel ,首先需要添加正确的生命周期依赖项。看看这里如何。
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
// alternatively - just ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
// For Kotlin use lifecycle-viewmodel-ktx
通常,您将为应用程序中的每个屏幕创建一个ViewModel类。这个ViewModel类将保存与屏幕关联的所有数据,并具有用于存储的数据的getter和setter。这会将代码显示出来,以显示 UI(在“Activities”和“Fragments”中实现)与数据(现在位于ViewModel中)之间。因此,让我们为Court-Counter中的一个屏幕创建一个ViewModel类:
class ScoreViewModel : ViewModel() {
var scoreTeamA: Int = 0
var scoreTeamB: Int = 0
}
步骤2:关联控制器和ViewModel
您的UI控制器(又称“Activity”或“Fragment”)需要了解您的ViewModel
。这样,您的UI控制器就可以在发生UI交互时显示数据并更新数据,例如,按下按钮以增加在Court-Counter中的团队得分。
但是,ViewModels
不应保留对Activity,Fragment或Context的引用。此外,ViewModels
不应包含包含对UI控制器(例如Views)的引用的元素,因为这将创建对Context 的间接引用。
您不应该存储这些对象的原因是 ViewModels
的寿命超过了特定的UI控制器实例-如果您将Activity旋转3次,则您已经创建了三个不同的Activity实例,但是只有一个ViewModel
。
考虑到这一点,让我们创建此UI控制器/ ViewModel关联。您将要在UI Controller中为ViewModel创建一个成员变量。对于Court-Counter,它是这样的:
import androidx.activity.viewModels
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private val viewModel: ScoreViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvOne.text = viewModel.scoreTeamA.toString()
tvTwo.text = viewModel.scoreTeamB.toString()
btAdd.setOnClickListener {
viewModel.scoreTeamA += 1
tvOne.text = viewModel.scoreTeamA.toString()
}
Log.d("MainActivity", "onCreate 执行了")
}
}
注意:“ ViewModels中没有上下文”规则有一个例外。有时您可能需要一个Application上下文 (而不是Activity上下文)来与诸如系统服务之类的东西一起使用。可以将应用程序上下文存储在ViewModel中,因为应用程序上下文与应用程序生命周期相关联。这不同于与活动生命周期相关联的活动上下文。实际上,如果需要应用程序上下文,则应扩展 AndroidViewModel 这只是一个包含应用程序引用的 ViewModel。
步骤3:在UI控制器中使用ViewModel
要访问或更改UI数据,现在可以在ViewModel中使用数据。这是新onCreate
方法的示例,以及通过向A组增加一个点来更新分数的方法:
import androidx.activity.viewModels
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private val viewModel: ScoreViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvOne.text = viewModel.scoreTeamA.toString()
tvTwo.text = viewModel.scoreTeamB.toString()
btAdd.setOnClickListener {
viewModel.scoreTeamA += 1
tvOne.text = viewModel.scoreTeamA.toString()
}
Log.d("MainActivity", "onCreate 执行了")
}
}
专家提示: ViewModel也可以与另一个架构组件LiveData很好地配合使用,在本系列文章中我将不做深入探讨。使用LiveData的额外好处是它是可观察的:当数据更改时,它可以触发UI更新。您可以在此处了解有关LiveData的更多信息。