activity 生命周期的阶段

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

1. 准备工作

在此 Codelab 中,您将了解 Android 的基本组成部分:activity 生命周期。

activity 在生命过程中会转换多种状态,并且有时会恢复之前的状态。这种状态转换称为 activity 生命周期。

在 Android 中,activity 是与用户交互的入口点。

过去,一个 activity 在一个应用中仅显示一个屏幕。根据当前的最佳实践,一个 activity 可以根据需要在不同屏幕间切换,从而显示多个屏幕。

activity 生命周期从 activity 创建一直持续到 activity 销毁。在销毁时,系统将收回 activity 的资源。当用户进入和退出 activity 时,每个 activity 都会在 activity 生命周期的不同状态之间转换。

作为 Android 开发者,您需要了解 activity 生命周期。如果您的 activity 无法正确响应生命周期状态变化,您的应用可能会生成奇怪的 bug、出现让您的用户感到困惑的行为,或使用过多的 Android 系统资源。了解 Android 生命周期并正确响应生命周期状态变化是 Android 开发的重要环节。

前提条件

  • 了解 activity 是什么,以及如何在应用中创建 activity
  • 了解 activity 的 onCreate() 方法的用途,以及该方法中执行的操作的类型

学习内容

  • 如何将日志记录信息输出到 Logcat
  • Activity 生命周期的基础知识,以及当 activity 在不同状态之间切换时调用的回调
  • 如何替换生命周期回调方法,以便在 activity 生命周期的不同时间执行操作

构建内容

  • 修改名为 Dessert Clicker 的起始应用,以添加在 Logcat 中显示的日志记录信息。
  • 替换生命周期回调方法并记录对 activity 状态的更改。
  • 运行应用,并记下 activity 启动、停止和恢复时显示的日志记录信息。
  • 实现 rememberSaveable 以保留在设备配置发生变更时可能丢失的应用数据。

2. 应用概览

在此 Codelab 中,您将使用一款名为 Dessert Clicker 的起始应用。在 Dessert Clicker 中,每当用户点按屏幕上的甜点时,该应用就会为用户“购买”相应甜点。应用会在布局中更新以下值:

  • 所购甜点的数量
  • “已购买”甜品的总收入

4a826cc7e8ef62.png

该应用存在多个与 Android 生命周期相关的 bug。例如,在某些情况下,该应用会将甜点数量值重置为 0。了解 Android 生命周期可以帮助您理解这些问题发生的原因以及解决方法。

下载起始代码

  • 在 Android Studio 中,打开 basic-android-kotlin-compose-training-dessert-clicker 文件夹。

在 Android Studio 中,打开 Dessert Clicker 应用代码。

3.探索生命周期方法并添加基本日志记录

每个 activity 都具有生命周期。这个词源于植物和动物的生命周期,就像蝴蝶的生命周期:蝴蝶的不同状态显示它从卵、幼虫、蛹到蝴蝶再到死亡的成长过程。

2e74068b69dab83f.png

同样,activity 生命周期由 activity 可能会经历的不同状态组成:从 activity 首次初始化一直到销毁,在销毁时,操作系统 (OS) 将收回其内存。通常,程序的入口点是 main() 方法。不过,Android activity 从 onCreate() 方法开始;此方法等同于上述示例中的蝴蝶卵阶段。您在本课程中已经使用过许多 activity,或许已经认识 onCreate() 方法。当用户启动您的应用、在 activity 之间导航、在应用内外部导航时,activity 会切换状态。

下图显示了所有 activity 生命周期状态。顾名思义,这些状态表示 activity 所处的状态。请注意,与蝴蝶生命周期不同,activity 在其整个生命周期内可以在不同状态之间来回切换,而不是仅向一个方向移动。

ca808edb1c95f07a.png

在 activity 生命周期状态发生变化时,您通常需要更改某些行为,或者运行一些代码。因此,Activity 类本身以及 Activity 的任何子类(例如 ComponentActivity)都会实现一组生命周期回调方法。Android 会在 activity 从一种状态切换为另一种状态时调用这些回调,而您可以在自己的 activity 中替换这些方法,通过执行任务来响应这些生命周期状态变化。下图显示了生命周期状态以及可用的可替换回调。

