Jetpack Compose 状态管理的三个误区

1. 注意在协程中更新状态值时的线程安全问题

例如我们有如下代码:

data class MyUiState(val counter: Int = 0,val text: String = ""
)class MainViewModel: ViewModel() {private val _state = MutableStateFlow(MyUiState())val state = _state.asStateFlow()fun incrementCount() {_state.value = state.value.copy(counter = state.value.counter + 1)}
}

这样写可能看不出什么问题,但是进一步的你可能会这样写:

fun incrementCount() {viewModelScope.launch {_state.value = state.value.copy(counter = state.value.counter + 1)}
}

虽然viewModelScope默认的协程上下文是 SupervisorJob + Dispatchers.Main.immediate,但是一旦你这样写了,就可能会继续这样调用:

fun incrementCount() {viewModelScope.launch {withContext(Dispatchers.IO) {_state.value = state.value.copy(counter = state.value.counter + 1)}}viewModelScope.launch {withContext(Dispatchers.IO) {_state.value = state.value.copy(counter = state.value.counter + 1)}}
} 

现在就存在潜在的线程并发安全问题了。假设两个线程同时进入更新代码块时,count都是0,它们各自会尝试将其+1,但是最终的结果不是2,而是1(无论哪个线程先结束,另一个也只会将其改成1,因为每个线程看到的本地副本的初始值都是0)。

这其实是一个习惯问题。

解决方法是使用 state.update { } 来更新:

fun incrementCount() {_state.update {it.copy(counter = state.value.counter + 1)}
}
fun incrementCount1() {viewModelScope.launch {_state.update {it.copy(counter = state.value.counter + 1)}}viewModelScope.launch {_state.update {it.copy(counter = state.value.counter + 1)}}
}

这样,不管你的代码是否会在协程中更新都会得到安全保证,我们假设以后官方将viewModelScope的调度器更改了,即便不是主线程也不会产生问题。

2. 注意应用因为低内存被系统杀死后状态恢复问题

如果应用回到后台,随时有可能因为内存不足而被系统杀死,这时用户从最近应用程序列表中返回时,会发现应用界面从头开始,上次看到的状态丢失。

例如前面的代码,假设用户调用了incrementCount()count 值增加之后,回到后台去做其他事情,此时应用因为内存不足被系统杀死,用户再次返回应用,应用会重启,用户看到的将会是初始值 0。

解决这个问题可以使用 Saved State APIs ,包括 rememberSaveable (Jetpack Compose) 和 onSaveInstanceState (View system),以及ViewModel中的SavedStateHandle 等等。

下面是一个使用 SavedStateHandle 的简单示例:

@Parcelize
data class MyUiState(val counter: Int = 0,val text: String = ""
): Parcelableclass MainViewModel(private val savedStateHandle: SavedStateHandle
): ViewModel() {val state = savedStateHandle.getStateFlow("state", MyUiState())fun incrementCount() {viewModelScope.launch {savedStateHandle["state"] = savedStateHandle.get<MyUiState>("state")?.copy(counter = state.value.counter + 1)}}
}class MyActivity: ComponentActivity() {val viewModel: MainViewModel by viewModels()...
}

注意:如果 ViewModel 构造函数只依赖于 SavedStateHandle ,您无需为其提供工厂函数,viewModels() 函数将会正确为其初始化。

当然这取决于业务场景,如果你的业务没有这样的需求,那么应用重启之后回到初始状态的情况可能就是一种正常的不错的选择。但是作为开发者我们应该有这样的意识,一旦用户有这样的需求,我们就可以立即修改代码来达到需求。

如果你想了解更多关于状态恢复相关的内容以及 Saved State APIs 的使用,可以参考我之前整理的下面文章:

  • Jetpack架构组件库:Lifecycle、LiveData、ViewModel 中的 ViewModel 部分

  • Jetpack Compose 中的架构思想 中的状态持久化与恢复部分

3.不要在单例类中保存全局状态值

这可能是最严重的一个状态管理问题。

例如我们有如下代码:

object SessionStorage {var sessionToken: String? = null
}

这个单例类的意图是用户登录之后将登录接口返回的token值保存在object 中,以便在任何地方都可以方便的访问到这个全局状态值。

假设用户登录成功,然后将token值正确的保存到了这个sessionToken当中,然后用户继续浏览,然后用户又切到其他应用,此时当前应用因为某种原因进程被杀死重启(如内存不足),那么这个时候 object 类的成员变量会被重新初始化为初始的零值(对于上面的代码来说就是初始化为null),用户再次返回应用时,所有使用到这个sessionToken的地方都会得到一个空值。对于用户来说,他已经登录过,但是此时他不能做任何事情(比如进行任何操作可能会得到一个“用户未登录”的toast提示)。

