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

news2024/11/16 13:23:34

目录

  • 1.概述
  • 2.实例解析
  • 4. Compose提供的MapSaver和ListSaver
    • 4.1 mapServer
    • 4.2 ListSaver

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1002436.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软件测试之功能测试

一、测试项目启动与研读需求文档 &#xff08;一&#xff09; 组建测试团队 1.测试团队中的角色 2.测试团队的基本责任 尽早地发现软件程序、系统或产品中所有的问题。 督促和协助开发人员尽快地解决程序中的缺陷。 帮助项目管理人员制定合理的开发和测试计划。 对缺陷进行跟…

芯科蓝牙BG27开发笔记3-修改第一个程序

提问&#xff1a; 如何实现连续发送通知消息&#xff1f; 蓝牙无线射频信号在时间轴不是连续不断地存在&#xff0c;为了实现大数量的传输&#xff0c;需要额外的机制保证设备可以在下一次启动射频后可以接着发送之前没有发完的消息&#xff0c;nordic是可以使用队列&#xf…

springboot jpa手动事务

创建springboot项目 搭建最简单的SpringBoot项目_Steven-Russell的博客-CSDN博客 引入jpa和数据据依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>…

yolov8 模型部署--TensorRT部署-c++服务化部署

写目录 yolov8 模型部署--TensorRT部署1、模型导出为onnx格式2、模型onnx格式转engine 部署 yolov8 模型部署–TensorRT部署 1、模型导出为onnx格式 如果要用TensorRT部署YOLOv8&#xff0c;需要先使用下面的命令将模型导出为onnx格式&#xff1a; yolo export modelyolov8n.p…

二维前缀和

导言 当我们需要求到某个矩阵的子矩阵的和时,就可以使用二维前缀和 这是一个矩阵, 就是左上角区域的所有数之和 ...... 如果要 求中间的子矩阵的和,(x,y)为左上角 ...... ...... ,(i,j)为右下角,那么只需要算 - - ------这一…

QT第五天

