保存与 Fragment 相关的状态

各种 Android 系统操作都会影响 Fragment 的状态。为了确保用户的状态得到保存,Android 框架会自动保存和恢复 Fragment 和返回堆栈。因此,您需要确保 Fragment 中的所有数据也得到保存和恢复。

下表列出了会导致 Fragment 丢失状态的操作,以及各种类型的状态是否会在发生这些更改后持续存在。表中提及的状态类型如下:

  • 变量:Fragment 中的局部变量。
  • 视图状态:Fragment 中一个或多个视图所拥有的任何数据。
  • SavedState:此 Fragment 实例固有的数据,这些数据应保存在 onSaveInstanceState() 中。
  • NonConfig:从外部来源(如服务器或本地代码库)提取的数据,或提交后发送到服务器的用户创建的数据。

通常,将变量视为与 SavedState 相同,但下表对这两者进行了区分,以说明各种操作对每种状态类型所产生的影响。

操作 变量 视图状态 SavedState NonConfig
添加到返回堆栈 x
配置更改 x
进程终止/重新创建 x ✓*
被移除,不添加到返回堆栈 x x x x
宿主已完成 x x x x

* 可以使用 ViewModel 的已保存状态模块在进程终止后保留 NonConfig 状态。

表 1:各种 Fragment 破坏性操作以及它们对不同状态类型所产生的影响。

我们来看一个具体的示例。假设有一个屏幕,它会生成一个随机字符串,将其显示在 TextView 中,并提供了一个选项,让用户在将该字符串发送给好友之前对其进行修改:

随机文本生成器应用,演示了各种类型的状态
图 1. 随机文本生成器应用,演示了各种类型的状态。

在本例中,假设用户按修改按钮后,应用会显示 EditText 视图,用户可以从中修改消息。如果用户点击取消,应清除 EditText 视图,并将其可见性设为 View.GONE。这样的屏幕可能需要管理四块数据,以确保提供无缝的体验:

数据 类型 状态类型 说明
seed Long NonConfig 用于随机生成新善行的种子。创建 ViewModel 时生成。
randomGoodDeed String SavedState + 变量 首次创建 Fragment 时生成。系统会保存 randomGoodDeed,以确保即使在进程终止和重新创建后,用户也会看到相同的随机善行。
isEditing Boolean SavedState + 变量 当用户开始修改时,布尔标记设为 true。系统会保存 isEditing,以确保重新创建 Fragment 后,屏幕的修改部分仍然可见。
修改后的文本 Editable 视图状态(由 EditText 拥有) EditText 视图中修改后的文本。此文本由“EditText”保存,以确保用户正在进行的修改不会丢失。

表 2:随机文本生成器应用必须管理的状态。

下面几部分介绍了如何正确管理数据在破坏性操作之后的状态。

视图状态

视图负责管理自己的状态。例如,当某个视图接受用户输入时,该视图负责保存和恢复该输入以处理配置更改。Android 框架提供的所有视图都有自己的 onSaveInstanceState()onRestoreInstanceState() 实现,因此您不必管理 Fragment 中的视图状态。

例如,在前面的场景中,修改后的字符串保存在 EditText 中。EditText 知道它当前显示的文本的值以及其他详细信息,如任何选定文本的开头和结尾。

视图需要一个 ID 才能保留其状态。此 ID 在 Fragment 及其视图层次结构中必须是唯一的。没有 ID 的视图无法保留其状态。

<EditText
    android:id="@+id/good_deed_edit_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

如表 1 所述,视图会保存其 ViewState 并在不移除 Fragment 或销毁宿主的所有操作之后恢复该状态。

SavedState

Fragment 负责管理它正常运行所不可或缺的少量动态状态。您可以使用 Fragment.onSaveInstanceState(Bundle) 保留轻松序列化的数据。与 Activity.onSaveInstanceState(Bundle) 类似,您放置在捆绑包中的数据会在配置更改以及进程终止和重新创建后保留,并且在 Fragment 的 onCreate(Bundle)onCreateView(LayoutInflater, ViewGroup, Bundle)onViewCreated(View, Bundle) 方法中可用。

提示:使用 ViewModel 时,您可以使用 SavedStateHandle 直接在 ViewModel 中保存状态。如需了解详情,请参阅 ViewModel 的已保存状态模块

接着前面的示例来讲,randomGoodDeed 是向用户显示的善行,isEditing 是一个用于确定 Fragment 显示还是隐藏 EditText 的标记。这种已保存状态应使用 onSaveInstanceState(Bundle) 来持久保留,如以下示例所示:

Kotlin

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.putBoolean(IS_EDITING_KEY, isEditing)
    outState.putString(RANDOM_GOOD_DEED_KEY, randomGoodDeed)
}

Java

@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putBoolean(IS_EDITING_KEY, isEditing);
    outState.putString(RANDOM_GOOD_DEED_KEY, randomGoodDeed);
}

如需恢复 onCreate(Bundle) 中的状态,请从捆绑包中检索存储的值:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    isEditing = savedInstanceState?.getBoolean(IS_EDITING_KEY, false)
    randomGoodDeed = savedInstanceState?.getString(RANDOM_GOOD_DEED_KEY)
            ?: viewModel.generateRandomGoodDeed()
}

Java

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState != null) {
        isEditing = savedInstanceState.getBoolean(IS_EDITING_KEY, false);
        randomGoodDeed = savedInstanceState.getString(RANDOM_GOOD_DEED_KEY);
    } else {
        randomGoodDeed = viewModel.generateRandomGoodDeed();
    }
}

如表 1 所述,请注意,将 Fragment 放置在返回堆栈上时,会保留变量。将它们视为已保存状态可确保它们在所有破坏性操作之后持续存在。

NonConfig

应将 NonConfig 数据放置在 Fragment 之外,如在 ViewModel 中。在前面的示例中,seed(NonConfig 状态)是在 ViewModel 中生成的。维护其状态的逻辑由 ViewModel 拥有。

Kotlin

public class RandomGoodDeedViewModel : ViewModel() {
    private val seed = ... // Generate the seed

    private fun generateRandomGoodDeed(): String {
        val goodDeed = ... // Generate a random good deed using the seed
        return goodDeed
    }
}

Java

public class RandomGoodDeedViewModel extends ViewModel {
    private Long seed = ... // Generate the seed

    private String generateRandomGoodDeed() {
        String goodDeed = ... // Generate a random good deed using the seed
        return goodDeed;
    }
}

ViewModel 类本来就能让数据在发生屏幕旋转等配置更改后继续留存,并且在将 Fragment 放置在返回堆栈上时会保留在内存中。进程终止和重新创建后,会重新创建 ViewModel,并生成新的 seed。将 SavedState 模块添加到 ViewModel 可让 ViewModel 在进程终止和重新创建后保留简单的状态。

其他资源

如需详细了解如何管理 Fragment 状态,请参阅下面列出的其他资源。

Codelab

指南