Android Kotlin 基础知识 04.2:复杂的生命周期情形

此 Codelab 是“Android Kotlin 基础知识”课程的一部分。如果您按顺序学习这些 Codelab,您将会充分发掘此课程的价值。“Android Kotlin 基础知识”Codelab 着陆页列出了所有课程 Codelab。

简介

在上一个 Codelab 中,您学习了 ActivityFragment 生命周期,并了解了在 activity 和 fragment 中生命周期状态发生变化时调用的方法。在此 Codelab 中,您将更详细地探索 activity 生命周期。此外,您还将了解 Android Jetpack 的生命周期库,它可以帮助您通过更有条理且更易于维护的代码管理生命周期事件。

您应当已掌握的内容

  • 什么是 activity,以及如何在应用中创建 activity。
  • ActivityFragment 生命周期的基础知识,以及当 activity 在不同状态之间切换时调用的回调。
  • 如何替换 onCreate()onStop() 生命周期回调方法,以在 activity 或 fragment 生命周期内的不同时间执行操作。

学习内容

  • 如何在生命周期回调中设置、启动和停止应用的各个部分。
  • 如何使用 Android 生命周期库创建生命周期观察器,以及如何使 Activity 和 Fragment 生命周期更易于管理。
  • Android 进程关闭对应用中的数据有何影响,以及在 Android 关闭应用后如何自动保存和恢复这些数据。
  • 设备旋转和其他配置更改会如何影响生命周期状态并影响应用的状态。

您应执行的操作

  • 修改 DessertClicker 应用以包含计时器功能,并在 activity 生命周期内的不同时间启动和停止该计时器。
  • 修改应用以使用 Android 生命周期库,并将 DessertTimer 类转换为生命周期观察器。
  • 设置并使用 Android 调试桥 (adb) 来模拟应用的进程关闭过程以及之后发生的生命周期回调。
  • 实现 onSaveInstanceState() 方法,以保留在应用意外关闭时可能丢失的应用数据。添加代码,以便在应用再次启动时恢复相应数据。

在此 Codelab 中,您将扩展上一个 Codelab 中的 DessertClicker 应用。您可以添加后台计时器,然后将应用转换为使用 Android 生命周期库

在上一个 Codelab 中,您学习了如何通过替换各种生命周期回调以及在系统调用这些回调时进行日志记录来观察 activity 和 fragment 生命周期。在此任务中,您将探索在 DessertClicker 应用中管理生命周期任务的更复杂的示例。您将使用计时器来输出每秒一条日志语句,以及其已经运行的秒数。

第 1 步:设置 DessertTimer

  1. 打开上一个 Codelab 中的 DessertClicker 应用。(如果您没有该应用,可以在此处下载 DessertClickerLogs。)
  2. Project 视图中,依次展开 java > com.example.android.dessertclicker,然后打开 DessertTimer.kt。请注意,目前所有代码都会被注释掉,因此它不会作为应用的一部分运行。
  3. 在编辑器窗口中选择所有代码。依次选择 Code > Comment with Line Comment,或者按 Control+/(在 Mac 上按 Command+/)。此命令可以取消备注文件中的所有代码。(在您重新构建该应用之前,Android Studio 可能会显示未解析的引用错误。)
  4. 请注意,DessertTimer 类包含 startTimer()stopTimer(),它们可以启动和停止计时器。当 startTimer() 正在运行时,计时器每秒输出一条日志消息,并且输出中会注明计时器已经运行的总秒数。反过来,stopTimer() 方法会停止计时器和日志语句。
  1. 打开 MainActivity.kt。在该类的顶部,在 dessertsSold 变量的正下方,为计时器添加一个变量:
private lateinit var dessertTimer : DessertTimer;
  1. 向下滚动到 onCreate() 并创建一个新的 DessertTimer 对象,紧跟在 setOnClickListener() 调用之后:
dessertTimer = DessertTimer()


现在,您已经创建了甜点计时器对象,请考虑在哪里启动和停止计时器,使其仅在 Activity 在屏幕上运行时运行。您将在后续步骤中看到几个选项。

第 2 步:启动和停止计时器

