ViewModel 的已保存状态模块   Android Jetpack 的一部分。

保存界面状态这篇文章提到过,ViewModel 对象可以处理配置更改,因此您无需担心旋转时或其他情况下的状态。但是,如果需要处理系统发起的进程终止,则可以使用 SavedStateHandle API 作为备用方式。

界面状态通常会在 ViewModel 对象(而非 activity)中存储或引用,因此,使用 onSaveInstanceState()rememberSaveable 时需要已保存的状态模块可以为您处理的某个样板文件。

使用此模块时,ViewModel 对象会通过其构造函数接收 SavedStateHandle 对象。此对象是一个键值对映射,用于向已保存状态写入对象以及从其中检索对象。这些值会在进程被系统终止后继续保留,并通过同一对象保持可用状态。

保存的状态与您的任务堆栈相关联。如果任务堆栈消失,保存的状态也会消失。强制停止应用、从“最近用过”菜单中移除应用或重新启动设备时,可能会发生这种情况。在这种情况下,任务堆栈会消失,并且您无法恢复保存的状态中的信息。在用户发起的界面状态解除情景中,不会恢复保存的状态。在系统发起的界面状态解除情景中,则会恢复。

设置

Fragment 1.2.0 或其传递依赖项 Activity 1.1.0 开始,您可以接受 SavedStateHandle 作为 ViewModel 的构造函数参数。

Kotlin

class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }

Java

public class SavedStateViewModel extends ViewModel {     private SavedStateHandle state;      public SavedStateViewModel(SavedStateHandle savedStateHandle) {         state = savedStateHandle;     }      ... }

然后,您可以检索 ViewModel 的实例,而无需任何其他配置。默认 ViewModel 工厂会向您的 ViewModel 提供相应的 SavedStateHandle

Kotlin

class MainFragment : Fragment() {     val vm: SavedStateViewModel by viewModels()      ... }

Java

class MainFragment extends Fragment {     private SavedStateViewModel vm;      public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {         vm = new ViewModelProvider(this).get(SavedStateViewModel.class);          ...       }      ... }

提供自定义 ViewModelProvider.Factory 实例时,您可以通过扩展 AbstractSavedStateViewModelFactory 启用 SavedStateHandle

使用 SavedStateHandle

SavedStateHandle 类是一个键值对映射,用于通过 set()get() 方法向已保存的状态写入数据以及从中检索数据。

通过使用 SavedStateHandle,查询值会在进程终止后保留下来,从而确保用户在重新创建前后看到同一组过滤后的数据,而无需 activity 或 fragment 手动保存、恢复该值并将其重新转给 ViewModel

此外,SavedStateHandle 还包含其他您与键值对映射互动时可能会用到的方法:

此外,您还可以使用可观察数据存储器从 SavedStateHandle 检索值。支持的类型包括:

LiveData

使用 getLiveData() 从封装在 LiveData 可观察对象中的 SavedStateHandle 检索值。当键的值更新时,LiveData 会收到新值。通常,该值是因用户互动而设置的,例如输入查询以过滤一系列数据。然后,您可以使用这个更新后的值转换 LiveData

Kotlin

class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {     val filteredData: LiveData<List<String>> =         savedStateHandle.getLiveData<String>("query").switchMap { query ->         repository.getFilteredData(query)     }      fun setQuery(query: String) {         savedStateHandle["query"] = query     } }

Java

public class SavedStateViewModel extends ViewModel {     private SavedStateHandle savedStateHandle;     public LiveData<List<String>> filteredData;     public SavedStateViewModel(SavedStateHandle savedStateHandle) {         this.savedStateHandle = savedStateHandle;         LiveData<String> queryLiveData = savedStateHandle.getLiveData("query");         filteredData = Transformations.switchMap(queryLiveData, query -> {             return repository.getFilteredData(query);         });     }      public void setQuery(String query) {         savedStateHandle.set("query", query);     } }

StateFlow

使用 getStateFlow() 从封装在 StateFlow 可观察对象中的 SavedStateHandle 检索值。当您更新该键的值时,StateFlow 会收到新值。通常,您可能会因用户互动而设置该值,例如输入查询来过滤数据列表。然后,您可以使用其他 Flow 运算符转换此更新的值。

Kotlin

class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {     val filteredData: StateFlow<List<String>> =         savedStateHandle.getStateFlow<String>("query")             .flatMapLatest { query ->                 repository.getFilteredData(query)             }      fun setQuery(query: String) {         savedStateHandle["query"] = query     } }

实验性 Compose 的状态支持

lifecycle-viewmodel-compose 工件提供了实验性的 saveable API,从而能够在 SavedStateHandle 和 Compose 的 Saver 之间实现互操作性,以便您能够通过 rememberSaveable 使用自定义 Saver 保存的任何 State 也可以通过 SavedStateHandle 保存。

Kotlin

class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {      var filteredData: List<String> by savedStateHandle.saveable {         mutableStateOf(emptyList())     }      fun setQuery(query: String) {         withMutableSnapshot {             filteredData += query         }     } }

支持的类型

保留在 SavedStateHandle 中的数据将作为 Bundle 与 activity 或 fragment 的 savedInstanceState 的其余部分一起保存和恢复。

直接支持的类型

默认情况下,您可以对 SavedStateHandle 调用 set()get(),以处理与 Bundle 相同的数据类型,如下所示。

类型/类支持 数组支持
double double[]
int int[]
long long[]
String String[]
byte byte[]
char char[]
CharSequence CharSequence[]
float float[]
Parcelable Parcelable[]
Serializable Serializable[]
short short[]
SparseArray
Binder
Bundle
ArrayList
Size (only in API 21+)
SizeF (only in API 21+)

