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

此 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 生命周期库

5f5803e64f578dd5.png

在上一个 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 类进行过滤。请注意,应用启动之后,计时器也会立即开始运行。a399e0fea061361e.png
  2. 点击返回按钮,请注意,计时器再次停止。计时器停止是因为 activity 及其控制的计时器都已被销毁。
  3. 使用“最近使用的应用”屏幕返回应用。请注意,在 Logcat 中,计时器从 0 重新开始计时。
  4. 点击分享按钮。请注意,在 Logcat 中,计时器仍在运行。e2319779260eb5ee.png
  5. 点击主屏幕按钮。请注意,在 Logcat 中,计时器停止运行。
  6. 使用“最近使用的应用”屏幕返回应用。请注意,在 Logcat 中,计时器从上次停止的位置再次启动,因为我们在 onStart() 方法中调用了 startTimer()
  7. MainActivityonStop() 方法中,注释掉对 stopTimer() 的调用。注释掉 stopTimer() 演示的是这样一种情况:您在 onStart() 中开始了一项操作,但又忘记了在 onStop() 中停止该操作。
  8. 编译并运行应用,在计时器启动后点击主屏幕按钮。即使应用在后台,计时器也在运行,并不断使用系统资源。让计时器继续运行可能会不必要地使用手机上的计算资源,这或许不是您想要的行为。

一般模式是如果您在一个回调中设置或开始了某项内容,之后需要在相应的回调中停止或移除该内容。这样一来,您就可以避免让不再需要的任何内容持续运行。

  1. 取消备注 onStop() 中停止计时器的代码行。
  2. startTimer() 调用从 onStart() 剪切并粘贴到 onCreate()。此更改演示的是这样一种情况:您既在 onCreate() 中初始化资源,又在该方法中启动资源,而不是使用 onCreate() 初始化资源,并使用 onStart() 启动资源。
  3. 编译并运行应用。请注意,正如您所期望的那样,计时器开始运行。
  4. 点击主屏幕按钮以停止应用。正如您所期望的那样,计时器停止运行。
  5. 使用“最近使用的应用”屏幕返回应用。请注意,在这种情况下,计时器不会再次启动,因为只有在应用启动时才会调用 onCreate(),当应用返回前台时不会调用该方法。

需要记住的要点:

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

f6b25a71cec4e401.png

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

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

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

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

  • 生命周期所有者 - 具有(和“拥有”)生命周期的组件。ActivityFragment 就是生命周期所有者。生命周期所有者会实现 LifecycleOwner 接口。
  • 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. DessertTimer 类中 runnable 变量的声明下面,向该类定义中添加一个 init 代码块。在 init 代码块中,使用 addObserver() 方法将从所有者(即 activity)传入的生命周期对象连接到此类(即观察器)。
 init {
   lifecycle.addObserver(this)
}
  1. startTimer() 添加 @OnLifecycleEvent 注解,并使用 ON_START 生命周期事件。生命周期观察器可以观察的所有生命周期事件都在 Lifecycle.Event 类中。例如,@OnLifecycleEvent(Lifecycle.Event.ON_START) 注解表明以下方法正在观察 onStart 生命周期事件。
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {
  1. stopTimer() 执行相同的操作,不过使用的是 ON_STOP 事件:
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopTimer()

第 2 步:修改 MainActivity

您的 MainActivity 类已经通过面向对象的继承成为生命周期所有者。请注意,MainActivityAppCompatActivity, 的子类,而后者又是 FragmentActivity 的子类。由于 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。请注意,正如预期的那样,计时器已开始运行。a399e0fea061361e.png
  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 标签页以打开命令行终端。

a37c3740cc57b67d.png

  1. 输入 adb,然后按回车键。

如果您看到大量以 Android Debug Bridge version X.XX.X 开头且以 tags to be used by logcat (see logcat --help) 结尾的输出,表明一切正常。如果您看到 adb: command not found,请确保 adb 命令在执行路径中可用。有关说明,请参阅“实用程序”一章中的“将 adb 添加到执行路径”。

  1. 将以下注释复制并粘贴到命令行中,然后按回车键:
adb shell am kill com.example.android.dessertclicker

此命令可以告知任何已连接的设备或模拟器发送 STOP 消息以终止具有 dessertclicker 软件包名称的进程,但仅当应用在后台时才这样做。由于您的应用之前在后台,因此设备或模拟器屏幕上没有显示任何内容来表明您的进程已停止。在 Android Studio 中,点击 Run 标签页以查看调用的 onStop() 方法。点击 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 停止后被调用的。每当您的应用进入后台时,系统都会调用它。

c259ab6beca0ca88.png

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

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

   Timber.i("onSaveInstanceState Called")
}
  1. 编译并运行应用,然后点击主屏幕按钮,以将应用置于后台。请注意,onSaveInstanceState() 回调是紧接着 onPause()onStop() 发生的:c80ed57c9e4e23a.png
  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