onStart() 方法是在 activity 变为可见之前被调用的。onStop() 方法是在 activity 停止可见之后被调用的。这些回调似乎非常适合用于启动和停止计时器。

  1. MainActivity 类中,启动 onStart() 回调中的计时器:
override fun onStart() {
   super.onStart()
   dessertTimer.startTimer()

   Timber.i("onStart called")
}
  1. onStop() 中停止计时器:
override fun onStop() {
   super.onStop()
   dessertTimer.stopTimer()

   Timber.i("onStop Called")
}
  1. 编译并运行应用。在 Android Studio 中,点击 Logcat 窗格。在 Logcat 搜索框中,输入 dessertclicker,这样将同时按 MainActivityDessertTimer 类进行过滤。请注意,应用启动后,计时器也会立即开始运行。
  2. 点击返回按钮,请注意,计时器再次停止。计时器会停止,因为相应的 Activity 及其控制的计时器都被销毁了。
  3. 使用“最近使用的应用”屏幕返回该应用。请注意,在 Logcat 中,计时器会从 0 开始重新计时。
  4. 点击分享按钮。请注意,在 Logcat 中,计时器仍在计时。

  5. 点击主屏幕按钮。请注意,在 Logcat 中,计时器会停止运行。
  6. 使用“最近使用的应用”屏幕返回该应用。请注意,在 Logcat 中,计时器会从上次停止的位置重新启动。
  7. MainActivityonStop() 方法中,注释掉对 stopTimer() 的调用。注释 stopTimer() 演示了您在 onStart() 中开始某项操作,但忘记在 onStop() 中再次停止的情况。
  8. 编译并运行应用,在计时器启动后点击主屏幕按钮。即使应用在后台,计时器也在运行,并不断使用系统资源。让计时器继续运行是您的应用的内存泄漏,很可能不是您想要的行为。

    总的来说,当您在回调中设置或启动某项时,您可以在相应的回调中停止或移除该项。这样一来,您就可以避免让不再需要的任何内容持续运行。
  1. 取消备注 onStop() 中停止计时器的代码行。
  2. startTimer() 调用从 onStart() 剪切并粘贴到 onCreate()。此更改演示了在 onCreate() 中初始化和启动资源,而不是使用 onCreate() 初始化资源并使用 onStart() 启动资源的情况。
  3. 编译并运行应用。请注意,计时器开始计时,如您所料。
  4. 点击主屏幕按钮以停止应用。正如您所期望的那样,计时器停止运行。
  5. 使用“最近使用的应用”屏幕返回应用。请注意,在这种情况下,计时器不会再次启动,因为系统仅在应用启动时调用 onCreate(),而不会在应用返回前台时调用。

需要记住的要点:

  • 如果您在生命周期回调中设置了某项资源,之后还需要拆解该资源。
  • 在相应的方法中执行设置和拆解。
  • 如果您在 onStart() 中设置了某些内容,请在 onStop() 中将其停止或销毁。

在 DessertClicker 应用中,很容易看出,如果您在 onStart() 中启动了计时器,之后需要在 onStop() 中停止计时器。因为只有一个计时器,所以记不住计时器。

在更复杂的 Android 应用中,您可能会在 onStart()onCreate() 中设置许多内容,然后在 onStop()onDestroy() 中将其全部拆解。例如,您可能有动画、音乐、传感器或计时器需要设置和拆解以及启动和停止。如果您忘记一个,就会导致错误和头痛。

生命周期库Android Jetpack 的一部分,可简化此任务。当您必须跟踪许多可动部分而其中某些部分处于不同的生命周期状态时,该库特别有用。该库反转了生命周期的工作方式:通常,activity 或 fragment 会告知组件(如 DessertTimer)在发生生命周期回调时要执行什么操作。但是,当您使用生命周期库时,组件本身会观察生命周期的变化,然后在发生这些变化时执行所需的操作。

生命周期库有三个主要部分:

  • 生命周期所有者,是具有生命周期(并因此拥有)生命周期的组件。ActivityFragment 就是生命周期所有者。生命周期所有者会实现 LifecycleOwner 接口。
  • Lifecycle 类,用于存储生命周期所有者的实际状态,并在生命周期发生变化时触发事件。
  • 生命周期观察器 - 观察生命周期状态,并在生命周期发生变化时执行任务。Lifecycle 观察器实现了 LifecycleObserver 接口。

