使用 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 的 Saved State 模块,可在进程终止后保留 NonConfig 状态。

表 1:各种 Fragment 破坏性操作及其对不同状态类型的影响。

我们来看一个具体示例。假设有一个屏幕,它会生成一个随机字符串,将其显示在 TextView 中,并提供在发送给朋友之前编辑字符串的选项。

random text generator app that demonstrates various
            types of state
图 1. 演示各种状态类型的随机文本生成器应用。

在此示例中,假设用户按下编辑按钮后,应用会显示一个 EditText 视图,用户可以在其中编辑消息。如果用户点击 CANCEL,则 EditText 视图应清空,并且其可见性应设置为 View.GONE。这样的屏幕可能需要管理四项数据,以确保无缝体验。

数据 类型 状态类型 说明
seed Long NonConfig 用于随机生成新善行的种子。在 ViewModel 创建时生成。
randomGoodDeed 字符串 SavedState + 变量 在 Fragment 首次创建时生成。randomGoodDeed 会被保存,以确保用户在进程终止并重新创建后仍能看到相同的随机善行。
isEditing Boolean SavedState + 变量 当用户开始编辑时,布尔值标志设置为 trueisEditing 会被保存,以确保在 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 所述,视图通过所有不移除 Fragment 或销毁宿主的操作来保存和恢复其 ViewState

SavedState

您的 Fragment 负责管理少量动态状态,这些状态是 Fragment 功能的组成部分。您可以使用 Fragment.onSaveInstanceState(Bundle) 保留易于序列化的数据。与 Activity.onSaveInstanceState(Bundle) 类似,您放入 Bundle 中的数据会通过配置更改和进程终止与重新创建而保留,并且在 Fragment 的 onCreate(Bundle)onCreateView(LayoutInflater, ViewGroup, Bundle)onViewCreated(View, Bundle) 方法中可用。

继续前面的示例,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) 中恢复状态,请从 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。向您的 ViewModel 添加 SavedState 模块可使 ViewModel 在进程终止和重新创建后保留简单状态。

其他资源

如需了解有关管理 Fragment 状态的更多信息,请参阅以下其他资源。

Codelabs

指南