Android Jetpack Compose之状态持久化与恢复

1.概述

在之前的文章中,我们提到了remember,我们都知道remember可以缓存创建状态,避免因为重组而丢失。使用remember缓存的状态虽然可以跨越重组,但是不能跨Activity或者跨进程。比如横竖屏切换等ConfigiurationChanged事件发生时,假设没有重写对应的onConfigurationChanged函数,Activity就会被销毁重建,导致remember保存的状态丢失。为了解决这个问题,Compose提供了rememberSavable,它可以像Activity的onSaveInstanceState那样在进程被杀死时自动保存状态,同时像onRestoreInstanceState一样随进程的重建而恢复。

2.实例解析

rememberSavable中的数据会随着onSaveInstanceState进行保存,并在进程或者是Activity重建时根据key恢复到对应的Composable中,这个key就是Composable在编译期间被确定的唯一标识。只有当用户手动退出的时候,rememberSavable中的数据才会被情况。

rememberSavable的实现原理实际上就是将数据以Bundle的形式保存,所以凡是Bundle支持的基本数据类型都是可以自动保存的。如果我们保存的是一个对象,那么需要变成一个Parcelable对象,比如我们要保存一个Person类,代码如下所示:

kotlin 复制代码
@Parcelize
data class Person(
    val name: String,
    val age: String
) : Parcelable

当为一个Parcelable接口的派生类添加@Parcelable时,Kotlin编译器会自动为其添加Parcelable的实现,使用@Parcelable注解时,需要添加kotlin-parcelize插件,在模块的build.gradle文件中对应地方添加如下的代码

kotlin 复制代码
plugins{
	id 'kotlin-parcelize'
}

测试的代码如下所示:

kotlin 复制代码
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就行了,代码如下:

kotlin 复制代码
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完成状态的持久化和恢复,结果和上一节的相同。

4. Compose提供的MapSaver和ListSaver

4.1 mapServer

MapSaver将对象转换为Map<String,Any>的结构进行保存,注意value是可保存到Bundle的类型。 代码如下:

kotlin 复制代码
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
)

4.2 ListSaver

ListSaver是将对象转换为List的数据结构进行保存。将下面的代码替换上面的saver部分皆可,结果都是一样的,只是使用的方式不同,建议读者根据情况选用。

kotlin 复制代码
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的,这里希望读者正确区分

相关推荐
hai_android8 小时前
LiveEventBusCore 跨 Activity 通讯源码解析
android·kotlin·android jetpack
我命由我123453 天前
Android 广播 - 组件通信
android·java·开发语言·android studio·android jetpack·android-studio·android runtime
alexhilton4 天前
为什么你的 LazyColumn 仍然卡顿
android·kotlin·android jetpack
hai_android4 天前
Android 嵌套滑动冲突:内部拦截 vs 外部拦截实战
android·android studio·android jetpack
alexhilton8 天前
模块化Jetpack Compose架构
android·kotlin·android jetpack
hai_android8 天前
InputStage 责任链核心执行流程
android·android studio·android jetpack
传奇开心果编程9 天前
【现代声明式UI学与练】第4课 列表渲染与 key——如何高效渲染列表、key 的作用、列表重排时的状态保持
学习·flutter·react native·ui·swiftui·android jetpack
传奇开心果编程10 天前
【现代声明式UI学与练】第7课 组件通信——父传子、子传父、跨层级通信、状态提升与 Context
学习·flutter·react native·ui·swiftui·android jetpack
传奇开心果编程10 天前
【现代声明式UI学与练】第9课 性能优化——渲染优化、列表优化、内存优化、启动优化
学习·flutter·react native·ui·性能优化·swiftui·android jetpack
天空之城--12 天前
Android一周动态:Android 18首次官宣、Compose Material3 1.4转正(5趋势+5资讯)
android·人工智能·flutter·架构·android jetpack