在此任务中,您将转换 DessertClicker 应用以使用 Android 生命周期库,并了解该库如何使 Android activity 和 fragment 生命周期的使用更易于管理。

第 1 步:使 DessertTimer 变成 LifecycleObserver

生命周期库的一个关键部分是生命周期观察的概念。观察可让类(如 DessertTimer)了解 activity 或 fragment 生命周期,并启动和停止它们自己来响应这些生命周期状态的变化。借助生命周期观察器,您可以让 activity 和 fragment 方法摆脱启动和停止对象的责任。

  1. 打开 DesertTimer.kt 类。
  2. 更改 DessertTimer 类的类签名,如下所示:
class DessertTimer(lifecycle: Lifecycle) : LifecycleObserver {

这一新的类定义可以执行以下两项操作:

  • 构造函数接受一个 Lifecycle 对象,该对象是计时器观察的生命周期。
  • 类定义可实现 LifecycleObserver 接口。
  1. runnable 变量下,将 init 代码块添加到类定义中。在 init 代码块中,使用 addObserver() 方法将来自所有者 (activity) 的生命周期对象连接到此类 (Observer)。
 init {
   lifecycle.addObserver(this)
}
  1. startTimer() 添加 @OnLifecycleEvent annotation 注解,并使用 ON_START 生命周期事件。生命周期观察器可以观察到的所有生命周期事件都位于 Lifecycle.Event 类中。
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {
  1. stopTimer() 执行相同的操作,不过使用的是 ON_STOP 事件:
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopTimer()

第 2 步:修改 MainActivity

您的 MainActivity 类已通过继承成为生命周期所有者,因为 FragmentActivity 父类实现了 LifecycleOwner。因此,您无需执行任何操作即可让 activity 具有生命周期感知能力。您只需将该 activity 的生命周期对象传入 DessertTimer 构造函数即可。

  1. 打开 MainActivity。在 onCreate() 方法中,修改 DessertTimer 的初始化,以包含 this.lifecycle
dessertTimer = DessertTimer(this.lifecycle)

Activity 的 lifecycle 属性用于存储此 Activity 拥有的 Lifecycle 对象。

  1. onCreate() 中移除对 startTimer() 的调用,并在 onStop() 中移除对 stopTimer() 的调用。您不再需要告知 DessertTimer 在 activity 中执行什么操作,因为 DessertTimer 现在自己观察生命周期,当生命周期状态发生变化时,它会自动收到通知。现在,您在这些回调中只需要记录一条消息。
  2. 编译并运行应用,然后打开 Logcat。请注意,计时器已开始按预期运行。
  3. 点击主屏幕按钮以将应用置于后台。请注意,正如预期的那样,计时器已停止运行。

如果 Android 在您的应用在后台时将其关闭,那么该应用及其数据会发生什么情况?了解这种棘手的极端情况非常重要。

当您的应用进入后台时,它并没有被销毁,它只是停止了并等待用户返回它。但是,Android 操作系统关注的主要问题之一是让前台的 activity 保持顺畅运行。例如,如果用户正在使用一款 GPS 应用帮助他们赶上公交车,那么快速呈现该 GPS 应用并不断显示路线非常重要。让 DessertClicker 应用(用户可能已经有几天没看它了)在后台保持顺畅运行不太重要。

Android 会管控后台应用,以使前台应用在运行过程中不会出现问题。例如,Android 会限制在后台运行的应用可以执行的处理量。

有时,Android 甚至会关闭整个应用进程,包括与应用关联的每个 activity。当系统承受压力并面临视觉延迟风险时,Android 就会执行这种关闭操作,因此此时不会运行额外的回调或代码。应用的进程只会在后台静默地关闭。但对用户来说,应用似乎并没有关闭。当用户返回 Android 操作系统已关闭的应用时,Android 会重启该应用。

在此任务中,您将模拟 Android 进程关闭,并检查当您的应用再次启动时它会发生什么情况。

第 1 步:使用 adb 模拟进程关闭

Android 调试桥 (adb) 是一个命令行工具,可让您向连接到计算机的模拟器和设备发送指令。在此步骤中,您将使用 adb 关闭应用的进程,并查看 Android 关闭应用时会出现什么情况。

  1. 编译并运行您的应用。点击几次纸杯蛋糕。
  2. 按主屏幕按钮以将您的应用置于后台。您的应用现已停止运行;如果 Android 需要使用应用使用的资源,应用可能会关闭。
  3. 在 Android Studio 中,点击 Terminal(终端)标签页以打开命令行终端。
  4. 输入 adb,然后按回车键。

    如果您看到大量以 Android Debug Bridge version X.XX.X 开头、以 tags to be used by logcat (see logcat —help 结尾的输出,一切正常。如果看到 adb: command not found,请确保执行命令中提供了 adb 命令。有关说明,请参阅“实用程序”一章中的“将 adb 添加到执行路径”。
  5. 复制此注释并将其粘贴到命令行中,然后按回车键:
adb shell am kill com.example.android.dessertclicker

此命令会指示任何连接的设备或模拟器使用 dessertclicker 软件包名称停止进程,但前提是应用在后台运行。由于您的应用之前在后台,因此设备或模拟器屏幕上没有显示任何内容来表明您的进程已停止。在 Android Studio 中,点击 Run 标签页,以查看一条内容为 "Application End." 的消息点击 Logcat 标签页,您会发现 onDestroy() 回调从未运行过,您的 Activity 只是结束了。

  1. 使用“最近使用的应用”屏幕返回应用。无论您的应用是已被置于后台还是已完全停止,它都会出现在“最近使用的应用”屏幕中。当您使用“最近使用的应用”屏幕返回应用时,activity 会再次启动。Activity 会经历整套启动生命周期回调,包括 onCreate()
  2. 请注意,应用重启后,会将您的“分数”(已售甜点数量和总金额)重置为默认值 (0)。如果 Android 关闭您的应用,为什么不保存状态?

    当操作系统重启您的应用时,Android 会尽最大努力将您的应用重置为之前的状态。每当您离开 activity 时,Android 都会获取某些视图的状态,并将其保存在一个 bundle 中。一些自动保存的数据示例包括 EditText 中的文本(只要布局中设置了 ID)以及 Activity 的返回堆栈。

    不过,Android 操作系统有时无法了解您的所有数据。例如,如果您在 DessertClicker 应用中有类似 revenue 的自定义变量,Android 操作系统就无法知道这些数据或其对 activity 的重要性。您需要自行将此数据添加到 bundle 中。

第 2 步:使用 onSaveInstanceState() 保存 bundle 数据

onSaveInstanceState() 方法是一个回调,用于在 Android 操作系统销毁您的应用后保存可能需要的任何数据。在生命周期回调图中,onSaveInstanceState() 是在 activity 停止后被调用的。每当您的应用进入后台时,系统都会调用它。

请将 onSaveInstanceState() 调用视为一项安全措施;这让您有机会在相应 activity 退出前台时将少量信息保存到 bundle 中。系统现在会保存这些数据,这是因为如果等到关闭应用时再保存,操作系统可能会面临资源压力。每次都保存数据可以确保 bundle 中的更新数据可以根据需要恢复。

  1. MainActivity 中,替换 onSaveInstanceState() 回调,并添加 Timber 日志语句。
override fun onSaveInstanceState(outState: Bundle) {
   super.onSaveInstanceState(outState)

   Timber.i("onSaveInstanceState Called")
}
  1. 编译并运行应用,然后点击主屏幕按钮,使其进入后台。请注意,onSaveInstanceState() 回调紧跟在 onPause()onStop() 之后:
  2. 在文件顶部(就在类定义之前)添加下列常量:
const val KEY_REVENUE = "revenue_key"
const val KEY_DESSERT_SOLD = "dessert_sold_key"
const val KEY_TIMER_SECONDS = "timer_seconds_key"

您将使用这些键将数据保存到实例状态 bundle 以及从中检索数据。

  1. 向下滚动到 onSaveInstanceState(),您会注意到 outState 参数,其类型为 Bundle

    内容包是键值对的集合,其中的键始终为字符串。您可以将基本值(如 intboolean 值)放入 bundle 中。
    由于系统会将此 bundle 保留在 RAM 中,因此最佳做法是确保 bundle 中的数据量较小。此 bundle 的大小也受到限制,但其大小因设备而异。通常情况下,您的存储空间大小应远小于 100k,否则可能会发生应用崩溃并出现 TransactionTooLargeException 错误的风险。
  2. onSaveInstanceState() 中,使用 putInt() 方法将 revenue 值(整数)放入 bundle 中:
outState.putInt(KEY_REVENUE, revenue)

putInt() 方法(以及 Bundle 类中的类似方法,如 putFloat()putString())采用两个参数:键的字符串(KEY_REVENUE 常量)以及要保存的实际值。

  1. 对已售甜点数量和计时器状态重复上述过程:
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)
outState.putInt(KEY_TIMER_SECONDS, dessertTimer.secondsCount)

第 3 步:使用 onCreate() 恢复 bundle 数据

  1. 向上滚动到 onCreate(),并检查方法签名:
override fun onCreate(savedInstanceState: Bundle) {

请注意,onCreate() 会在每次调用时都获取 Bundle。当您的 activity 因进程关闭而重启时,您保存的 bundle 会传递给 onCreate()。如果您的 activity 重新启动了,那么 onCreate() 中的这个 bundle 将是 null。因此,如果 bundle 不是 null,您就会知道自己是在基于一个先前已知的点“重新创建”该 activity。

  1. DessertTimer 设置完成后,将以下代码添加到 onCreate() 中:
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
}

通过测试是否有 null,可以确定 bundle 中是否存在数据,或者 bundle 是否为 null,进而告知您应用是重新启动过,还是在关闭后重新创建过。此测试是从 bundle 恢复数据的常见模式。

请注意,您在此处使用的键 (KEY_REVENUE) 与用于 putInt() 的键相同。为确保每次都使用相同的键,最佳做法是将这些键定义为常量。getInt() 用于从 bundle 中获取数据,就像您之前使用 putInt() 将数据放到 bundle 中一样。getInt() 方法接受两个参数:

  • 一个充当键的字符串,例如 "key_revenue"(表示收入值)。
  • 相应键在 bundle 中不存在值的情况下的默认值。

然后,您从 bundle 获取的整数将分配给 revenue 变量,并且界面将使用该值。

  1. 添加了 getInt() 方法,以恢复已售甜点数量和计时器的值:
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
   dessertTimer.secondsCount =
       savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
}
  1. 编译并运行应用。按纸杯蛋糕至少五次,直到屏幕切换到甜甜圈。点击主屏幕按钮以将应用置于后台。
  2. 在 Android Studio 的 Terminal(终端)标签页中,运行 adb 以关闭该应用的进程。
adb shell am kill com.example.android.dessertclicker
  1. 使用“最近使用的应用”屏幕返回应用。请注意,这一次,返回应用后显示的是来自 bundle 的正确收入和已售甜点数量值。另请注意,甜点已恢复为纸杯蛋糕。您还需要完成另一项工作,才能确保该应用从关闭状态完全恢复为原来的状态。
  2. MainActivity 中,检查 showCurrentDessert() 方法。请注意,此方法会根据当前已售的甜点数量以及 allDesserts 变量中的甜点列表,决定在 activity 中显示哪张甜点图片。
for (dessert in allDesserts) {
   if (dessertsSold >= dessert.startProductionAmount) {
       newDessert = dessert
   }
    else break
}

此方法依赖于已售甜点的数量来选择正确的图片。因此,您无需执行任何操作,即可在 onSaveInstanceState() 中将对图片的引用存储到 bundle 内。在该 bundle 中,您已经存储了已售甜点的数量。

