ViewModel 的 Saved State 模块 是 Android Jetpack 的一部分。
如 保存 UI 状态 中所述,ViewModel
对象可以处理配置更改,因此您无需担心旋转或其他情况下的状态。但是,如果您需要处理系统触发的进程终止,则可能需要使用 SavedStateHandle
API 作为备份。
UI 状态通常存储在 ViewModel
对象中或在其中引用,而不是活动中,因此使用 onSaveInstanceState()
或 rememberSaveable
需要一些样板代码,saved state 模块 可以为您处理这些代码。
使用此模块时,ViewModel
对象通过其构造函数接收 SavedStateHandle
对象。此对象是一个键值映射,允许您写入和检索保存状态中的对象。这些值在系统终止进程后仍然存在,并且可以通过相同的对象获得。
保存的状态与您的任务栈相关联。如果您的任务栈消失,您的保存状态也会消失。这可能发生在强制停止应用、从最近使用的应用菜单中删除应用或重新启动设备时。在这种情况下,任务栈会消失,您无法恢复保存状态中的信息。在 用户触发的 UI 状态关闭 场景中,不会恢复保存的状态。在 系统触发的 场景中,会恢复保存的状态。
设置
从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
工厂会将适当的 SavedStateHandle
提供给您的 ViewModel
。
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
,查询值在进程死亡后得以保留,确保用户在重新创建前后看到相同的一组过滤数据,而无需活动或片段手动保存、恢复并将该值转发回ViewModel
。
SavedStateHandle
还具有与键值映射交互时可能期望的其他方法
contains(String key)
- 检查给定键是否存在值。remove(String key)
- 删除给定键的值。keys()
- 返回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
之间进行互操作,以便您可以通过使用自定义 Saver
的rememberSaveable
保存的任何 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
中的数据与活动或片段的其余savedInstanceState
一起作为Bundle
保存和恢复。
直接支持的类型
默认情况下,您可以对 SavedStateHandle
调用 set()
和 get()
,以获取与 Bundle
相同的数据类型,如下所示
类型/类支持 | 数组支持 |
double |
double[] |
int |
int[] |
long |
long[] |
字符串 |
String[] |
byte |
byte[] |
char |
char[] |
CharSequence |
CharSequence[] |
float |
float[] |
Parcelable |
Parcelable[] |
Serializable |
Serializable[] |
short |
short[] |
SparseArray |
|
Binder |
|
Bundle |
|
ArrayList |
|
Size(仅在 API 21+ 中) |
|
SizeF(仅在 API 21+ 中) |
如果类没有扩展上述列表中的任何一个,请考虑通过添加@Parcelize
Kotlin 注释或直接实现Parcelable
来使类可打包。
保存不可打包的类
如果某个类未实现 Parcelable
或 Serializable
,并且无法修改为实现其中一个接口,则无法将该类的实例直接保存到 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; } }
为了确保如果活动的进程被终止并随后恢复,临时文件不会丢失,TempFileViewModel
可以使用SavedStateHandle
来持久化其数据。为了允许TempFileViewModel
保存其数据,请实现SavedStateProvider
并将其设置为ViewModel
的SavedStateHandle
上的提供程序
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
为您推荐
- 注意:当 JavaScript 关闭时,将显示链接文本
- 保存 UI 状态
- 使用可观察数据对象
- 创建具有依赖项的 ViewModel