468988518c270b38.png

请务必了解 Android 何时调用可替换回调以及每个回调方法中应执行什么操作,但这两个图都很复杂并且可能会让人感到困惑。在此 Codelab 中,您不只要了解每种状态和回调的含义,还要进行一些检测工作并逐渐理解 Android activity 生命周期。

第 1 步:检查 onCreate() 方法并添加日志记录

为了弄清 Android 生命周期的一些情况,了解何时调用各种生命周期方法会很有帮助。这些信息有助于您发现 Dessert Clicker 应用中的问题。

确定这些信息的一种简单方法是使用 Android 日志记录功能。通过日志记录功能,您可以在应用运行期间向控制台写入简短的消息,并利用它们来显示不同回调的触发时间。

  1. 运行 Dessert Clicker 应用,并在某款甜点的图片上点按多次。请注意 Desserts sold 的值与美元总金额发生的变化。
  2. 打开 MainActivity.kt 并检查此 activity 的 onCreate() 方法:
override fun onCreate(savedInstanceState: Bundle?) {
    // ...
}

在 activity 生命周期图中,您可能认出了 onCreate() 方法,因为您之前使用过此回调。这便是每个 activity 都必须实现的方法。您可使用 onCreate() 方法为 activity 执行所有一次性初始化。例如,在 onCreate() 中,调用 setContent() 以指定 activity 的界面布局。

e3bc14c112d5363b.png

系统会在 activity 初始化(操作系统在内存中创建新的 Activity 对象)之后立即调用一次 onCreate() 生命周期方法。执行 onCreate() 后,相应 activity 会被视为已创建

  1. MainActivity.kt 的顶层(类声明 class MainActivity 的上方)添加以下常量。

一种比较好的做法是在文件中声明 TAG 常量,因为该值不会更改。

若要将其标记为编译时常量,请在声明变量时使用 const。编译时常量是在编译期间已知的值。

private const val TAG = "MainActivity"
  1. onCreate() 方法中,在对 super.onCreate() 的调用后,紧接着添加下面一行代码:
Log.d(TAG, "onCreate Called")
  1. 根据需要导入 Log 类(按 Alt+Enter,或者在 Mac 上按 Option+Enter,然后选择 Import)。如果您启用了自动导入功能,系统应该会自动执行此操作。
import android.util.Log

Log 类会将消息写入 LogcatLogcat 是用于记录消息的控制台。来自 Android 的关于您应用的消息会出现在这里,包括您使用 Log.d() 方法或其他 Log 类方法显式发送到日志的消息。

Log 指令有三个重要方面:

  • 日志消息的优先级,即消息的重要性。在本示例中,Log.v() 会记录详细消息。Log.d() 方法会写入调试消息。Log 类中的其他方法包括 Log.i()(表示信息性消息)、Log.w()(表示警告)和Log.e()(表示错误消息)。
  • 日志 tag(第一个参数),在本示例中为 "MainActivity"。该标签是一个字符串,可让您更轻松地在 Logcat 中找到自己的日志消息。该标签通常是类的名称。
  • 实际的日志消息称为 msg(第二个参数),它是一个简短的字符串,在本示例中为 "onCreate Called"

3ed7e1a51149ef64.png

  1. 编译并运行 Dessert Clicker 应用。当您点按甜点后,应用行为没有出现任何变化。在 Android Studio 中,点击屏幕底部的 Logcat 标签页。

79d83e283488ce7.png

  1. Logcat 窗口的搜索字段中输入 MainActivity

cce04503ce108254.png