  1. onCreate() 中,在用于从 bundle 恢复状态的代码块中,调用 showCurrentDessert()
 if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
   dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
   dessertTimer.secondsCount = 
      savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
   showCurrentDessert()                   
}
  1. 编译并运行应用,并将其置于后台。使用 adb 可关闭该进程。使用“最近使用的应用”屏幕返回应用。请注意,现在已售甜点数量和总收入的值以及甜点图片均已正确恢复。

在管理 activity 和 fragment 生命周期方面,还有最后一种特殊情况,了解这种情况非常重要:配置变更如何影响 activity 和 fragment 的生命周期。

当设备的状态发生了根本性改变,以至于系统解决改变的最简单方式就是完全关闭并重建 activity 时,就会发生配置变更。例如,如果用户更改了设备语言,整个布局可能就需要更改为适应不同的文本方向。如果用户将设备插入基座或添加物理键盘,应用布局可能需要利用不同的显示尺寸或布局。如果设备屏幕方向发生变化,比如设备从纵向旋转为横向或反过来,布局可能需要改为适应新的屏幕方向。

第 1 步:了解设备旋转和生命周期回调

  1. 编译并运行您的应用,然后打开 Logcat。
  2. 将设备或模拟器旋转为横屏模式。您可以使用旋转按钮或 Control 和箭头键(在 Mac 上,则为 Command 和箭头键)向左或向右旋转模拟器。
  3. 检查 Logcat 中的输出。过滤 MainActivity 的输出。
    请注意,当设备或模拟器旋转屏幕时,系统会调用所有生命周期回调来关闭 activity。然后,在重新创建 Activity 时,系统会调用所有生命周期回调来启动 Activity。
  4. MainActivity 中,注释掉整个 onSaveInstanceState() 方法。
  5. 再次编译并运行您的应用。点击几次纸杯蛋糕,然后旋转设备或模拟器。这一次,当设备旋转且 activity 关闭并重新创建时,activity 会以默认值启动。

    当配置发生更改时,Android 会使用您在上一个任务中了解的同一实例状态 bundle 来保存和恢复应用的状态。与进程关闭时一样,使用 onSaveInstanceState() 将应用的数据放入 bundle 中。然后,恢复 onCreate() 中的数据,以避免在设备旋转后丢失 Activity 状态数据。
  6. MainActivity 中,取消备注 onSaveInstanceState() 方法,运行应用,点击纸杯蛋糕,然后旋转应用或设备。请注意,这次的 dessert 数据在活动旋转过程中保留。

