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

2023-09-11 13:41:54

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类,代码如下所示:

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

4. Compose提供的MapSaver和ListSaver

4.1 mapServer

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

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部分皆可,结果都是一样的,只是使用的方式不同,建议读者根据情况选用。

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

更多推荐

4.1.9-映射应用程序体系结构

映射应用程序体系结构IDWSTG-INFO-10总结为了有效地测试应用程序,并能够就如何解决所识别的任何问题提供有意义的建议,了解实际测试的内容非常重要。此外,确定是否应将特定组件视为超出测试范围可能会有所帮助。现代Web应用程序的复杂性差异很大,从在单个服务器上运行的简单脚本到分布在数十个不同系统、语言和组件的高度复

华为云云耀云服务器L实例评测-基于华为云服务器的测试及简单配置

引言云计算已经成为现代企业和个人的重要组成部分。在云计算市场上,华为云一直以来都以其出色的性能和服务质量而闻名。周末的时候,利用华为云云耀云服务器搭建了一个基于hexo的个人博客,我用的是2核2G的3M带宽的配置,访问起来挺丝滑的,记录一下本次对华为云的一些测试及看法,探讨其性能、可靠性以及适用场景,帮助您更好地了解这

【python数据分析基础】—pandas中loc()与iloc()的介绍与区别

文章目录前言一、loc[]函数二、iloc[]函数三、详细用法loc方法iloc方法总结共同点不同点前言我们经常在寻找数据的某行或者某列的时常用到Pandas中的两种方法iloc和loc,两种方法都接收两个参数,第一个参数是行的范围,第二个参数是列的范围。一、loc[]函数loc:接收的是行、列的名称或标签。在切片是按

【送书活动】网络安全(黑客)自学

前言「作者主页」:雪碧有白泡泡「个人网站」:雪碧的个人网站「推荐专栏」:★java一站式服务★★React从入门到精通★★前端炫酷代码分享★★从0到英雄,vue成神之路★★uniapp-从构建到提升★★从0到英雄,vue成神之路★★解决算法,一个专栏就够了★★架构咱们从0说★★数据流通的精妙之道★★后端进阶之路★文章目

以太网媒体接口MII/RMII/SMII/GMII/RGMII/SGMII

以太网媒体接口MII/RMII/SMII/GMII/RGMII/SGMIIGMAC系统框架(EMAC是百兆mac,GMAC是千兆mac)网卡网卡系统框架结构PHY(PhysicalLayer,物理层)MAC(MediaAccessControl、媒体访问控制器)以太网结构大框架MAC硬件框图MII(MediaIndep

使用cpolar配合Plex搭建私人媒体站

文章目录1.前言2.Plex网站搭建2.1Plex下载和安装2.2Plex网页测试2.3cpolar的安装和注册3.本地网页发布3.1Cpolar云端设置3.2Cpolar本地设置4.公网访问测试5.结语1.前言用手机或者平板电脑看视频,已经算是生活中稀松平常的场景了,特别是各种碎片时间(追剧下饭、地铁上刷剧等等),看

设计模式:单例模式

目录什么是单例模式为什么使用单例模式常见的单例写法1.懒汉式(LazyInitialization)2.双重检查锁定(Double-CheckedLocking)3.饿汉式(EagerInitialization)4.枚举实现单例总结什么是单例模式单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供全局访问点来

Docker Compose

DockerCompose是一个用于定义和运行多容器Docker应用程序的工具。它使用一个YAML文件来配置应用程序的服务、网络和存储等方面,并使用简单的命令来管理应用程序的生命周期。使用DockerCompose,你可以将多个容器(即服务)组合在一起,形成一个完整的应用程序。每个服务都可以由一个或多个容器组成,并且可

Redis分布式锁及其常见问题解决方案

Redis是一种内存中的数据结构存储系统,它可以用作数据库、缓存和消息代理。由于其高性能和灵活的数据结构,Redis被广泛应用在各种场景中,包括实现分布式锁。分布式锁是一种在分布式系统中实现互斥访问的技术。在许多实际应用场景中,我们需要确保某些操作在同一时间只能被一个节点执行,例如更新共享资源、处理任务队列等。这时,我

【线性代数】沉浸式线性代数在线学习网站

地址:http://immersivemath.com/ila/index.html这是全球第一本带交互式图形的线性代数教材,作者是J.Ström,K.Åström,andT.Akenine-Möller。全书一共十章,各章节内容如下:接下来我将对各章节进行简单的总结,另外请注意,阅读过程中请一定不要忘记各章节提供的的

好物周刊#7:炫酷的浏览器标签页

村雨遥的好物周刊,记录每周看到的有价值的信息,主要针对计算机领域,每周五发布。一、项目1.Qexo一个快速、强大、漂亮的在线Hexo编辑器,支持以下功能:自定义图床上传图片在线配置编辑在线页面管理开放API自动检查更新在线一键更新快速接入友情链接简单的说说短文类似不算子的统计自动填文章模板2.Twikoo一个简洁、安全

热文推荐