Logcat 可能包含很多消息,大多数消息对您而言都无用。您可以通过多种方式过滤 Logcat 条目,但搜索是最简单的方式。由于您在代码中使用了 MainActivity 作为日志标签,因此您可以使用该标签来过滤日志。在搜索字词的开头添加 D/ 可仅过滤出由 Log.d() 创建的调试消息。您的日志消息包含日期、时间、软件包名称 (com.example.dessertclicker)、您的日志标签(开头为 D/)以及实际消息。由于此消息出现在日志中,所以您会知道 onCreate() 已执行。

您还可以使用搜索栏旁边的下拉菜单按级别过滤日志。您可以从下拉菜单中选择 Debug,然后输入相同的查询字符串以进行相同的搜索。

f27eb3f13aba6617.png

第 2 步:实现 onStart() 方法

系统会在调用 onCreate() 之后立即调用 onStart() 生命周期方法。onStart() 运行后,您的 activity 会显示在屏幕上。与为初始化 activity 而仅调用一次的 onCreate() 不同,onStart() 可在 activity 的生命周期内由系统多次调用。

a357d2291de472d9.png

请注意,onStart() 需要与相应的 onStop() 生命周期方法配对使用。如果用户启动您的应用后又返回设备的主屏幕,相应 activity 会停止,并且不会再在屏幕上显示。

  1. 在 Android Studio 中,打开 MainActivity.kt 并将光标移到 MainActivity 类中,依次选择 Code > Override Methods,或按 Control+O。此时,系统会显示一个对话框,其中包含您可以在此类中替换的所有方法的长列表。

44bc297e84d5a2c6.png

  1. 开始输入 onStart 以搜索正确的方法。如需滚动到下一个匹配项,请使用向下箭。从列表中选择 onStart(),然后点击 OK 插入样板替换代码。代码如以下示例所示:
override fun onStart() {
    super.onStart()
}
  1. onStart() 方法中,添加一条日志消息:
override fun onStart() {
    super.onStart()
    Log.d(TAG, "onStart Called")
}
  1. 编译并运行 Dessert Clicker 应用,然后打开 Logcat 窗格。
  2. 在搜索字段中输入 MainActivity,以过滤日志。请注意,onCreate()onStart() 方法会依次调用,并且您的 activity 会显示在屏幕上。
  3. 按设备上的主屏幕按钮,然后使用“最近使用的应用”屏幕返回相应 activity。请注意,activity 会从上次停止的位置恢复,同时使用所有相同的值,并且 onStart() 会再次记录到 Logcat 中。另请注意,系统不会再次调用 onCreate() 方法。
2022-07-12 08:11:29.216 16346-16346/com.example.dessertclicker D/MainActivity: onCreate Called
2022-07-12 08:11:29.344 16346-16346/com.example.dessertclicker D/MainActivity: onStart Called
2022-07-12 08:11:42.585 16346-16346/com.example.dessertclicker D/MainActivity: onStart Called

第 3 步:添加更多日志语句

在此步骤中,您将为所有其他生命周期方法实现日志记录功能。

  1. 替换 MainActivity 中其余的生命周期方法,然后为每种方法添加日志语句,如以下代码所示:
override fun onResume() {
    super.onResume()
    Log.d(TAG, "onResume Called")
}

override fun onRestart() {
    super.onRestart()
    Log.d(TAG, "onRestart Called")
}

override fun onPause() {
    super.onPause()
    Log.d(TAG, "onPause Called")
}

override fun onStop() {
    super.onStop()
    Log.d(TAG, "onStop Called")
}

override fun onDestroy() {
    super.onDestroy()
    Log.d(TAG, "onDestroy Called")
}
  1. 再次编译并运行 Dessert Clicker,然后检查 Logcat。

请注意,除了 onCreate()onStart() 之外,还有一条有关 onResume() 生命周期回调的日志消息。

2022-07-12 08:16:13.356 16421-16421/com.example.dessertclicker D/MainActivity: onCreate Called
2022-07-12 08:16:13.424 16421-16421/com.example.dessertclicker D/MainActivity: onStart Called
2022-07-12 08:16:13.426 16421-16421/com.example.dessertclicker D/MainActivity: onResume Called