void Widget::on_show_clicked() { QString sql "select * from myTable" ; QSqlQuery querry; if(!querry.exec(sql)) { QMessageBox::information(this,"失败","展示失败"); return; } //此时&…

如何用 DAP 仿真器下载程序

1.仿真器简介 本书配套的仿真器为 Fire-Debugger&#xff0c;遵循 ARM 公司的 CMSIS-DAP 标准&#xff0c;支持所有基于 Cortex-M内核的单片机&#xff0c;常见的 M3、M4 和 M7 都可以完美支持。 Fire-Debugger 支持下载和在线仿真程序&#xff0c;支持XP/WIN7/WIN8/WIN10 这…

c++ day 4

1、仿照string类&#xff0c;完成myString 类 #include <iostream> #include<cstring>using namespace std;class myString { private:char *str; //记录c风格的字符串int size; //记录字符串的实际长度 public://无参构造myString():size(10…

JVM优化(OOM,内存溢出),查看线程快照,堆内存情况等问题

1&#xff1a;堆大小 新生代 老年代&#xff0c;新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ) 2&#xff1a;-Xmn参数总是应当小于-Xmx参数&#xff0c;否则就会触发OOM错误 3&#xff1a;jvm优化与查看gc回收情况&#x…

数据通信网络之OSPFv3基础

文章及资源归档至【AIShareLab】&#xff0c;回复 通信系统与网络 可获取。 文章目录 一、目的二、拓扑三、需求四、步骤 一、目的 掌握路由器的IPv6 基础配置。掌握OSPFv3&#xff08;单区域&#xff09;的基础配置。 二、拓扑 如图1 所示&#xff0c;三台路由器R1、R2 和R…

网络传输方式

1. 单播 1.1. 定义 单播是指一种向单个目标地址传送数据的方式&#xff0c;即单独的一对一通讯方式。 1.2. 可使用协议 UDP、TCP等协议 1.3. 常见的场景 发送电子邮件传输文件 2. 广播 2.1. 定义 一种向本地网络中所有设备发送数据的方式。 2.2. 常见的场景 电视和电…

仅做笔记用:Stable Diffusion 通过 ControlNet 扩展图片 / 扩图

发觉之前的 Outpainting 脚本效果仍旧不是很理想。这里又找了一下有没有效果更好的途径来扩图。于是就找到了通过 ControlNet 的方式来实现效果更好的扩图。这里临时记录一下在 Stable Diffusion 怎么使用 ControlNet 来扩展图片。 下载 control_v11p_sd15_inpaint_fp16.safet…

Sql注入详解(原理篇)

一、简介 SQL 注入漏洞非常复杂&#xff0c;区分各种数据库类型&#xff0c;提交方法&#xff0c;数据类型等注入&#xff0c;同样此类漏洞是WEB安全中严重的安全漏洞&#xff0c;学习如何利用&#xff0c;挖掘&#xff0c;修复也是很重要的 二、SQL注入原理 1、什么是SQL注…

嵌入式学习之链表

对于链表&#xff0c;要重点掌握链表和数组区别和实现&#xff0c;链表静态添加和动态遍历&#xff0c;链表中pointpoint-next,链表节点个数的查找&#xff0c;以及链表从指定节点后方插入新节点的知识。

Scrum敏捷开发流程及敏捷研发关键环节

Scrum是一个迭代式增量软件开发过程&#xff0c;是敏捷方法论中的重要框架之一。它通常用于敏捷软件开发&#xff0c;包括了一系列实践和预定义角色的过程骨架。Scrum中的主要角色包括Scrum主管&#xff08;Scrum Master&#xff09;、产品负责人&#xff08;Product Owner&…

Mybatis -- 读取 DATE 类型字段时可能遇到的问题(夏令时问题)

在使用 MYBATIS 读取数据库字段的时候&#xff0c;我们一般需要为查询字段指定数据类型。特别是当我们使用 mybatis generator 去生成对应的接口代码时&#xff0c;会自动按照数据库字段类型生成响应映射规则的代码。   如下&#xff0c;左侧是 date 类型生成的字段映射规则&…

如何在Excel中创建VBA程序--基于Office 365版本

目录 一 VBA程序简介二 为何选择VBA程序1. 高效便捷2. 自定义程度高3. 兼容性好 三 如何创建VBA程序1. 插入VBA程序&#xff1a;2. 使用VBA程序&#xff1a; 四 VBA程序应用场景示例1. 数据处理与分析2. 自定义函数3. 数据验证 五 VBA程序的优缺点及未来发展优点缺点 了解如何在…

计算机网络第四章——网络层(下)

长相思兮长相忆&#xff0c;短相思兮无穷极 文章目录 RIP和OSPE在于使用的算法是不一样的&#xff0c;RIP使用的是距离向量&#xff0c;适用于比较小的网络&#xff0c;OSPE使用的是链路状态&#xff0c;适用于比较大的网络 每一个路由器都有一个路由表&#xff0c;路由表的表…

Kerberos 身份验证

简介 Kerberos 是一种由 MIT&#xff08;麻省理工大学&#xff09;提出的一种基于加密 Ticket 的身份认证协议。它旨在通过使用密钥加密技术为客户端/服务器应用程序提供强身份验证&#xff0c;用于验证用户或主机的标识。。 适用范围&#xff1a;Windows Server 2022、Window…

Go基础八股

【Go面试】Go slice深拷贝和浅拷贝_哔哩哔哩_bilibili 基础篇 1.Go方法值接收者和指针接收者的区别 简单言之就是是否会影响调用的结构体&#xff0c;方法接收者是指针会影响 2.返回局部变量的指针 一般来说&#xff0c;局部变量会在函数返回后被销毁&#xff0c;因此被返回…