Android Studio 项目:DessertClickerFinal

生命周期提示

  • 如果您在一个生命周期回调中设置或开始了某项内容,之后需要在相应的回调中停止或移除该内容。通过停止该内容,您可以确保当不再需要它时它不会继续运行。例如,如果您在 onStart() 中设置计时器,则需要在 onStop() 中暂停或停止计时器。
  • 使用 onCreate() 仅初始化您的应用中在它首次启动时运行一次的部分。使用 onStart() 启动应用的两个部分,应用在应用启动时和每次返回前台时都会运行。

生命周期库

  • 使用 Android 生命周期库将生命周期控制从 Activity 或 Fragment 转移到需要生命周期感知的实际组件。
  • 生命周期所有者是具有(因而“拥有”)生命周期的组件,包括 ActivityFragment生命周期所有者会实现 LifecycleOwner 接口。
  • 生命周期观察者会关注当前的生命周期状态,并在生命周期发生变化时执行任务。生命周期观察器会实现 LifecycleObserver 接口。
  • Lifecycle 对象包含实际的生命周期状态,并在生命周期发生变化时触发事件。

如需创建生命周期感知型类,请执行以下操作:

  • 在需要具有生命周期感知能力的类中实现 LifecycleObserver 接口。
  • 使用来自 activity 或 fragment 的生命周期对象来初始化生命周期观察器类。
  • 在生命周期观察器类中,为生命周期感知型方法添加其感兴趣的生命周期状态变化注解。

    例如,@OnLifecycleEvent(Lifecycle.Event.ON_START) 注解用于指明该方法正在观察 onStart 生命周期事件。