当 activity 从头开始启动时,您会看到系统按顺序调用以下三个生命周期回调:

  • onCreate(),在系统创建应用时调用。
  • onStart(),使应用显示在屏幕上,但用户无法与应用进行交互。
  • onResume(),将应用置于前台,用户现在可以与应用交互。

onResume() 方法尽管名称是这样,但会在启动时调用,即使没有要恢复的 activity 也是如此。

2678d691f608762a.png

4. 探索生命周期用例

现在,您已经完成了 Dessert Clicker 应用的日志记录设置,接下来可以开始使用应用并探索如何触发生命周期回调了。

用例 1:打开和关闭 activity

您可以先从最基本的用例入手,也就是首次启动您的应用,然后关闭应用。

  1. 编译并运行 Dessert Clicker 应用(如果该应用尚未运行)。正如您所看到的,当 activity 首次启动时,系统会调用 onCreate()onStart()onResume() 回调。
2022-07-12 08:30:20.171 16656-16656/com.example.dessertclicker D/MainActivity: onCreate Called
2022-07-12 08:30:20.236 16656-16656/com.example.dessertclicker D/MainActivity: onStart Called
2022-07-12 08:30:20.239 16656-16656/com.example.dessertclicker D/MainActivity: onResume Called
  1. 多次点按纸杯蛋糕。
  2. 点按设备上的返回按钮。

请注意,在 Logcat 中,系统将按上述顺序调用 onPause()onStop()

2022-07-12 08:31:18.729 16656-16656/com.example.dessertclicker D/MainActivity: onPause Called
2022-07-12 08:31:19.301 16656-16656/com.example.dessertclicker D/MainActivity: onStop Called

在本示例中,使用返回按钮会导致 activity(和应用)从屏幕中移除并移至 activity 堆栈的后侧。

如果您的代码手动调用 activity 的 finish() 方法,或者用户强制退出应用,Android OS 可能会关闭该 activity。例如,用户可以在“最近使用的应用”屏幕中强制退出或关闭应用。如果应用长时间没有在屏幕上显示,操作系统也可能会自行关闭您的 activity。Android 这样做是为了延长电池续航时间,并让其他应用能够使用您的应用的资源。以上只是举例说明 Android 系统销毁 activity 的原因。在其他一些情况下,Android 系统会在不发出警告的情况下销毁您的 activity。

用例 2:离开和返回 activity

现在,您已经启动了应用并将其关闭,在此过程中,您看到了 activity 首次创建时经历的大部分生命周期状态。此外,您还看到了 activity 关闭时所经历的大多数生命周期状态。但当用户与其 Android 设备交互时,他们需要在应用之间切换、返回主屏幕、启动新应用以及处理由通话等其他 activity 导致的中断。

每次用户离开您的 activity 时,它都不会关闭:

  • 当 activity 不再在屏幕上可见时,就说明该 activity 已置于后台。反之,activity 位于前台或屏幕上。
  • 当用户返回您的应用时,相应 activity 会重启并再次可见。这部分生命周期称为应用的可见生命周期。

当您的应用位于后台时,为了保留系统资源和电池续航时间,应用通常不应活跃运行。您可以使用 Activity 生命周期及其回调来了解应用何时切换到后台,以便您暂停任何正在进行的操作。然后,在您的应用进入前台时重启相应操作。

在这一步中,您将查看当应用进入后台以及返回前台时的 activity 生命周期。

  1. 在 Dessert Clicker 应用运行时,点击几次纸杯蛋糕。
  2. 按设备上的主屏幕按钮,然后在 Android Studio 中观察 Logcat。返回主屏幕的操作会将您的应用置于后台,而不是完全关闭应用。请注意,系统会调用 onPause()onStop() 方法。
2022-07-12 11:49:48.593 18350-18350/com.example.dessertclicker D/MainActivity: onPause Called
2022-07-12 11:49:49.191 18350-18350/com.example.dessertclicker D/MainActivity: onStop Called

