当前位置: 首页 > news >正文

Android笔记(九):Compose组件的状态(一)

在使用Compose定义UI界面时,可以发现界面的变换往往与Compose组件内部的状态相关,当状态值发生变化时,Compose构成的可组合的界面也会刷新发生相应的变化。将在本笔记中将对可组合项的状态的定义、状态提升、状态丢失和状态的保存进行简单介绍。。

一、什么是可组合项的状态

Compose采用了单向数据流设计思想。定义界面的可组合函数本身没有任何返回值,也没有像类一样封装内部的私有状态。因此通过定义可组合函数的状态,使得可组合函数关联的界面可以观察是否发生了变化。

在Kotlin语言中定义了一个接口MutableState,代码如下:

interface MutableState : State {
override var value: T
}

实现MutableState接口的任何类型的对象就是一个状态,状态是可变的,每个状态中保存一个value值。在执行可组合函数期间读取 value 属性。如果value属性值发生了变化,则可组合函数会发生重构,如果value属性值没有变化,则不会产生可组合函数的重构。 Compose组件可以通过mutableStateOf函数来获得一个这样的状态对象。例如:

val someState = mutableStateOf(true)

例如在上述的定义中, someState就会被解析为一个可以存储Boolean布尔真值的可变状态值。

Android结合remember API可以将状态值保存到内存中,当在内存中记住这个状态值。这样的好处就是,系统会在初始组合期间将由 remember 计算的值存储在组合中,并在重组期间返回存储的值。当remember和状态值结合,会非常容易对可组合函数的重构产生作用,因为remember记住的状态值在内存中。当然,remember不仅仅与可变的状态值组合,也可以与非可变值组合。

在可组合项中声明 MutableState 对象的方法有三种:

val mutableState = remember { mutableStateOf(default) }
var value by remember { mutableStateOf(default) }
val (value, setValue) = remember{ mutableStateOf(default) }

1.方式一:val mutableState = remember { mutableStateOf(default) }

这种方式是直接通过状态的引用来获取或设置value属性值
需要导入

import androidx.compose.runtime.remember

示例代码如下:

@Preview
@Composable
fun CountScreen(){val counterState = remember{mutableStateOf(0)}Column(modifier = Modifier.fillMaxSize(),horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center){//引用状态值Text(text = "点击的次数:${counterState.value}",fontSize = 20.sp)Button(onClick={//修改状态值counterState.value += 1}){Text("点击按钮",fontSize = 18.sp)}}
}

在这种方式中,是直接引用以及修改状态counterState的value属性值。当状态值发生变化界面也进行重构。运行效果如下所示:
在这里插入图片描述
图1运行效果

2.方式二:var value by remember { mutableStateOf(default) }

在这种方式中,采用了代理的方式来直接获取或设置状态内部包含的value属性值。在这种方式中必须导入:

import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.remember

示例代码如下

@Preview
@Composable
fun CountScreen(){var counter by remember {mutableStateOf(0)}Column(modifier = Modifier.fillMaxSize(),horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center){Text(text = "点击的次数:${counter}",fontSize = 20.sp)Button(onClick={//修改状态值counter += 1}){Text("点击按钮",fontSize = 18.sp)}}
}

在上述代码中,直接将状态包含的value值进行设置和修改。因此,上述代码的counter就是一个var变量,实际上就是对应状态的value属性值。
这时,运行效果如图1所示

3.方式三:val (value, setValue) = remember{ mutableStateOf(default) }

第三种方式表达形式有些奇怪。其中value对应的是状态的value属性的值,而设置状态的value属性是通过指定的setValue来实现的。
示例代码如下:

@Preview
@Composable
fun CountScreen(){val (counter,setValue) = remember {mutableStateOf(0)}Column(modifier = Modifier.fillMaxSize(),horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center){Text(text = "点击的次数:${counter}",fontSize = 20.sp)Button(onClick={//修改状态值setValue(counter+1)}){Text("点击按钮",fontSize = 18.sp)}}
}

这时,运行效果如图1所示

二、无状态的可组合函数和有状态的可组合函数

因为可组合函数对参数的处理不同导致了两种形式的可组合函数。

1.无状态的可组合Stateless Composable

无状态的可组合形式,就是函数定义形参,通过调用时依赖传递的实参,实现界面的重构。这种的可组合形式称为无状态的可组合。如下列代码所示:

@Composable
fun CountScreen(counter:Int){Box(contentAlignment= Alignment.Center,modifier = Modifier.size(300.dp,200.dp)){Text(text = "点击的次数:${counter}",fontSize = 20.sp)}
}

要调用以上的可组合函数,必须传递一个整型的数值。

2.有状态的可组合Stateful Composable

有状态的可组合形式,就是函数没有定义形参。通过定义内部的状态值,如果状态值发生变化,会导致界面进行重构。在下列定义的DisplayScreen就是一个有状态的可组合函数。DisplayScreen通过点击按钮,使得状态值发生变化,导致界面的重构。在该函数中实现对上述无状态可组合函数CountScreen的调用,代码如下:

@Preview
@Composable
fun DisplayScreen(){//定义状态值 var counter by remember{mutableStateOf(0)}Column(modifier=Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally){//调用无状态的可组合函数CountScreenCountScreen(counter)Button(onClick = {counter+=1}){Text("点击按钮")}}
}

以上两个可组合函数很好的解释了什么是无状态的和有状态的。

三、状态提升

在组合函数中,在上述的CounterScreen可组合函数中,内部状态值的变化,导致可组合进行界面的重组。如果其他可组合项共用界面元素状态,并在不同位置将界面逻辑应用到状态,则这时需要在界面层次结构中提升状态所在的层次。这样做会使可组合项的可重用性更高,并且更易于测试。具体表现形式是:将有状态的可组合函数中的状态移至可组合项的调用方,使得原来的有状态的可组合函数变成无状态的形式。

场景一:有状态的可组合函数,没有状态提升

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DisplayScreen(){val messageState = remember{mutableStateOf("")}Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()){TextField(modifier = Modifier.wrapContentWidth(),value = "${messageState.value}",label = {Text("消息")},leadingIcon={Icon(Icons.Filled.Info,contentDescription = "message")},onValueChange={messageState.value = it})}
}

场景二:状态提升

状态提升常规需要对相应的状态需要考虑替换成可组合函数的两个参数:

  • value:T:需要修改的状态的值
  • action(T)->Unit :请求修改值的事件

修改上述函数,如下所示:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DisplayScreen(message:String,action:(String)->Unit){Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()){TextField(modifier = Modifier.wrapContentWidth(),value = "${message}",label = {Text("消息")},leadingIcon={Icon(Icons.Filled.Info,contentDescription = "message")},onValueChange={action.invoke(it)})}
}

这个DisplayScreen函数修改为一个无状态的函数,需要调用该函数,形式如下:

@Preview
@Composable
fun MainScreen(){var input by remember{mutableStateOf("请输入")}Box(modifier = Modifier.fillMaxSize(),contentAlignment = Alignment.Center){//调用无状态的可组合函数DisplayContentDisplayContent(message = input,action = {it:String->input = it})}
}

在MainScreen中通过传递实参input和对应action事件给DisplayScreen,这样,使得原来的DisplayScreen函数的状态提升到MainScreen中。

三、状态丢失

任何Android应用都可能因为活动Activity重新创建或者进程,导致丢失界面的状态。
现在,修改手机(模拟器)的设置Settings->Display->Auto-rotate Screen为true,如下所示:
在这里插入图片描述
图2 设置模拟器的为自动旋转
运行上述的DisplayScreen,然后旋转手机模拟器,运行情况如下图所示:

在这里插入图片描述
图3 DisplayScreen界面的运行效果
配置的变化导致状态丢失,会导致移动应用运行的连续性遭到破坏。

四、保留状态

要解决重新创建活动或进程导致状态的丢失问题,则可以通过rememberSaveable来保留状态,使得重新创建活动或进程依然可以使用原有的状态。
rememberSaveable 通过保存的实例状态机制将界面元素状态存储在 Bundle 中。

  • 自动将基元类型存储到 Bundle 中。
  • 如果是自定义的类实现Parcelable,实现序列化,可以通过Bundle来传递数据。
  • 使用listSaver 和 mapSaver 等 ComposeAPI
  • 实现会扩展 Compose 运行时 Saver 类的自定义 Saver类。

方式一:自动将基元类型和实现Parcelable接口的类型的数据存储到 Bundle 中

任何基元类型如String、Int、Double、Float、Boolean、Short、Long等以及实现parcelable接口自定义类型的对象,可以通过rememberSaveable中的状态会随着onSaveInstanceState以Bundle的键值对的形式进行存储。这里的关键字就是Composable函数在编译期确定的唯一标识。通过这个唯一标识,可以将数据按照键值对保存在Bundle,并通过这个关键字进行数据恢复。

在下列示例中,自定义类,因为需要实现Parcelable,为了简化代码,需要在项目模块的build.gradle.kt中设置使用kotlin-parcelize插件。

plugins {     id("kotlin-parcelize") 
}

自定义一个数据类Employee,代码如下:

@Parcelize
data class Employee(val name:String,val gender:String,var salary:Double): Parcelable
@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun EmployeeScreen(){val userState = rememberSaveable {mutableStateOf(Employee("张三","男",5000.0))}var salary by rememberSaveable{mutableStateOf(0.0)}Box(contentAlignment = Alignment.Center,modifier = Modifier.fillMaxSize()){Column{Text(userState.value.toString())TextField(value = "${salary}",label={Text("修改工资:")},leadingIcon = {Icon(imageVector = Icons.Filled.Info,contentDescription = "工资")},onValueChange = {salary = it.toDouble()})Button(onClick ={userState.value.salary = salary}){Text("修改工资")}}}
}

在上述的代码中,定义了两处可保存的状态:

val userState = rememberSaveable {
mutableStateOf(Employee(“张三”,“男”,5000.0))
}

var salary by rememberSaveable{mutableStateOf(0.0)}

运行结果如下图所示:
在这里插入图片描述
图4 EmployeeScreen的运行效果

在这个简单应用中,当屏幕没有横纵屏切换,修改文本框的值,点击按钮,第一行的文本并没有发送变化。这是因为Text中显示UserState.value对应的对象并没有变化,只是变化了UserState.value对象的属性salary的值而已。因此,点击按钮没有发生文本的变换。但是,当切换屏幕的横纵方向时,因为重新创建屏幕依附的活动,导致从Bundle数据中读取已经保存的状态值,第一行的文本内容会发生相应的变化。

方式二:实现会扩展 Compose 运行时 Saver 类的自定义 Saver类

自定义Saver类,自定义保存状态值的逻辑。通过自定义的Saver类定制数据保存的方式和数据恢复的方式。下面定义一个对应上例Employee数据类的EmployeeSaver类定制保存和恢复Employee数据的逻辑,代码如下:

object EmployeeSaver: Saver<Employee, Bundle> {//恢复成Employee对象override fun restore(value: Bundle): Employee? {return value.getString("name")?.let{name:String->value.getString("gender")?.let{gender:String->value.getDouble("salary")?.let{salary:Double->Employee(name,gender,salary)}}}}//保存到Bundle中override fun SaverScope.save(value: Employee): Bundle? {return Bundle().apply{putString("name",value.name)putString("gender",value.gender)putDouble("salary",value.salary)}}
}

然后修改EmployeeScreen可组合函数,将Employee对象的存储和恢复按照EmployeeSaver指定的逻辑进行,对应的代码如下:

@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun EmployeeScreen(){val userState = rememberSaveable(stateSaver = EmployeeSaver) {mutableStateOf(Employee("张三","男",5000.0))}var salary by rememberSaveable{mutableStateOf(0.0)}Box(contentAlignment = Alignment.Center,modifier = Modifier.fillMaxSize()){Column{Text("${userState.value}")TextField(value = "${salary}",label={Text("修改工资:")},leadingIcon = {Icon(imageVector = Icons.Filled.Info,contentDescription = "工资")},onValueChange = {salary = it.toDouble()})Button(onClick ={userState.value.salary = salary}){Text("修改工资")}}}
}

运行结果如图4一致。

方式三:使用listSaver 和 mapSaver 等 ComposeAPI进行数据保存和恢复

通过listSaver和mapSaver等Compose API定制保存和恢复数据的逻辑,修改上述的EmployeeScreen函数,代码如下:

@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun EmployeeScreen(){//定义保存和恢复数据的逻辑val employeeSaver = run{mapSaver(save = {//定义映射的方式指定键值对进行数据保存逻辑mapOf("name" to it.name,"gender" to it.gender,"salary" to it.salary)},restore={//定义数据根据映射恢复数据的逻辑Employee(it["name"] as String,it["gender"] as String,it["salary"] as Double)})}val userState = rememberSaveable(stateSaver = employeeSaver) {mutableStateOf(Employee("张三","男",5000.0))}var salary by rememberSaveable{mutableStateOf(0.0)}Box(contentAlignment = Alignment.Center,modifier = Modifier.fillMaxSize()){Column{Text("${userState.value}")TextField(value = "${salary}",label={Text("修改工资:")},leadingIcon = {Icon(imageVector = Icons.Filled.Info,contentDescription = "工资")},onValueChange = {salary = it.toDouble()})Button(onClick ={userState.value.salary = salary}){Text("修改工资")}}}
}

运行结果如图4一致。

参考文献

(1) 状态和JetPack Compose
https://developer.android.google.cn/jetpack/compose/state?hl=zh-cn

相关文章:

Android笔记(九):Compose组件的状态(一)

在使用Compose定义UI界面时&#xff0c;可以发现界面的变换往往与Compose组件内部的状态相关&#xff0c;当状态值发生变化时&#xff0c;Compose构成的可组合的界面也会刷新发生相应的变化。将在本笔记中将对可组合项的状态的定义、状态提升、状态丢失和状态的保存进行简单介绍…...

3.2. onnx export multi_batch

前言 将onnx bs=1 修改为多batch操作 参考链接: https://www.cnblogs.com/tangjunjun/p/16500116.html https://blog.csdn.net/weixin_43863869/article/details/128638397?spm=1001.2101.3001.6650.3&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault…...

探索低代码PaaS平台的优势与选择原因

PaaS是一种云产品&#xff0c;它为应用程序的开发和部署提供基础结构。它提供中间件、开发工具和人工智能来创建功能强大的应用程序&#xff0c;大多数PaaS服务都与存储和网络基础架构捆绑在一起&#xff0c;就像基础架构即服务&#xff08;IaaS&#xff09;一样&#xff0c;可…...

AD教程(一)工程组成及创建

AD教程&#xff08;一&#xff09;工程组成及创建 工程组成 原理图库 绘制电阻模型、芯片模型、电容模型等&#xff0c;即将元件模型绘制出来。 原理图 将绘制的原件模型放置到原理图中&#xff0c;然后再添加连接的导线、网络标号。器件和器件之间的连接关系&#xff0c;在原…...

SAP业务从ECC升级到SAP S/4HANA有哪些变化?有哪些功能得到增强?

SAP在2015年推出了新一代商务套件SAP S/4 HANA。 SAP S/4 HANA (全称SAP Business suite 4 SAP HANA),这款新产品完全构建于目前先进的内存平台SAP HANA 之上&#xff0c;同时采用现代设计理念&#xff0c;通过SAP Fiori 提供精彩的用户体验 (UX)。提供比ECC更强大的功能。S/4h…...

常用conda和pip命令总结

conda 环境相关命令 conda 新建环境命令 conda create -n env_name pythonx.xenv_name 是环境名&#xff0c;自己换成所要创建的虚拟环境的名字 pythonx.x 是版本号&#xff0c;比如3.7&#xff0c;3.8这样 查看conda环境下所有的虚拟环境 conda info -e conda env list两条…...

【计算机网络】路由器的工作原理

文章目录 输入端口处理和基于目的地转发交换结构输出端口处理排队问题参考资料 路由器的四个组件 输入端口(input port)&#xff1a;执行物理层功能&#xff08;input port 左边方框、output port 右边方框&#xff09;、数据链路层功能&#xff08;input/output port 中间方框…...

队列概念|循环队列的实现

前言 今天我们将学习循环队列实现&#xff0c;我们首先介绍队列的概念和结构&#xff0c;之后一步步讲解循环队列由来与实现。 一、队列的概念与结构 1、队列的概念 队列&#xff1a; 只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表。队列是…...

监控数据控中的数据表

背景&#xff1a; 在做一个项目的时候&#xff0c;每次代码分析的数据会写入到数据库&#xff0c;目前想实现当数据插入到数据库后&#xff0c;对新插入的数据进行监控解析。当有一个新纪录插入到数据表的时候&#xff0c;数据库可以自动解析新插入的数据记录。 思路如下&…...

进程替换..

1、单进程版 – 最简单的先看看程序替换 现象就是 1、我们用自己的进程封装了内置指令ls,并且代码中execl 后 printf 的after并没有打印出来。 2、谈进程替换的原理 单进程替换基本原理 上面例子中execl的做法非常简单粗暴&#xff0c;要调用ls&#xff0c;那么就把mycom…...

M1安装OpenPLC Editor

下载OpenPLC Editor for macOS.zip文件后&#xff0c;使用tar -zvxf命令解压&#xff0c;然后将"OpenPLC Editor"拖入到"应用程序"文件夹 右键点击"OpenPLC Editor"&#xff0c;打开这个""文件&#xff0c;替换为以下内容 #!/bin/bash…...

STM32F10xx 存储器和总线架构

一、系统架构 在小容量、中容量和大容量产品 中&#xff0c;主系统由以下部分构成&#xff1a; 四个驱动单元 &#xff1a; Cotex-M3内核、DCode总线&#xff08;D-bus&#xff09;和系统总线&#xff08;S-bus&#xff09; 通用DMA1和通用DMA2 四个被动单元 内部SRAM 内部…...

并发编程

什么是并发编程&#xff1f; 并行&#xff1a;在同一个时间节点上&#xff0c;多个线程同时执行(是真正意义上的同时执行) 并发&#xff1a;一个时间段内&#xff0c;多个线程依次执行。 并发编程&#xff1a;在例如买票、抢购、秒杀等等场景下&#xff0c;有大量的请求访问…...

Lauterbach使用指南之RunTime功能

Lauterbach使用指南之RunTime功能 前言 首先&#xff0c;请问大家几个小小问题&#xff0c;你清楚&#xff1a; Lauterbach这个工具是干什么用的吗&#xff1f;在软件运行过程中如何测量两个运行point之间的runtime时间呢&#xff1f;Lauterbach的RunTime功能具体应当如何来操…...

GaussDB数据库管理系统介绍

1.GaussDB的发展 2.GaussDB的生态 内部&#xff1a; 云化自动化方案。通过数据库运行基础设施的云化将DBA(数据库管理员)和运维人员的日常工作 自动化。外部&#xff1a; 采用与数据库周边生态伙伴对接与认证的生态连接融合方案&#xff0c;解决开发者/DBA难获取、应用难对接等…...

使用docker部署lnmp多站点

1. 创建一个 Docker 网络 以便容器可以在同一网络上进行通信 docker network create lnmpnetwork2. 运行 MySQL 容器&#xff1a; 运行 MySQL 容器并将其连接到创建的网络。确保将 MySQL 的端口映射到宿主机上&#xff0c;以便您可以从宿主机访问数据库。 将mysql的配置和数…...

实例详解:Java使用JWT和Redis实现高效单点登录(SSO)

前言 单点登录&#xff08;Single Sign-On&#xff0c;简称SSO&#xff09;是一种身份验证和访问控制机制&#xff0c;允许用户使用一组凭证&#xff08;如登录名和密码&#xff09;登录到多个应用程序中&#xff0c;而无需为每个应用程序单独进行身份验证。用户只需要登录一次…...

SQL中使用ROLLUP和CUBE函数轻松生成汇总行

在数据分析和报表制作中&#xff0c;通常需要对数据进行汇总和分组&#xff0c;我们常用的就是GROUP BY汇总数据&#xff0c;当我们想按照不同维度汇总时&#xff0c;往往需要编写多个GROUP BY预计&#xff0c;而借助ROLLUP 和 CUBE 函数可以一次性生成子总计和总计行&#xff…...

CentOS 7 安装和配置java环境

1 安装包准备 安装包可以通过下面地址进行版本选择安装&#xff1a; https://www.oracle.com/java/technologies/downloads/#java8 2 正式开始安装 本次分享的安装方法直接通过编辑/etc/profile文件实现java的安装 2.1 新建安装包存放目录 mkdir /java cd /java/ 2.2 解压安…...

「实验记录」CS144 Lab0 networking warmup

文章目录 一、Motivation二、SolutionsS1 - Writing webgetS2 - An in-memory reliable byte stream 三、Results四、Source 一、Motivation 第一个小测试 webget 是想让我们体验并模拟一下在浏览器中键入 URL 后获得远程服务器传来的内容&#xff0c;这并没有太大的难度&…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

无法与IP建立连接,未能下载VSCode服务器

如题&#xff0c;在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈&#xff0c;发现是VSCode版本自动更新惹的祸&#xff01;&#xff01;&#xff01; 在VSCode的帮助->关于这里发现前几天VSCode自动更新了&#xff0c;我的版本号变成了1.100.3 才导致了远程连接出…...

uniapp中使用aixos 报错

问题&#xff1a; 在uniapp中使用aixos&#xff0c;运行后报如下错误&#xff1a; AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...

听写流程自动化实践,轻量级教育辅助

随着智能教育工具的发展&#xff0c;越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式&#xff0c;也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建&#xff0c;…...

rnn判断string中第一次出现a的下标

# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)

前言&#xff1a; 最近在做行为检测相关的模型&#xff0c;用的是时空图卷积网络&#xff08;STGCN&#xff09;&#xff0c;但原有kinetic-400数据集数据质量较低&#xff0c;需要进行细粒度的标注&#xff0c;同时粗略搜了下已有开源工具基本都集中于图像分割这块&#xff0c…...

CSS | transition 和 transform的用处和区别

省流总结&#xff1a; transform用于变换/变形&#xff0c;transition是动画控制器 transform 用来对元素进行变形&#xff0c;常见的操作如下&#xff0c;它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能

1. 开发环境准备 ​​安装DevEco Studio 3.1​​&#xff1a; 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK ​​项目配置​​&#xff1a; // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...

【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案

目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后&#xff0c;迭代器会失效&#xff0c;因为顺序迭代器在内存中是连续存储的&#xff0c;元素删除后&#xff0c;后续元素会前移。 但一些场景中&#xff0c;我们又需要在执行删除操作…...

SQL Server 触发器调用存储过程实现发送 HTTP 请求

文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...