此 Codelab 是“Android Kotlin 基础知识”课程的一部分。如果您按顺序学习这些 Codelab,您将会充分发掘此课程的价值。“Android Kotlin 基础知识”Codelab 着陆页列出了所有课程 Codelab。
广告标题画面 | 游戏界面 | 得分界面 |
简介
在此 Codelab 中,您将了解 Android 架构组件之一 ViewModel
:
- 您可以使用
ViewModel
类以注重生命周期的方式存储和管理界面相关的数据。ViewModel
类让数据可在发生设备配置更改(例如屏幕旋转和键盘可用性更改)后继续留存。 - 您可以使用
ViewModelFactory
类来实例化并返回在配置更改后继续存在的ViewModel
对象。
您应当已掌握的内容
- 如何使用 Kotlin 创建基本 Android 应用。
- 如何使用导航图在应用中实现导航。
- 如何添加代码以在应用的各个目的地之间导航,以及在导航目的地之间传递数据。
- activity 和 fragment 生命周期如何发挥作用。
- 如何在 Android Studio 中使用 Logcat 向应用添加日志记录信息和读取日志。
学习内容
- 如何使用推荐的 Android 应用架构。
- 如何在应用中使用
Lifecycle
、ViewModel
和ViewModelFactory
类。 - 如何在设备配置更改时保留界面数据。
- 什么是工厂方法设计模式以及如何使用它。
- 如何使用接口
ViewModelProvider.Factory
创建ViewModel
对象。
您将执行的操作
- 向应用添加
ViewModel
,以保存应用的数据,使数据在发生配置更改后继续留存。 - 使用
ViewModelFactory
和工厂方法设计模式来实例化具有构造函数参数的ViewModel
对象。
在第 5 课的 Codelab 中,您将从起始代码开始开发 GuessTheWord 应用。GuessTheWord 是一款双人猜字谜游戏,玩家在游戏中可以协作以获得最高得分。
第一位玩家查看应用中的单词,然后依次表演每个单词,确保不让第二位玩家看到单词。第二位玩家尝试猜出该单词。
要玩这个游戏,第一个玩家需要在设备上打开应用,然后会看到一个字词,例如“吉他”,如下面的屏幕截图所示。
第一位玩家表演该字词,但要小心不要实际说出该字词。
- 当第二位玩家正确猜出单词后,第一位玩家会按 Got It 按钮,这会将计数增加 1,并显示下一个单词。
- 如果第二位玩家猜不出单词,第一位玩家可以按跳过按钮,这样一来,计数器会减 1,并跳到下一个单词。
- 如需结束游戏,请按 End Game 按钮。(此功能未包含在本系列第一个 Codelab 的起始代码中。)
在此任务中,您将下载并运行起始应用,并检查代码。
第 1 步:开始使用
- 下载 GuessTheWord 起始代码,并在 Android Studio 中打开该项目。
- 在 Android 设备或模拟器上运行应用。
- 点按相应按钮。请注意,点按 Skip 按钮后会显示下一个单词,并且得分会减 1;点按 Got It 按钮后会显示下一个单词,并且得分会加 1。结束游戏按钮尚未实现,因此点按该按钮时不会发生任何情况。
第 2 步:进行代码演练
- 在 Android Studio 中,浏览代码以了解应用的工作方式。
- 请务必查看下述文件,这些文件尤为重要。
MainActivity.kt
此文件仅包含默认的模板生成的代码。
res/layout/main_activity.xml
此文件包含应用的主布局。当用户在应用中导航时,NavHostFragment
会托管其他 fragment。
界面 fragment
起始代码在 com.example.android.guesstheword.screens
软件包下包含三个不同软件包中的三个 fragment:
title/TitleFragment
:表示标题画面game/GameFragment
:表示游戏界面score/ScoreFragment
(用于得分界面)
screens/title/TitleFragment.kt
标题 fragment 是应用启动时显示的第一个界面。为 Play 按钮设置了点击处理程序,以导航到游戏界面。
screens/game/GameFragment.kt
这是主 fragment,游戏中的大多数操作都在其中发生:
- 为当前字词和当前得分定义了变量。
- 在
resetList()
方法内定义的wordList
是要在游戏中使用的示例字词列表。 onSkip()
方法是 Skip 按钮的点击处理程序。它会将得分减 1,然后使用nextWord()
方法显示下一个字词。onCorrect()
方法是 Got It 按钮的点击处理程序。此方法的实现方式与onSkip()
方法类似。唯一的区别在于,此方法会为得分加 1,而不是减 1。
screens/score/ScoreFragment.kt
ScoreFragment
是游戏的最终界面,用于显示玩家的最终得分。在此 Codelab 中,您将添加用于显示此界面和最终得分的实现。
res/navigation/main_navigation.xml
导航图显示了 fragment 如何通过导航连接:
- 用户可以从标题 fragment 导航到游戏 fragment。
- 用户可以从游戏 fragment 导航到得分 fragment。
- 用户可以从得分 fragment 导航回游戏 fragment。
在此任务中,您将查找 GuessTheWord 起始应用存在的问题。
- 运行起始代码,玩一下游戏,猜几个单词,并在猜完每个单词后点按 Skip 或 Got It。
- 游戏画面现在会显示一个字词和当前得分。此时通过旋转设备或模拟器更改屏幕方向,请注意,当前得分会丢失。
- 玩一下游戏,猜几个单词。当游戏界面显示某个得分时,关闭并重新打开应用。请注意,游戏会从头开始,因为应用状态未保存。
- 玩一下游戏,猜几个单词,然后点按 End Game 按钮。请注意,没有任何反应。
应用中的问题:
- 在配置更改期间(例如设备的屏幕方向发生变化时或应用关闭并重新启动时),起始应用不会保存和恢复应用状态。
您可以使用onSaveInstanceState()
回调来解决此问题。不过,使用onSaveInstanceState()
方法需要编写额外的代码将状态保存在一个软件包中,并实现相应逻辑来检索该状态。而且,可以存储的数据量极少。 - 当用户点按 End Game 按钮时,游戏界面不会导航到得分界面。
您可以使用本 Codelab 中介绍的应用架构组件来解决这些问题。
应用架构
应用架构是一种设计应用类及其之间关系的方式,可确保代码井然有序、在特定场景中表现出色,并且易于使用。在这组包含四个 Codelab 的课程中,您对 GuessTheWord 应用所做的改进遵循了 Android 应用架构指南,并且使用了 Android 架构组件。Android 应用架构类似于 MVVM(模型-视图-视图模型)架构模式。
GuessTheWord 应用遵循分离关注点设计原则,并划分为多个类,每个类负责处理一个单独的关注点。在本课的第一个 Codelab 中,您将使用的类包括界面控制器、ViewModel
和 ViewModelFactory
。
界面控制器
界面控制器是基于界面的类,例如 Activity
或 Fragment
。界面控制器应仅包含处理界面和操作系统交互的逻辑,例如显示视图和捕获用户输入。请勿将决策逻辑(例如确定要显示的文本的逻辑)放入界面控制器中。
在 GuessTheWord 起始代码中,界面控制器是三个 fragment:GameFragment
、ScoreFragment,
和 TitleFragment
。遵循“关注点分离”的设计原则,GameFragment
仅负责将游戏元素绘制到屏幕上并了解用户何时点按按钮,除此之外不负责任何其他操作。当用户点按某个按钮时,此信息会传递给 GameViewModel
。
ViewModel
ViewModel
中存储了要在与 ViewModel
关联的 fragment 或 activity 中显示的数据。ViewModel
可以对数据进行简单的计算和转换,以准备好数据供界面控制器进行显示。在此架构中,ViewModel
执行决策。GameViewModel
保存得分值、字词列表和当前字词等数据,因为这些数据将显示在屏幕上。GameViewModel
还包含用于执行简单计算以确定数据当前状态的业务逻辑。
ViewModelFactory
ViewModelFactory
可实例化 ViewModel
对象,无论是否带有构造函数形参。
在后续 Codelab 中,您将了解与界面控制器和 ViewModel
相关的其他 Android 架构组件。
ViewModel
类旨在存储和管理界面相关的数据。在此应用中,每个 ViewModel
都与一个 fragment 相关联。
在此任务中,您将向应用添加第一个 ViewModel
,即 GameFragment
的 GameViewModel
。您还将了解 ViewModel
具有生命周期感知能力意味着什么。
第 1 步:添加 GameViewModel 类
- 打开
build.gradle(module:app)
文件。在dependencies
块内,添加ViewModel
.
的 Gradle 依赖项。如果您使用该库的最新版本,解决方案应用应能按预期进行编译。如果不是,请尝试解决问题,或恢复到下方所示的版本。
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
- 在
screens/game/
软件包文件夹中,创建一个名为GameViewModel
的新 Kotlin 类。 - 使
GameViewModel
类扩展抽象类ViewModel
。 - 为了帮助您更好地了解
ViewModel
如何感知生命周期,请添加一个带有log
语句的init
块。
class GameViewModel : ViewModel() {
init {
Log.i("GameViewModel", "GameViewModel created!")
}
}
第 2 步:替换 onCleared() 并添加日志记录
当关联的 fragment 分离后或 activity 完成后,ViewModel
会被销毁。在 ViewModel
被销毁前会调用 onCleared()
回调来清理资源。
- 在
GameViewModel
类中,替换onCleared()
方法。 - 在
onCleared()
内添加日志语句,以跟踪GameViewModel
生命周期。
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}
第 3 步:将 GameViewModel 与游戏 fragment 相关联
ViewModel
需要与界面控制器相关联。如需将两者相关联,请在界面控制器内创建对 ViewModel
的引用。
在此步骤中,您将在相应的界面控制器(即 GameFragment
)内创建 GameViewModel
的引用。
- 在
GameFragment
类中,添加一个类型为GameViewModel
的字段作为顶级类变量。
private lateinit var viewModel: GameViewModel
第 4 步:初始化 ViewModel
在发生屏幕旋转等配置更改期间,fragment 等界面控制器会被重新创建。不过,ViewModel
实例会继续存在。如果您使用 ViewModel
类创建 ViewModel
实例,则每次重新创建 fragment 时都会创建一个新对象。请改用 ViewModelProvider
创建 ViewModel
实例。
ViewModelProvider
的运作方式:
- 如果存在现有的
ViewModel
,ViewModelProvider
会返回该对象;如果不存在,则会创建一个新的ViewModel
。 ViewModelProvider
会创建与指定范围(activity 或 fragment)关联的ViewModel
实例。- 只要该作用域处于有效状态,就会保留创建的
ViewModel
。例如,如果作用域是 fragment,则ViewModel
会一直保留到 fragment 分离为止。
初始化 ViewModel
,使用 ViewModelProviders.of()
方法创建 ViewModelProvider
:
- 在
GameFragment
类中,初始化viewModel
变量。将此代码放在onCreateView()
内,在绑定变量的定义之后。使用ViewModelProviders.of()
方法,并传入关联的GameFragment
上下文和GameViewModel
类。 - 在
ViewModel
对象的初始化上方,添加一条用于记录ViewModelProviders.of()
方法调用的日志语句。
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
- 运行应用。在 Android Studio 中,打开 Logcat 窗格并按
Game
进行过滤。点按设备或模拟器上的播放按钮。游戏界面随即打开。
如 Logcat 中所示,GameFragment
的onCreateView()
方法会调用ViewModelProviders.of()
方法来创建GameViewModel
。您添加到GameFragment
和GameViewModel
的日志记录语句会显示在 Logcat 中。
- 在您的设备上或模拟器中启用自动屏幕旋转设置,并更改几次屏幕方向。
GameFragment
每次都被销毁并重新创建,因此每次都会调用ViewModelProviders.of()
。而GameViewModel
只创建了一次,并不是每次调用都重新创建或销毁。
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of
- 退出游戏或退出游戏 fragment。
GameFragment
会被销毁。关联的GameViewModel
也会被销毁,并调用onCleared()
回调。
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel destroyed!
ViewModel
不受配置变化的影响,因此非常适合存储需要在配置变化后继续留存的数据:
- 将要显示在屏幕上的数据和用于处理该数据的代码放在
ViewModel
中。 ViewModel
不应包含对 fragment、activity 或视图的引用,因为 activity、fragment 和视图在配置更改后不会继续存在。
为了进行比较,以下展示了在添加 ViewModel
之前和之后,初始应用中 GameFragment
界面数据的处理方式:ViewModel
- 添加
ViewModel
之前:
当应用经历配置变更(例如屏幕旋转)时,游戏 fragment 会被销毁并重新创建。数据会丢失。 - 添加
ViewModel
并将游戏 fragment 的界面数据移入ViewModel
后:
fragment 需要显示的所有数据现在都位于ViewModel
中。当应用经历配置更改时,ViewModel
会继续存在,并且数据会保留。
在此任务中,您将应用的界面数据以及处理数据的方法移至 GameViewModel
类。这样做是为了在配置更改期间保留数据。
第 1 步:将数据字段和数据处理移至 ViewModel
将以下数据字段和方法从 GameFragment
移至 GameViewModel
:
- 移动
word
、score
和wordList
数据字段。确保word
和score
不是private
。
请勿移动绑定变量GameFragmentBinding
,因为它包含对视图的引用。此变量用于扩充布局、设置点击监听器以及在屏幕上显示数据,这些都是 fragment 的职责。 - 移动了
resetList()
和nextWord()
方法。这些方法决定了要在屏幕上显示哪些字词。 - 在
onCreateView()
方法内,将对resetList()
和nextWord()
的方法调用移至GameViewModel
的init
块。
这些方法必须位于init
块中,因为您应该在创建ViewModel
时重置字词列表,而不是在每次创建 fragment 时都重置。您可以删除GameFragment
的init
代码块中的日志语句。
GameFragment
中的 onSkip()
和 onCorrect()
点击处理程序包含用于处理数据和更新界面的代码。用于更新界面的代码应保留在 fragment 中,但用于处理数据的代码需要移至 ViewModel
。
目前,请在两个位置放置相同的方法:
- 将
GameFragment
中的onSkip()
和onCorrect()
方法复制到GameViewModel
。 - 在
GameViewModel
中,确保onSkip()
和onCorrect()
方法不是private
,因为您将从 fragment 引用这些方法。
以下是重构后的 GameViewModel
类的代码:
class GameViewModel : ViewModel() {
// The current word
var word = ""
// The current score
var score = 0
// The list of words - the front of the list is the next word to guess
private lateinit var wordList: MutableList<String>
/**
* Resets the list of words and randomizes the order
*/
private fun resetList() {
wordList = mutableListOf(
"queen",
"hospital",
"basketball",
"cat",
"change",
"snail",
"soup",
"calendar",
"sad",
"desk",
"guitar",
"home",
"railway",
"zebra",
"jelly",
"car",
"crow",
"trade",
"bag",
"roll",
"bubble"
)
wordList.shuffle()
}
init {
resetList()
nextWord()
Log.i("GameViewModel", "GameViewModel created!")
}
/**
* Moves to the next word in the list
*/
private fun nextWord() {
if (!wordList.isEmpty()) {
//Select and remove a word from the list
word = wordList.removeAt(0)
}
updateWordText()
updateScoreText()
}
/** Methods for buttons presses **/
fun onSkip() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}
}
以下是重构后的 GameFragment
类的代码:
/**
* Fragment where the game is played
*/
class GameFragment : Fragment() {
private lateinit var binding: GameFragmentBinding
private lateinit var viewModel: GameViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate view and obtain an instance of the binding class
binding = DataBindingUtil.inflate(
inflater,
R.layout.game_fragment,
container,
false
)
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
binding.correctButton.setOnClickListener { onCorrect() }
binding.skipButton.setOnClickListener { onSkip() }
updateScoreText()
updateWordText()
return binding.root
}
/** Methods for button click handlers **/
private fun onSkip() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
private fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
/** Methods for updating the UI **/
private fun updateWordText() {
binding.wordText.text = word
}
private fun updateScoreText() {
binding.scoreText.text = score.toString()
}
}
第 2 步:更新 GameFragment 中对点击处理程序和数据字段的引用
- 在
GameFragment
中,更新onSkip()
和onCorrect()
方法。移除用于更新得分的代码,改为在viewModel
上调用相应的onSkip()
和onCorrect()
方法。 - 由于您已将
nextWord()
方法移至ViewModel
,因此游戏 fragment 无法再访问该方法。
在GameFragment
中,在onSkip()
和onCorrect()
方法中,将对nextWord()
的调用替换为updateScoreText()
和updateWordText()
。这些方法用于在屏幕上显示数据。
private fun onSkip() {
viewModel.onSkip()
updateWordText()
updateScoreText()
}
private fun onCorrect() {
viewModel.onCorrect()
updateScoreText()
updateWordText()
}
- 在
GameFragment
中,更新score
和word
变量以使用GameViewModel
变量,因为这些变量现在位于GameViewModel
中。
private fun updateWordText() {
binding.wordText.text = viewModel.word
}
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.toString()
}
- 在
GameViewModel
中,在nextWord()
方法内,移除对updateWordText()
和updateScoreText()
方法的调用。这些方法现在从GameFragment
调用。 - 构建应用,确保没有错误。如果出现错误,请清理并重建项目。
- 运行应用并玩一下游戏,猜几个单词。在游戏界面中,旋转设备。请注意,在更改屏幕方向后,当前得分和当前单词会保留下来。
太棒了!现在,应用的所有数据都存储在 ViewModel
中,因此在配置更改期间会保留。
在此任务中,您将实现 End Game 按钮的点击监听器。
- 在
GameFragment
中,添加一个名为onEndGame()
的方法。当用户点按结束游戏按钮时,系统会调用onEndGame()
方法。
private fun onEndGame() {
}
- 在
GameFragment
中,在onCreateView()
方法内,找到为 Got It 和 Skip 按钮设置点击监听器的代码。在这两行代码下方,为 End Game 按钮设置点击监听器。使用绑定变量binding
。在点击监听器内,调用onEndGame()
方法。
binding.endGameButton.setOnClickListener { onEndGame() }
- 在
GameFragment
中,添加一个名为gameFinished()
的方法,以将应用导航到得分界面。使用 Safe Args 将得分作为实参传入。
/**
* Called when the game is finished
*/
private fun gameFinished() {
Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
val action = GameFragmentDirections.actionGameToScore()
action.score = viewModel.score
NavHostFragment.findNavController(this).navigate(action)
}
- 在
onEndGame()
方法中,调用gameFinished()
方法。
private fun onEndGame() {
gameFinished()
}
- 运行应用,玩游戏,并循环浏览一些单词。点按结束游戏 按钮。请注意,应用会进入得分界面,但不会显示最终得分。您将在下一项任务中解决此问题。
当用户结束游戏时,ScoreFragment
不会显示得分。您希望使用 ViewModel
来保存要由 ScoreFragment
显示的分数。您将在 ViewModel
初始化期间使用工厂方法模式传入得分值。
工厂方法模式是一种使用工厂方法来创建对象的创建型设计模式。工厂方法是一种返回同一类实例的方法。
在此任务中,您将创建一个 ViewModel
,其中包含得分 fragment 的参数化构造函数和一个用于实例化 ViewModel
的工厂方法。
- 在
score
软件包下,创建一个名为ScoreViewModel
的新 Kotlin 类。此类将成为得分片段的ViewModel
。 - 从
ViewModel.
扩展ScoreViewModel
类。为最终得分添加构造函数参数。添加一个带有日志语句的init
代码块。 - 在
ScoreViewModel
类中,添加一个名为score
的变量,用于保存最终得分。
class ScoreViewModel(finalScore: Int) : ViewModel() {
// The final score
var score = finalScore
init {
Log.i("ScoreViewModel", "Final score is $finalScore")
}
}
- 在
score
软件包下,创建另一个名为ScoreViewModelFactory
的 Kotlin 类。此类将负责实例化ScoreViewModel
对象。 - 从
ViewModelProvider.Factory
扩展ScoreViewModelFactory
类。为最终得分添加构造函数参数。
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}
- 在
ScoreViewModelFactory
中,Android Studio 会显示有关未实现的抽象成员的错误。如需解决此错误,请替换create()
方法。在create()
方法中,返回新构建的ScoreViewModel
对象。
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) {
return ScoreViewModel(finalScore) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
- 在
ScoreFragment
中,为ScoreViewModel
和ScoreViewModelFactory
创建类变量。
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory
- 在
ScoreFragment
的onCreateView()
中,初始化binding
变量后,初始化viewModelFactory
。使用ScoreViewModelFactory
。将参数 bundle 中的最终得分作为构造函数参数传递给ScoreViewModelFactory()
。
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)
- 在
onCreateView(
中,初始化viewModelFactory
后,初始化viewModel
对象。调用ViewModelProviders.of()
方法,传入关联的乐谱 fragment 上下文和viewModelFactory
。这将使用viewModelFactory
类中定义的工厂方法创建ScoreViewModel
对象.
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ScoreViewModel::class.java)
- 在
onCreateView()
方法中,初始化viewModel
后,将scoreText
视图的文本设置为ScoreViewModel
中定义的最终得分。
binding.scoreText.text = viewModel.score.toString()
- 运行应用并玩游戏。轮流猜一些或所有单词,然后点按结束游戏。请注意,得分 fragment 现在会显示最终得分。
- 可选:通过过滤
ScoreViewModel
,在 Logcat 中检查ScoreViewModel
日志。应显示得分值。
2019-02-07 10:50:18.328 com.example.android.guesstheword I/ScoreViewModel: Final score is 15
在此任务中,您实现了 ScoreFragment
以使用 ViewModel
。您还学习了如何使用 ViewModelFactory
接口为 ViewModel
创建参数化构造函数。
恭喜!您已更改应用的架构,以使用某个 Android 架构组件 ViewModel
。您解决了应用生命周期问题,现在游戏的数据在发生配置更改后仍会留存。您还学习了如何使用 ViewModelFactory
接口创建用于创建 ViewModel
的参数化构造函数。
Android Studio 项目:GuessTheWord
- Android 应用架构准则建议将具有不同责任的类分离。
- 界面控制器是基于界面的类,例如
Activity
或Fragment
。界面控制器应仅包含处理界面和操作系统交互的逻辑;它们不应包含界面中要显示的数据。将这些数据放入ViewModel
中。 ViewModel
类负责存储和管理与界面相关的数据。ViewModel
类让数据可在发生屏幕旋转等配置更改后继续留存。ViewModel
是推荐使用的 Android 架构组件之一。ViewModelProvider.Factory
是一个可用于创建ViewModel
对象的接口。
下表比较了界面控制器与用于保存其数据的 ViewModel
实例:
界面控制器 | ViewModel |
界面控制器的一个示例是您在此 Codelab 中创建的 |
|
不包含任何要在界面中显示的数据。 | 包含界面控制器在界面中显示的数据。 |
包含用于显示数据的代码,以及点击监听器等用户事件代码。 | 包含用于数据处理的代码。 |
在每次配置更改期间销毁并重新创建。 | 仅当关联的界面控制器永久消失时销毁 - 对于 activity,是在 activity 完成时;对于 fragment,是在 fragment 分离时。 |
包含视图。 | 不应包含对 activity、fragment 或视图的引用,因为它们在配置发生变化后会失效,但 |
包含对关联 | 不包含对关联界面控制器的任何引用。 |
Udacity 课程:
Android 开发者文档:
其他:
此部分列出了在由讲师主导的课程中,学生学习此 Codelab 后可能需要完成的家庭作业。讲师自行决定是否执行以下操作:
- 根据需要布置作业。
- 告知学生如何提交家庭作业。
- 给家庭作业评分。
讲师可以酌情采纳这些建议,并且可以自由布置自己认为合适的任何其他家庭作业。
如果您是在自学此 Codelab,可随时通过这些家庭作业来检测您的知识掌握情况。
回答以下问题
问题 1
为避免数据在设备配置更改时丢失,您应在以下哪种类中存储应用数据?
ViewModel
LiveData
Fragment
Activity
问题 2
ViewModel
不可包含对 fragment、activity 或视图的引用。判断正误。
- 正确
- 错误
问题 3
ViewModel
何时销毁?
- 当关联的界面控制器在设备屏幕方向发生更改的情况下被销毁并重新创建时。
- 屏幕方向更改时。
- 当关联的界面控制器完成(如果是 activity)或分离(如果是 fragment)时。
- 用户按返回按钮时。
问题 4
ViewModelFactory
接口的用途是什么?
- 实例化
ViewModel
对象。 - 在屏幕方向更改期间保留数据。
- 刷新屏幕上显示的数据。
- 在应用数据更改时收到通知。
开始学习下一课:
如需本课程中其他 Codelab 的链接,请参阅“Android Kotlin 基础知识”Codelab 着陆页。