在调用 onPause() 后,该应用不会再获得焦点。在 onStop() 之后,该应用将不再显示在屏幕上。虽然该 activity 已停止,但 Activity 对象仍位于后台内存中。Android OS 尚未销毁 activity。用户可能会返回该应用,因此 Android 会保留您的 activity 资源。

c470ee28ab7f8a1a.png

  1. 使用“最近使用的应用”屏幕返回该应用。在模拟器上,您可以通过下图中所示的方形系统按钮访问“最近使用的应用”屏幕。

请注意,在 Logcat 中,activity 使用 onRestart()onStart() 重启,然后使用 onResume() 恢复。

1ce2c5a03ad43e14.png

2022-07-12 11:50:54.787 18350-18350/com.example.dessertclicker D/MainActivity: onRestart Called
2022-07-12 11:50:54.789 18350-18350/com.example.dessertclicker D/MainActivity: onStart Called
2022-07-12 11:50:54.791 18350-18350/com.example.dessertclicker D/MainActivity: onResume Called

当该 activity 返回前台时,系统不会再次调用 onCreate() 方法。相应 activity 对象未被销毁,因此不需要重新创建。系统会调用 onRestart() 方法,而不是 onCreate()。请注意,这一次该 activity 返回前台时,系统会保留 Desserts sold 数值。

  1. 除 Dessert Clicker 以外,至少启动一个应用,这样设备的“最近使用的应用”屏幕中就会有一些应用。
  2. 启动“最近使用的应用”屏幕,打开另外一个近期 activity。然后,返回最近用过的应用并让 Dessert Clicker 返回前台。

请注意,您在 Logcat 这里看到的回调与按主屏幕按钮后看到的相同。当应用进入后台时,系统会调用 onPause()onStop(),并在应用返回时调用 onRestart()onStart()onResume()

当应用停止并进入后台,或应用重启并返回前台时,系统会调用这些方法。在这些情况下,如果您需要在应用中执行一些工作,则可以替换相关的生命周期回调方法。

用例 3:隐藏部分 activity

您已了解,在应用启动并且系统调用 onStart() 后,该应用将在屏幕上变得可见。当系统调用 onResume() 时,应用会获得用户焦点,即用户可以与应用交互。应用在屏幕上完全显示并且具有用户焦点的这部分生命周期称为前台生命周期

当应用进入后台后,焦点在 onPause() 后便会丢失,并且在 onStop() 后,该应用将不再可见。

区分焦点与可见性之间的差异很重要。某个 activity 有可能在屏幕上部分可见,但没有用户焦点。在这一步,您将查看 activity 处于部分可见状态,但没有用户焦点的情况。

  1. 在 Dessert Clicker 应用运行时,点击屏幕右上角的共享按钮。

共享 activity 出现在屏幕的下半部分,但相应 activity 在上半部分仍然可见。

206854c62b507676.pngd895bab6022f40cd.png

  1. 检查 Logcat,您会注意到,系统仅调用了 onPause()
2022-07-12 12:14:10.864 18350-18350/com.example.dessertclicker D/MainActivity: onPause Called

在此用例中,系统没有调用 onStop(),因为相应 activity 仍然部分可见。但是,该 activity 没有用户焦点,并且用户无法与之交互;位于前台的“共享”activity 具有用户焦点。

为什么这种区别至关重要?单纯的 onPause() 导致的中断通常仅持续很短的时间,然后用户就会返回您的 activity,或者导航到另一个 activity 或应用。通常,您需要持续更新界面,使应用的其余部分不会卡顿。

onPause() 中运行的任何代码都会阻止其他内容显示,因此请使 onPause() 中的代码保持轻量级。例如,当有来电时,onPause() 中的代码可能会延迟来电通知。

  1. 在共享对话框之外点击一下,返回应用,您会注意到系统调用了 onResume()

onResume()onPause() 都必须处理焦点。当相应 activity 获得焦点时,系统会调用 onResume() 方法;当该 activity 失去焦点时,系统会调用 onPause()

5. 探索配置变更

activity 生命周期管理中的另一个用例也很重要:配置变更会如何影响 activity 生命周期。

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