解决这个问题的办法是使用状态持久化存储,即便应用重启,我们依然可以从本地读取到正确的状态值。

状态持久化存储的方案可以参考我之前整理的下面文章,这里不再赘述:

  • Jetpack架构组件库:DataStore
  • Jetpack架构组件库:Room

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

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

相关文章

Vue2 迁移到 Vue3

一.《Vue 3 迁移指南》参考文档&#xff1a;https://v3-migration.vuejs.org/zh/ 二.Vue 3 中需要关注的一些新特性。 1. 组合式 API*&#xff1b; 2. 单文件组件中的组合式 API 语法糖 (<script setup>)*&#xff1b; 3. Teleport 组件&#xff1b; 4. Fragments 片段&…

【uni-app】常用组件和 API

常用组件 uni-app 为开发者提供了一系列基础组件&#xff0c;类似 HTML 里的基础标签元素&#xff0c;但 uni-app 的组件与 HTML 不同&#xff0c;而是与小程序相同&#xff0c;更适合手机端使用。 虽然不推荐使用 HTML 标签&#xff0c;但实际上如果开发者写了div等标签&…

基于uniapp大学生社团活动管理系统python+java+node.js+php微信小程序

uni-app框架&#xff1a;使用Vue.js开发跨平台应用的前端框架&#xff0c;编写一套代码&#xff0c;可编译到Android、小程序等平台。 语言&#xff1a;pythonjavanode.jsphp均支持 框架支持:springboot/Ssm/thinkphp/django/flask/express均支持 运行软件:idea/eclipse/vscod…

关于 REST API 和 SOAP,你知道多少?

背景 通过上篇文章 关于 REST API&#xff0c;你了解多少&#xff1f;&#xff0c;我们知道REST API是在Web应用程序的发展过程中产生的。在Web应用程序的早期阶段&#xff0c;应用程序之间的通信主要是通过SOAP&#xff08;Simple Object Access Protocol&#xff09;和XML-R…

递归和迭代【Py/Java/C++三种语言详解】LeetCode每日一题240218【树DFS】LeetCode 589、 N 叉树的前序遍历

有LeetCode算法/华为OD考试扣扣交流群可加 948025485 可上全网独家的 欧弟OJ系统 练习华子OD、大厂真题 绿色聊天软件戳 od1336了解算法冲刺训练 文章目录 题目描述解题思路代码方法一&#xff1a;递归法PythonJavaC时空复杂度 方法二&#xff1a;迭代法PythonJavaC时空复杂度 …

面试redis篇-08数据淘汰策略

原理 当Redis中的内存不够用时,此时在向Redis中添加新的key,那么Redis就会按照某一种规则将内存中的数据删除掉,这种数据的删除规则被称之为内存的淘汰策略。 Redis支持8种不同策略来选择要删除的key: noeviction: 不淘汰任何key,但是内存满时不允许写入新数据,默认就是…

【Pytorch】模块化

文章目录 1. 获取数据2. 创建Dataset和DataLoader3. 定义模型4. 创建训练模型引擎函数5. 创建保存模型的函数6. 训练、评估并保存模型 模块化涉及将jupyter notebook代码转换为一系列提供类似功能的不同 Python 脚本。 可以将笔记本代码从一系列单元格转换为以下 Python 文件&…

JetBrains系列工具,配置PlantUML绘图

PlantUML是一个很强大的绘图工具&#xff0c;各种图都可以绘制&#xff0c;具体的可以去官网看看&#xff0c;或者百度。 PlantUML简述 https://plantuml.com/zh/ PlantUML语言参考指引 https://plantuml.com/zh/guide PlantUML语言是依赖Graphviz进行解析的。Graphviz是开源…

[设计模式Java实现附plantuml源码~行为型] 撤销功能的实现——备忘录模式

前言&#xff1a; 为什么之前写过Golang 版的设计模式&#xff0c;还在重新写Java 版&#xff1f; 答&#xff1a;因为对于我而言&#xff0c;当然也希望对正在学习的大伙有帮助。Java作为一门纯面向对象的语言&#xff0c;更适合用于学习设计模式。 为什么类图要附上uml 因为很…

2024程序员容器化上云之旅-第6集-Ubuntu-WSL2-Windows11版:艰难复活