进程关闭和保存 activity 状态

  • Android 会管控在后台运行的应用,以使前台应用在运行过程中不会出现问题。这种管控包括限制后台应用可以执行的处理量,有时甚至会关闭整个应用进程。
  • 用户无法判断系统是否关闭了一个后台应用。应用仍会显示在“最近使用的应用”屏幕中,并且应该会以用户退出时的状态重启。
  • Android 调试桥 (adb) 是一个命令行工具,可让您向连接到计算机的模拟器和设备发送指令。您可以使用 adb 来模拟应用中的进程关闭。
  • 当 Android 关闭您的应用进程时,系统不会调用 onDestroy() 生命周期方法。应用只是停止了。

保留 activity 和 fragment 状态

  • 当您的应用进入后台时(就在调用 onStop() 之后),系统会将应用数据保存到 bundle 中。系统会自动为您保存一些应用数据,如 EditText 的内容。
  • bundle 是 Bundle 的一个实例,Bundle 是键和值的集合。键始终是字符串。
  • 使用 onSaveInstanceState() 回调可将其他数据保存到您要保留的 bundle 中,即使应用自动关闭也是如此。如需将数据放入 bundle 中,请使用以 put 开头的 bundle 方法,例如 putInt()
  • 您可以通过 onRestoreInstanceState() 方法从 bundle 中重新获取数据,更常用的方式是使用 onCreate()onCreate() 方法有一个用于存储 bundle 的 savedInstanceState 参数。
  • 如果 savedInstanceState 变量包含 null,表示 activity 启动时没有状态 bundle,并且没有可以检索的状态数据。
  • 如需使用键从 bundle 中检索数据,请使用以 get 开头的 Bundle 方法,如 getInt()