演示的最后一个生命周期回调是 onDestroy(),它会在 onStop() 之后调用。系统会在销毁 activity 之前调用此方法。当应用的代码调用 finish(),或者因配置变更而需要销毁并重新创建 activity 时,可能会发生这种情况。

配置变更会导致系统调用 onDestroy()

屏幕旋转是导致 activity 关闭并重启的一种配置变更类型。如需模拟此配置变更并检查其影响,请完成以下步骤:

  1. 编译并运行您的应用。
  2. 确保已停用模拟器中的屏幕旋转锁定功能。
  3. 将设备或模拟器旋转为横屏模式。您可以使用旋转按钮向左或向右旋转模拟器。
  4. 检查 Logcat,注意当 activity 关闭时,它会依次调用 onPause()onStop()onDestroy()
2022-07-16 20:03:31.147 30587-30587/com.example.dessertclicker D/MainActivity: onPause Called
2022-07-16 20:03:31.155 30587-30587/com.example.dessertclicker D/MainActivity: onStop Called
2022-07-16 20:03:31.184 30587-30587/com.example.dessertclicker D/MainActivity: onDestroy Called

设备旋转时出现数据丢失情况

  1. 编译并运行您的应用,然后打开 Logcat。
  2. 点击几次纸杯蛋糕,请注意,所售甜点的数量和总收入并不为零。
  3. 确保已停用模拟器中的屏幕旋转锁定功能。
  4. 将设备或模拟器旋转为横屏模式。您可以使用旋转按钮向左或向右旋转模拟器。

8a5b471d2b2359a8.png

  1. 检查 Logcat 中的输出。用 MainActivity 过滤输出。
2022-07-12 12:31:55.098 19326-19326/com.example.dessertclicker D/MainActivity: onCreate Called
2022-07-12 12:31:55.152 19326-19326/com.example.dessertclicker D/MainActivity: onStart Called
2022-07-12 12:31:55.154 19326-19326/com.example.dessertclicker D/MainActivity: onResume Called
2022-07-12 12:31:59.600 19326-19326/com.example.dessertclicker D/MainActivity: onPause Called
2022-07-12 12:31:59.608 19326-19326/com.example.dessertclicker D/MainActivity: onStop Called
2022-07-12 12:31:59.644 19326-19326/com.example.dessertclicker D/MainActivity: onDestroy Called
2022-07-12 12:31:59.668 19326-19326/com.example.dessertclicker D/MainActivity: onCreate Called
2022-07-12 12:31:59.678 19326-19326/com.example.dessertclicker D/MainActivity: onStart Called
2022-07-12 12:31:59.679 19326-19326/com.example.dessertclicker D/MainActivity: onResume Called

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

当设备旋转,而相应 activity 被关闭并重新创建时,该 activity 会使用默认值重新启动 - 甜点图片、已售甜点的数量和总收入会重置为零。

如需了解这些值被重置的原因以及如何纠正这些值,您需要了解可组合项的生命周期,以及可组合项如何知晓要观察并保留其状态。

可组合项的生命周期

应用的界面最初是通过在名称为 Composition 的进程中运行可组合函数而构建的。

当应用的状态发生更改时,系统会安排重组。重组是指 Compose 重新执行状态可能已发生变化的可组合函数,并创建更新后的界面。系统会更新 Composition 以反映这些更改。

创建或更新 Composition 的唯一方法是通过其初始组合和后续重组。

可组合函数有自己的生命周期,并且其生命周期独立于 activity 生命周期。其生命周期由多个事件组成:进入 Composition,重组 0 次或多次,然后退出 Composition。

为了让 Compose 能够跟踪和触发重组,它需要知晓状态何时发生了更改。如需向 Compose 指示它应当跟踪某个对象的状态,该对象的类型必须为 StateMutableStateState 类型不可变,仅允许读取。MutableState 类型是可变的,并且可允许读取和写入。

在之前的 Codelab 中,您已在 Lemonade 应用Tip Time 应用中看到和使用过 MutableState