bundle 是键值对的集合,其中的键始终为字符串。您可以将基元值(如 intboolean 值)放入 bundle 中。由于系统会将此 bundle 保留在 RAM 中,因此最佳做法是让 bundle 中的数据量少一点。此 bundle 的大小也受到限制,但其大小因设备而异。通常,您应存储远远少于 100k 的数据,否则会面临应用崩溃并出现 TransactionTooLargeException 错误的风险。

  1. 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. 将以下代码添加到 onCreate(),在 DessertTimer 设置后面:
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 菜单中依次选择 Run > Stop ‘app' 来停止应用。现在,应用已安装在设备/模拟器上,但并未运行。
  3. 在设备/模拟器中打开应用启动器,然后选择 Dessert Clicker 应用。按纸杯蛋糕至少五次,直到切换到甜甜圈。点击主屏幕按钮以将应用置于后台。
  4. 在 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. 编译并运行应用。
  2. 从 Android Studio 菜单中依次选择 Run > Stop ‘app' 来停止应用。现在,应用已安装在设备/模拟器上,但并未运行。
  3. 打开应用启动器,然后选择 Dessert Clicker 应用。点击几次甜点图标。
  4. 将应用置于后台。使用 adb 关闭进程。
adb shell am kill com.example.android.dessertclicker
  1. 使用“最近使用的应用”屏幕返回应用。请注意,现在已售甜点数量和总收入的值以及甜点图片均已正确恢复。

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

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

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

  1. 编译并运行您的应用,然后打开 Logcat。
  2. 将设备或模拟器旋转为横屏模式。您可以使用旋转按钮或 Control 和箭头键(在 Mac 上,则为 Command 和箭头键)来向左或向右旋转模拟器。623fce7c623d42bd.png
  3. 检查 Logcat 中的输出。过滤 MainActivity 的输出。

a9e19f1f0f43fffe.png

请注意,设备或模拟器旋转屏幕时,系统会调用所有生命周期回调来关闭 activity。然后,在重新创建 activity 时,系统会调用所有生命周期回调来启动 activity。

  1. MainActivity 中,注释掉整个 onSaveInstanceState() 方法。
  2. 再次编译并运行您的应用。点击几次纸杯蛋糕,然后旋转设备或模拟器。这一次,当设备旋转而 activity 被关闭并重新创建时,该 activity 会使用默认值启动。

当发生配置变更时,Android 同样会使用您在上一项任务中了解的实例状态 bundle 来保存和恢复应用的状态。与进程关闭一样,使用 onSaveInstanceState() 将应用的数据放入 bundle 中。然后,在 onCreate() 中恢复数据,以避免在设备旋转后丢失 activity 状态数据。

  1. MainActivity 中,取消备注 onSaveInstanceState() 方法,运行应用,点击纸杯蛋糕,然后旋转应用或设备。请注意,这一次,在 activity 旋转后保留了甜点数据。

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() 将应用的状态保存到 bundle 中。

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。
  • 重新启动了设备。

开始学习下一课:ViewModel 和 ViewModelFactory

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