此 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 并显示下一个单词。
- 如果第二个玩家猜不出单词,第一个玩家会按 Skip 按钮,这会将计数减少 1,并跳至下一个单词。
- 如需结束游戏,请按结束游戏按钮。(本系列第一个 Codelab 的起始代码中不包含此功能。)
在此任务中,您将下载并运行起始应用并检查代码。
第 1 步:开始使用
- 下载 GuessTheWord 起始代码并在 Android Studio 中打开项目。
- 在 Android 设备或模拟器上运行应用。
- 点按按钮。请注意,Skip 按钮会显示下一个单词并降低分数,而 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
,用于得分屏幕
screen/title/TitleFragment.kt
标题 fragment 是应用启动时显示的第一个屏幕。将点击处理程序设置为 Play 按钮,以导航到游戏屏幕。
screen/game/GameFragment.kt
这是主要 fragment,其中大多数游戏操作发生:
- 为当前单词和当前得分定义变量。
resetList()
方法中定义的wordList
是要在游戏中使用的单词的示例列表。onSkip()
方法是 Skip 按钮的点击处理程序。它会将得分减少 1,然后使用nextWord()
方法显示下一个字词。onCorrect()
方法是 Got It 按钮的点击处理程序。此方法的实现方式与onSkip()
方法类似。唯一的区别在于,此方法会在得分的基础上加 1,而不是减去。
screen/score/ScoreFragment.kt
ScoreFragment
是游戏中的最后一个屏幕,显示玩家的最终得分。在此 Codelab 中,您将添加显示此屏幕和显示最终得分的实现。
res/navigation/main_navigation.xml
导航图显示了 fragment 如何通过导航连接:
- 从标题 fragment 可以导航到游戏 fragment。
- 从游戏 fragment,用户可以导航到得分 fragment。
- 用户可通过得分 Fragment 返回到游戏 Fragment。
在此任务中,您将查找 GuessTheWord 起始应用的问题。
- 运行起始代码并玩完几个单词,在猜完每个单词后点按跳过或知道了。
- 游戏画面现在会显示一个单词和当前得分。此时通过旋转设备或模拟器更改屏幕方向,请注意,当前得分已丢失。
- 再猜几个单词。当游戏屏幕显示某个得分时,关闭并重新打开应用。请注意,游戏不会从头开始保存,因为系统不会保存应用状态。
- 玩一下游戏,猜几个单词,然后点按结束游戏按钮。请注意,没有任何反应。
应用中存在的问题:
- 起始应用不会在配置更改期间(例如设备屏幕方向发生变化或应用关闭后重启时)保存和恢复应用状态。
您可以使用onSaveInstanceState()
回调来解决此问题。不过,使用onSaveInstanceState()
方法需要编写额外的代码以在 bundle 中保存状态,并实现用于检索该状态的逻辑。此外,可以存储的数据量极少。 - 用户点按结束游戏按钮时,游戏屏幕未转到分数屏幕。
您可以使用此 Codelab 中了解的应用架构组件来解决这些问题。
应用架构
应用架构是一种设计应用类及其关系的方式,以便代码井然有序、在特定情况下表现出色且易于使用。在这组 Codelab(共四个)中,您对 GuessTheWord 应用所做的改进遵循了 Android 应用架构准则,并且您使用的是 Android 架构组件。Android 应用架构类似于 MVVM (model-view-viewmodel) 架构模式。
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
的运作方式:
ViewModelProvider
会返回一个现有的ViewModel
(如果存在),或创建一个新的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
进行过滤。点按设备或模拟器上的 Play 按钮。游戏屏幕打开。
如 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
之前以及添加 ViewModel
后如何处理起始应用中的 GameFragment
界面数据:
- 添加
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
中。
现在,在这两个地方放置相同的方法:
- 将
onSkip()
和onCorrect()
方法从GameFragment
复制到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
中,因此数据在配置更改期间会保留。
在此任务中,您将实现结束游戏按钮的点击监听器。
- 在
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 类。此类将是得分 Fragment 的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
。将最终得分从参数包作为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 分离时永久销毁。 |
包含视图。 | 绝不应包含对 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 着陆页。