一、前言
虽然ViewModel已经帮我们处理了屏幕旋转导致的页面销毁重建导致的数据保留问题,但是对于由于内存不足等原因导致得页面销毁重建并没有做到数据保存。本篇主要根据官方文档和示例进行一个统一的完善和整理,辅以代码示例进行演示。
二、添加依赖
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.4.0"
三、SavedStateHandle
在ViewModel中主要是使用SavedStateHandle进行数据保存,这里对这个使用进行下简单的演示。
定义如下:
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
class MainFragment : Fragment() {
val vm: SavedStateViewModel by viewModels()
...
}
保存和取值如下:
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
}
}
通过使用 SavedStateHandle
,查询值会在进程终止后保留下来,从而确保用户在重新创建前后看到同一组过滤后的数据,而无需 Activity 或 Fragment 手动保存、恢复该值并将其重新转给 ViewModel
。
此外,SavedStateHandle
还包含其他您与键值对映射互动时可能会用到的方法:
contains(String key)
- 检查是否存在给定键的值。remove(String key)
- 移除给定键的值。keys()
- 返回SavedStateHandle
中包含的所有键。
四、支持类型
默认情况下,您可以对 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 类
如果某个类未实现 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
封装了创建该临时文件的逻辑。
为确保临时文件在 Activity 的进程终止随后又恢复后不会丢失,TempFileViewModel
可以使用 SavedStateHandle
保留其数据。如需允许 TempFileViewModel
保存其数据,请实现 SavedStateProvider
,并在 ViewModel
的 SavedStateHandle
上将其设置为提供程序:
如需在用户返回时恢复 File
数据,请从 SavedStateHandle
中检索 temp_file
Bundle
。这正是 saveTempFile()
提供的包含绝对路径的 Bundle
。该绝对路径随后可用于实例化新的 File
。
其参考代码如下:
//保存状态
//异常保存状态
private fun File.saveTempFile() = bundleOf("path" to absolutePath)
class SaveStateViewModel(private val state: SavedStateHandle): ViewModel() {
private var tempFile: File? = null
init {
state.setSavedStateProvider("temp_file") { // saveState()
tempFile?.saveTempFile() ?: Bundle()
}
}
fun createOrGetTempFile(): File {
return tempFile ?: File.createTempFile("temp", null).also {
tempFile = it
}
}
}
六、AbstractSavedStateViewModelFactory
前例看到可以使用viewModels()
构建ViewModel,而且无需我们传入相应的SavedStateHandle实例,但是这种构造函数也不能传递多余的参数,因为我们在使用Factory的方式创建新的ViewModel实例的时候需要采用新的方式,示例如下:
class DetailViewModel(
private val githubApi: GithubApi,
private val handle: SavedStateHandle
) : ViewModel() {
fun loadData() {
val id = handle["id"] ?: "default"
viewModelScope.launch {
val response = githubApi.getCommit(id)
// Handle response
}
}
}
class DetailViewModelFactory(
private val githubApi: GithubApi,
owner: SavedStateRegistryOwner,
defaultArgs: Bundle? = null
) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
override fun <T : ViewModel> create(
key: String,
modelClass: Class<T>,
handle: SavedStateHandle
): T {
return DetailViewModel(githubApi, handle) as T
}
}
使用如下:
class DetailActivity : AppCompatActivity() {
private val githubApi = GithubApi()
private val viewModel by viewModels { DetailViewModelFactory(githubApi, this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Use viewModel here
viewModel.loadData()
}
}
因为ComponentActivity已经实现了SavedStateRegistryOwner接口,所以这里直接传入this既可。