如需创建可变变量 revenue,请使用 mutableStateOf 声明该变量。0 是它的初始默认值。

var revenue = mutableStateOf(0)

尽管这足以让 Compose 在收入值发生更改时触发重组,但保留更新后的值是不够的。每次重新执行可组合项时,该收入值都会重新初始化为初始默认值 0

如需指示 Compose 在重组期间保留并重复使用其值,您需要使用 remember API 进行声明。

var revenue by remember { mutableStateOf(0) }

如果 revenue 的值发生更改,Compose 会安排读取此值的所有可组合函数进行重组。

尽管 Compose 会在重组期间记住收入状态,但在配置变更期间不会保留此状态。为了让 Compose 在配置更改期间保留状态,您必须使用 rememberSaveable

如需了解其他实践和信息,请参阅“简介:Compose 中的状态”Codelab。

在配置变量期间使用 rememberSaveable 保存值

当 Android OS 销毁并重新创建 activity 时,您可以使用 rememberSaveable 函数保存所需的值。

如需在重组期间保存值,您需要使用 remember。在重组和配置更改期间使用 rememberSaveable 保存值。

使用 rememberSaveable 保存值可确保该值在 activity 恢复时可用(如果需要的话)。

  1. MainActivity 中,将当前使用 remember 的五个变量更新为 rememberSaveable
var revenue by remember { mutableStateOf(0) }
...
var currentDessertImageId by remember {
    mutableStateOf(desserts[currentDessertIndex].imageId)
}
var revenue by rememberSaveable { mutableStateOf(0) }
...
var currentDessertImageId by rememberSaveable {
    mutableStateOf(desserts[currentDessertIndex].imageId)
}
  1. 编译并运行您的应用。
  2. 点击几次纸杯蛋糕,请注意,所售甜点的数量和总收入并不为零。
  3. 将设备或模拟器旋转为横屏模式。
  4. 请注意,在销毁并重新创建 activity 后,甜点图片、已售甜点和总收入将恢复为之前的值。

6. 解决方案代码

7. 总结

activity 生命周期

  • activity 生命周期是 activity 会转换的一组状态。activity 生命周期从 Android OS 首次创建 activity 时开始,在 Android OS 销毁 activity 时结束。
  • 当用户在 activity 之间以及应用内外导航时,每个 activity 会在 activity 生命周期中的状态之间切换。
  • activity 生命周期中的每种状态都有一个对应的回调方法,您可以在 Activity 类中替换此类方法。核心的生命周期方法集合包括:onCreate()onRestart()onStart()onResume()onPause()onStop()onDestroy()
  • 如需添加要在 activity 转换为某种生命周期状态时发生的行为,请替换相应状态的回调方法。
  • 如需在 Android Studio 中为类添加框架替换方法,请依次选择 Code > Override Methods,或按 Control+O

使用日志进行记录

  • 借助 Android 日志记录 API(尤其是 Log 类),您可以写入要在 Android Studio 内的 Logcat 中显示的简短消息。
  • 使用 Log.d() 可写入调试消息。此方法采用两个参数:日志标签(通常是类的名称)和日志消息(一个简短的字符串)。
  • 使用 Android Studio 中的 Logcat 窗口可查看系统日志,包括您写入的消息。

配置变更

  • 当设备的状态发生了根本性改变,以至于系统解决改变的最简单方式就是销毁并重建 activity 时,就会发生配置变更。
  • 最常见的配置变更示例是用户将设备从竖屏模式旋转为横屏模式,或者从横屏模式旋转为竖屏模式。当设备语言发生变化或用户插入硬件键盘时,也可能会发生配置更改。
  • 当发生配置变更时,Android 会调用所有 activity 生命周期的关闭回调。然后,Android 会从头开始重启相应 activity,同时运行所有生命周期启动回调。
  • 当 Android 因配置变更而关闭某个应用时,它会使用 onCreate() 重启 activity。
  • 如需保存需要在配置更改后继续存在的值,请使用 rememberSaveable 声明其变量。

了解详情