故事梗概 Java程序员马意浓在互联网公司维护老旧电商后台系统。 渴望学习新技术的他在工作中无缘Docker和K8s。 他开始自学Vue3并使用SpringBoot3完成了一个前后端分离的Web应用系统&#xff0c;并打算将其用Docker容器化后用K8s上云。 8 复活重生 周末终于有点属于自己的…

【书籍分享 • 第三期】虚拟化与容器技术

文章目录 一、本书内容二、读者对象三、编辑推荐四、前言4.1 云计算技术的发展4.2 KVM、Docker4.3 本书内容简介4.4 作者简介 五、粉丝福利 一、本书内容 《虚拟化与容器技术》通过深入浅出的方式介绍KVM虚拟化技术与Docker容器技术的概念、原理及实现方法&#xff0c;内容包括…

Linux之安装Nginx、前后端分离项目部署

目录 一、安装Nginx 1.1先一键安装4个依赖 1.2下载并解压安装包 1.3安装nginx&#xff0c;一般我们在nginx都是要安装ssl证书的 1.4 启动nginx服务 1.5开放80端口 1.6配置nginx自启动 1.7修改/etc/rc.d/rc/local的权限 二、多个tomcat负载加后端部署 2.1创建多个tomca…

Windows已经安装了QT 6.3.0,如何再安装一个QT 5.12

要在Windows上安装Qt 5.12&#xff0c;您可以按照以下步骤操作&#xff1a; 下载Qt 5.12&#xff1a;访问Qt官方网站或其他可信赖的来源&#xff0c;下载Qt 5.12的安装包。 下载安装地址 下载安装详细教程 安装问题点 qt安装时“Error during installation process(qt.tools…

react useRef用法

1&#xff0c;保存变量永远不丢失 import React, { useState,useRef } from react export default function App() { const [count,setcount] useState(0) var mycount useRef(0)//保存变量永远不丢失--useRef用的是闭包原理 return( <div> <button onClick{()>…

跨境电商营销进化史:从传统广告到智能化策略的全面探析

随着全球化的不断推进和互联网技术的飞速发展&#xff0c;跨境电商在过去几年里取得了显著的发展。在这个竞争激烈的市场中&#xff0c;企业们纷纷调整营销策略以应对不断变化的消费者需求和市场趋势。本文Nox聚星将和大家探讨跨境电商营销策略的演变过程&#xff0c;从传统的推…

MySQL基础(二)

文章目录 MySQL基础&#xff08;二&#xff09;1. 数据库操作-DQL1.1 介绍1.2 语法1.3 基本查询1.4 条件查询1.5 聚合函数1.6 分组查询1.7 排序查询1.8 分页查询1.9 案例1.9.1 案例一1.9.2 案例二 2. 多表设计2.1 一对多2.1.1 表设计2.1.2 外键约束 2.2 一对一2.3 多对多2.4 案…

【Spring Boot 3】【JPA】@ManyToOne 实现一对多单向关联

【Spring Boot 3】【JPA】@ManyToOne 实现一对多单向关联 背景介绍开发环境开发步骤及源码工程目录结构总结背景 软件开发是一门实践性科学,对大多数人来说,学习一种新技术不是一开始就去深究其原理,而是先从做出一个可工作的DEMO入手。但在我个人学习和工作经历中,每次学…

Python爬虫中的单线程、多线程问题(文末送书)

前言 在使用爬虫爬取数据的时候&#xff0c;当需要爬取的数据量比较大&#xff0c;且急需很快获取到数据的时候&#xff0c;可以考虑将单线程的爬虫写成多线程的爬虫。下面来学习一些它的基础知识和代码编写方法。 一、进程和线程 进程可以理解为是正在运行的程序的实例。进…

【Flink精讲】Flink反压调优

Flink 网络流控及反压的介绍&#xff1a; Apache Flink学习网 反压的理解 简单来说&#xff0c; Flink 拓扑中每个节点&#xff08;Task&#xff09;间的数据都以阻塞队列的方式传输&#xff0c;下游来不及消费导致队列被占满后&#xff0c;上游的生产也会被阻塞&#xff0c;…

dpvs 笔记

20、 基于ECMP的多活负载均衡策略 当使用ospf/ECMP来实现高可用&#xff0c;所以keepalived不需要配置vrrp功能。keepalived只使用后端服务健康检查功能。 Equal-Cost Multi-Path Routing (ECMP) ECMP根据SIP-DIP对来选择路由 keepalived 健康检查机制说明 keepalived TCP chec…