配置变更

  • 当设备的状态发生了根本性改变,以至于系统解决改变的最简单方式就是关闭并重建 activity 时,就会发生配置变更。
  • 最常见的配置变更示例是用户将设备从纵向旋转为横屏模式,或者从横向转为纵向模式。当设备语言发生变化或插入硬件键盘时,也会发生配置更改。
  • 当发生配置变更时,Android 会调用所有 activity 生命周期的关闭回调。然后,Android 会从头开始重启相应 activity,同时运行所有生命周期启动回调。
  • 当 Android 因配置变更而关闭某个应用时,它会使用对 onCreate() 可用的状态 bundle 重启相应 activity。
  • 与进程关闭一样,请将应用的状态保存到 onSaveInstanceState() 中的捆绑包。

Udacity 课程:

Android 开发者文档:

其他:

此部分列出了在由讲师主导的课程中,学生学习此 Codelab 后可能需要完成的家庭作业。讲师自行决定是否执行以下操作:

  • 根据需要布置作业。
  • 告知学生如何提交家庭作业。
  • 给家庭作业评分。

讲师可以酌情采纳这些建议,并且可以自由布置自己认为合适的任何其他家庭作业。

如果您是在自学此 Codelab,可随时通过这些家庭作业来检测您的知识掌握情况。

更改应用

打开第 1 课中的 DiceRoller 应用。(如果您没有此应用,可以在此处下载该应用)。编译并运行应用,请注意,如果您旋转设备,骰子的当前值会丢失。实现 onSaveInstanceState() 以将该值保留在 bundle 中,并在 onCreate() 中恢复该值。

回答以下问题

问题 1

您的应用包含的物理模拟需要大量的计算才能显示。然后,用户会接到电话。下面哪项是正确的?

  • 在通话期间,您应继续计算物理模拟中对象的位置。
  • 在通话期间,您应停止计算物理模拟中对象的位置。

问题 2

当应用未在屏幕上时,您应替换哪个生命周期方法来暂停模拟?

  • onDestroy()
  • onStop()
  • onPause()
  • onSaveInstanceState()

问题 3

如需通过 Android 生命周期库使类具有生命周期感知能力,该类应该实现哪个接口?

  • Lifecycle
  • LifecycleOwner
  • Lifecycle.Event
  • LifecycleObserver

问题 4

在什么情况下,activity 中的 onCreate() 方法会收到包含数据的 Bundle(即 Bundle 不是 null)?可能会有多个答案。

  • 旋转设备后重启了 activity。
  • 从头开始了 activity。
  • activity 从后台返回后会恢复。
  • 重新启动了设备。

开始学习下一课:5.1:ViewModel 和 ViewModelFactory

如需本课程中其他 Codelab 的链接,请参阅“Android Kotlin 基础知识”Codelab 着陆页