如果该类没有扩展上述列表中的任何一项,则应考虑通过添加 @Parcelize Kotlin 注解或直接实现 Parcelable 来使该类变为 Parcelable 类。

保存非 Parcelable 类

如果某个类未实现 ParcelableSerializable 且不能修改为实现这些接口之一,则无法直接将该类的实例保存到 SavedStateHandle 中。

Lifecycle 2.3.0-alpha03 开始,SavedStateHandle 允许您保存任何对象,具体方法是:使用 setSavedStateProvider() 方法提供您自己的逻辑用于将对象作为 Bundle 来保存和恢复。SavedStateRegistry.SavedStateProvider 是一个接口,用于定义单个 saveState() 方法来返回包含您希望保存的状态的 Bundle。当 SavedStateHandle 准备好保存其状态后,它会调用 saveState() 以从 SavedStateProvider 检索 Bundle,并为关联的键保存 Bundle

设想一个示例应用,该应用通过 ACTION_IMAGE_CAPTURE Intent 向相机应用请求图片,并传递一个临时文件,以供相机存储图片。TempFileViewModel 封装了创建该临时文件的逻辑。

Kotlin

class TempFileViewModel : ViewModel() {     private var tempFile: File? = null      fun createOrGetTempFile(): File {         return tempFile ?: File.createTempFile("temp", null).also {             tempFile = it         }     } }

Java

class TempFileViewModel extends ViewModel {     private File tempFile = null;      public TempFileViewModel() {     }       @NonNull     public File createOrGetTempFile() {         if (tempFile == null) {             tempFile = File.createTempFile("temp", null);         }         return tempFile;     } }

为确保临时文件在 activity 的进程终止随后又恢复后不会丢失,TempFileViewModel 可以使用 SavedStateHandle 保留其数据。如需允许 TempFileViewModel 保存其数据,请实现 SavedStateProvider,并在 ViewModelSavedStateHandle 上将其设置为提供程序:

Kotlin

private fun File.saveTempFile() = bundleOf("path", absolutePath)  class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {     private var tempFile: File? = null     init {         savedStateHandle.setSavedStateProvider("temp_file") { // saveState()             if (tempFile != null) {                 tempFile.saveTempFile()             } else {                 Bundle()             }         }     }      fun createOrGetTempFile(): File {         return tempFile ?: File.createTempFile("temp", null).also {             tempFile = it         }     } }

Java

class TempFileViewModel extends ViewModel {     private File tempFile = null;      public TempFileViewModel(SavedStateHandle savedStateHandle) {         savedStateHandle.setSavedStateProvider("temp_file",             new TempFileSavedStateProvider());     }     @NonNull     public File createOrGetTempFile() {         if (tempFile == null) {             tempFile = File.createTempFile("temp", null);         }         return tempFile;     }      private class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider {         @NonNull         @Override         public Bundle saveState() {             Bundle bundle = new Bundle();             if (tempFile != null) {                 bundle.putString("path", tempFile.getAbsolutePath());             }             return bundle;         }     } }

如需在用户返回时恢复 File 数据,请从 SavedStateHandle 中检索 temp_file Bundle。这正是 saveTempFile() 提供的包含绝对路径的 Bundle。该绝对路径随后可用于实例化新的 File

Kotlin

private fun File.saveTempFile() = bundleOf("path", absolutePath)  private fun Bundle.restoreTempFile() = if (containsKey("path")) {     File(getString("path")) } else {     null }  class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {     private var tempFile: File? = null     init {         val tempFileBundle = savedStateHandle.get<Bundle>("temp_file")         if (tempFileBundle != null) {             tempFile = tempFileBundle.restoreTempFile()         }         savedStateHandle.setSavedStateProvider("temp_file") { // saveState()             if (tempFile != null) {                 tempFile.saveTempFile()             } else {                 Bundle()             }         }     }      fun createOrGetTempFile(): File {       return tempFile ?: File.createTempFile("temp", null).also {           tempFile = it       }     } }

Java

class TempFileViewModel extends ViewModel {     private File tempFile = null;      public TempFileViewModel(SavedStateHandle savedStateHandle) {         Bundle tempFileBundle = savedStateHandle.get("temp_file");         if (tempFileBundle != null) {             tempFile = TempFileSavedStateProvider.restoreTempFile(tempFileBundle);         }         savedStateHandle.setSavedStateProvider("temp_file", new TempFileSavedStateProvider());     }      @NonNull     public File createOrGetTempFile() {         if (tempFile == null) {             tempFile = File.createTempFile("temp", null);         }         return tempFile;     }      private class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider {         @NonNull         @Override         public Bundle saveState() {             Bundle bundle = new Bundle();             if (tempFile != null) {                 bundle.putString("path", tempFile.getAbsolutePath());             }             return bundle;         }          @Nullable         private static File restoreTempFile(Bundle bundle) {             if (bundle.containsKey("path") {                 return File(bundle.getString("path"));             }             return null;         }     } }

测试中的 SavedStateHandle

如需测试将 SavedStateHandle 作为依赖项的 ViewModel,请使用所需的测试值创建一个 SavedStateHandle 新实例,并将其传递给要测试的 ViewModel 实例。

Kotlin

class MyViewModelTest {      private lateinit var viewModel: MyViewModel      @Before     fun setup() {         val savedState = SavedStateHandle(mapOf("someIdArg" to testId))         viewModel = MyViewModel(savedState = savedState)     } }

其他资源

如需详细了解 ViewModel 的已保存状态模块,请参阅以下资源。

Codelab