在之前的文章中,我们提到了remember,我们都知道remember可以缓存创建状态,避免因为重组而丢失。使用remember缓存的状态虽然可以跨越重组,但是不能跨Activity或者跨进程。比如横竖屏切换等ConfigiurationChanged事件发生时,假设没有重写对应的onConfigurationChanged函数,Activity就会被销毁重建,导致remember保存的状态丢失。为了解决这个问题,Compose提供了rememberSavable,它可以像Activity的onSaveInstanceState那样在进程被杀死时自动保存状态,同时像onRestoreInstanceState一样随进程的重建而恢复。
rememberSavable中的数据会随着onSaveInstanceState进行保存,并在进程或者是Activity重建时根据key恢复到对应的Composable中,这个key就是Composable在编译期间被确定的唯一标识。只有当用户手动退出的时候,rememberSavable中的数据才会被情况。
rememberSavable的实现原理实际上就是将数据以Bundle的形式保存,所以凡是Bundle支持的基本数据类型都是可以自动保存的。如果我们保存的是一个对象,那么需要变成一个Parcelable对象,比如我们要保存一个Person类,代码如下所示:
@Parcelize
data class Person(
val name: String,
val age: String
) : Parcelable
当为一个Parcelable接口的派生类添加@Parcelable时,Kotlin编译器会自动为其添加Parcelable的实现,使用@Parcelable注解时,需要添加kotlin-parcelize插件,在模块的build.gradle文件中对应地方添加如下的代码
plugins{
id 'kotlin-parcelize'
}
测试的代码如下所示:
class ComposeCounterAct : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyComposeTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
GetPerson()
}
}
}
}
@Composable
fun GetPerson() {
val person = rememberSaveable(key = "zxj") {
mutableStateOf(Person("walt", 29))
}
Column {
ShowText(person.value)
Button(modifier = Modifier.wrapContentWidth().height(40.dp), onClick = {
person.value = (Person("zhong", 30))
}) {
Text("修改数据!!!")
}
}
}
@Composable
fun ShowText(person: Person) {
Text(text = "name: ${person.name}===>${person.age}")
}
}
@Parcelize
data class Person(
var name: String,
var age: Int
) : Parcelable
在上面的代码中我们使用了rememberSaveable
来保存了我们的Person
对象,刚开始的时候给个初始值,主要是为了演示rememberSavable
的功能,我们设置了一个按钮,点击按钮后person对
象的对应属性会被修改,这时候如果我们让当前的activity
被系统销毁重建,则会发现我们修改后的值不会被重置成默认的值,而如果使用remember
的话,值会被重置。那么如何模拟当前的页面被系统回收呢?这里提供两个方法,一是不重写Activity的onConfigurationChanged(newConfig: Configuration)
方法的情况下,旋转你的手机,这时Activity
会被销毁重建。第二个方法是:在开发这选项中打开不保留后台活动的开关,然后退出应用再进入。以荣耀手机为例:
建议读者可以自己动手验证下,这里需要注意的是保留的数据能恢复只是在当前页面被系统回收的情况下会自动恢复,而用户手动退出,异常皆不会恢复之前的数据
#3. 自定义Saver
上一节我们说到,保存数据的类必须是一个Parcelable类,但是有的数据结构可能无法添加Parcelable接口,比如一些定义在三方类库中的类。对于这些类,我们可以使用自定义Saver为其实现保存和恢复的逻辑。只需要我们在调用rememberSavable时传入此Saver就行了,代码如下:
class ComposeCounterAct : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyComposeTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
GetPerson()
}
}
}
}
@Composable
fun GetPerson() {
var person = rememberSaveable(stateSaver = PersonSaver) {
mutableStateOf(Person("walt",29))
}
Column {
ShowText(person.value)
Button(modifier = Modifier.wrapContentWidth().height(40.dp), onClick = {
person.value = (Person("zhong", 30))
}) {
Text("修改数据!!!")
}
}
}
@Composable
fun ShowText(person: Person) {
Text(text = "name: ${person.name}===>${person.age}")
}
}
object PersonSaver:Saver<Person,Bundle>{
override fun restore(value: Bundle): Person? {
return value.getString("name")?.let {
name->
value.getInt("age").let { age->
Person(name,age)
}
}
}
override fun SaverScope.save(value: Person): Bundle? {
return Bundle().apply {
putString("name",value.name)
putInt("age",value.age)
}
}
}
data class Person(
var name: String,
var age: Int
)
如上面的代码所示,我们去掉了Person的Parcelable实现,然后使用自定义的Saver完成状态的持久化和恢复,结果和上一节的相同。
MapSaver将对象转换为Map
代码如下:
class ComposeCounterAct : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyComposeTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
GetPerson()
}
}
}
}
@Composable
fun GetPerson() {
var person = rememberSaveable(stateSaver = personSaver) {
mutableStateOf(Person("walt",29))
}
Column {
ShowText(person.value)
Button(modifier = Modifier.wrapContentWidth().height(40.dp), onClick = {
person.value = (Person("zhong", 30))
}) {
Text("修改数据!!!")
}
}
}
@Composable
fun ShowText(person: Person) {
Text(text = "name: ${person.name}===>${person.age}")
}
}
val personSaver = run {
val nameKey = "name"
val ageKey = "age"
mapSaver(
save = { mapOf(nameKey to it.name,ageKey to it.age) },
restore = {Person(it[nameKey] as String,it[ageKey] as Int)}
)
}
data class Person(
var name: String,
var age: Int
)
ListSaver是将对象转换为List的数据结构进行保存。将下面的代码替换上面的saver部分皆可,结果都是一样的,只是使用的方式不同,建议读者根据情况选用。
val personListSaver = listSaver<Person,Any>(
save = { listOf(it.name,it.age) },
restore = {Person(it[0] as String,it[1] as Int)}
)
注意: 假设只是需要状态跨越Configurationchagned而不需要跨进程恢复,那么我们可以在Androidmanifest.xml中设置android:configChanges,然后使用普通的remember即可。因为Compose能够在所有ConfigurationChanged发生时做出响应。但是理论上纯Compose项目是不需要因为ConfigurationChange重建Activity的,这里希望读者正确区分