39-设置开关退出就复原-用偏好写队列和回滚状态兜住

第39篇|设置开关退出就复原:用偏好写队列和回滚状态兜住

摘要:设置页开关最容易被写成 @State 一改就完事。实际项目里,用户快速连点、离开页面、写入失败、重启应用都会暴露问题:UI 显示已开启,持久化里还是旧值。更稳的做法是让设置写入走串行队列,页面可以乐观展示,但失败必须回滚,并用重启验证证明真的保存成功。

我处理过一个提醒开关问题:用户打开“每日提醒”,页面显示已开启,退出再进又变回关闭。日志里没有崩溃,因为 UI 状态确实变了,只是 Preferences 写入被后一次旧值覆盖。更麻烦的是用户快速点三次,最后界面和持久化各拿一个值。这个问题不能靠“加个 await”随便修,要把写入路径收成一个队列。

在这里插入图片描述

这篇文章解决四个实际问题:

  1. 区分页面乐观状态和已落盘状态。
  2. 用 SettingWriteQueue 串行处理同一个 key 的写入。
  3. 写入失败时回滚 UI,并给用户可理解提示。
  4. 用退出重进和杀进程重启验证持久化真实生效。

在这里插入图片描述
在这里插入图片描述

先确认复原发生在哪个时刻

设置复原可能发生在返回页面时,也可能是杀进程重启后。两个现象对应的根因不同,不能只看开关 UI。

位置现象风险处理方向
返回页面复原页面重新读取旧缓存写入还没完成等待写入确认或展示保存中
重启后复原持久化失败只改了内存状态统一写入 Preferences
快速连点错乱并发写同一个 key后完成的旧值覆盖新值串行队列
写入失败无提示catch 后吞错用户以为保存成功失败回滚

这一步的价值是先把问题放回运行链路。只要能确认问题停在哪个位置,后面就不用靠猜测改页面。

页面只能发意图,不能直接散写偏好

设置项看起来简单,但长期维护时会有默认值、迁移、校验和失败回滚。页面直接写 Preferences 会让这些逻辑散得到处都是。

模块应该负责不应该负责
SettingsPage展示值、保存中、错误文案Preferences API
SettingsService设置项校验和保存流程具体 UI 布局
SettingWriteQueue同 key 串行写入业务默认值
PreferenceStore读写持久化连点策略
Migration旧 key 到新 key页面事件

边界定清后,代码就不会在页面、服务和回调之间来回复制同一段判断。后续新增场景也能先判断应该落在哪一层。

设置状态要有 pending 标记

export interface SettingItemState {
  key: string
  value: boolean
  persistedValue: boolean
  pending: boolean
  errorText: string
}

export function createSettingState(key: string, value: boolean): SettingItemState {
  return {
    key,
    value,
    persistedValue: value,
    pending: false,
    errorText: ''
  }
}

value 是页面当前展示,persistedValue 是最后一次确认落盘的值。失败时页面能回滚到真实持久化状态,而不是随便猜一个默认值。

这类模型最好放在 modelscommon 中,页面、Service 和仓储都使用同一份类型,避免各层用字符串互相猜。

写队列保证同一个 key 顺序执行

export class SettingWriteQueue {
  private jobs: Map<string, Promise<void>> = new Map()

  enqueue(key: string, task: () => Promise<void>): Promise<void> {
    const previous = this.jobs.get(key) ?? Promise.resolve()
    const next = previous.then(task).finally(() => {
      if (this.jobs.get(key) === next) {
        this.jobs.delete(key)
      }
    })
    this.jobs.set(key, next)
    return next
  }
}

队列按 key 串行,不会让两个提醒开关互相阻塞,但能保证同一个 key 的后一次操作不会被更早的写入覆盖。

Service 的目标不是把所有逻辑都塞进去,而是把跨页面、跨生命周期或需要持久化的判断集中起来。页面只表达用户动作。

页面先乐观更新,再等确认

@Component
struct ReminderSettingRow {
  @State item: SettingItemState = createSettingState('daily_reminder', false)
  private service: SettingsService = createSettingsService()

  async toggleReminder(nextValue: boolean): Promise<void> {
    const before = this.item.persistedValue
    this.item = { ...this.item, value: nextValue, pending: true, errorText: '' }

    const saved = await this.service.saveBoolean(this.item.key, nextValue)
    if (!saved) {
      this.item = { ...this.item, value: before, pending: false, errorText: '保存失败,请重试' }
      return
    }

    this.item = { ...this.item, persistedValue: nextValue, pending: false, errorText: '' }
  }
}

乐观更新能让交互及时,但 pending 和回滚不能省。否则用户看到的是“已开启”,持久化里却可能还是关闭。

页面层要控制展示和交互节奏,但不应该拥有底层事实。这样页面重建、横竖屏变化或返回前台时,都能重新从 Service 拿到可信结果。

SettingsService 统一读写默认值

export class SettingsService {
  constructor(
    private readonly store: PreferenceStore,
    private readonly queue: SettingWriteQueue
  ) {}

  async loadBoolean(key: string, defaultValue: boolean): Promise<boolean> {
    const value = await this.store.getBoolean(key)
    return value ?? defaultValue
  }

  async saveBoolean(key: string, value: boolean): Promise<boolean> {
    return this.queue.enqueue(key, async () => {
      await this.store.putBoolean(key, value)
      await this.store.flush()
    }).then(() => true).catch(() => false)
  }
}

读默认值和写入确认都在 Service 层。页面不用知道是否需要 flush,也不会因为多个页面同时写同一个 key 出现竞争。

实际项目里,很多问题不是第一次进入页面暴露,而是在冷启动、返回前台、页面重建或回调晚到时暴露。把这段生命周期补上,修复才算闭环。

失败路径要回滚而不是假装成功

情况页面表现工程处理
写入失败回滚到 persistedValue展示保存失败
用户快速连点同 key 排队最后一次结果一致
读取旧 key迁移后删除旧 key避免重启读回旧值
页面离开时 pending保留保存中提示或禁用返回不要直接丢状态

兜底路径不是可有可无。用户遇到异常时,页面至少要给出当前状态、可执行动作和可排查线索。

排查时找散写偏好

rg -n "putBoolean|getBoolean|Preferences|flush|daily_reminder" entry features common
rg -n "@State.*reminder|persistedValue|pending|SettingWriteQueue" entry features common
rg -n "onClick|onChange|Toggle|Switch" entry features common

第一组命令找旧路径,第二组命令找新边界,第三组命令看生命周期或回调位置。命中后不要只看字段名,要判断它是否仍在直接改页面状态。

验证清单

验证项预期结果
打开开关后返回再进仍保持新值
杀进程重启仍读取到新值
快速连点三次最终 UI 和持久化一致
模拟写入失败UI 回滚并显示错误
旧版本迁移旧 key 不再覆盖新 key

这些验证最好在真机或模拟器里按顺序走一遍。只看源码很难发现冷启动、重启和回调晚到这类问题。

常见问题和处理方式

现象常见原因处理方式
退出就复原只改 @State必须走 SettingsService
偶现反向并发写入乱序同 key 串行队列
失败无感catch 后吞错回滚并提示
重启读旧值默认值和旧 key 散落集中迁移和读取

如果现象没有出现在表里,仍然建议按“输入来源 -> 中间状态 -> 持久化或页面展示 -> 失败兜底”的顺序排查。

小结:设置项要把显示和落盘分清

开关状态不是点一下就结束。页面可以先展示用户选择,但服务层必须串行写入、确认落盘、失败回滚,最后用重启验证。这样设置页才不会出现看似保存成功、实际重进复原的问题。
| 默认值和旧 key 散落 | 集中迁移和读取 |

如果现象没有出现在表里,仍然建议按“输入来源 -> 中间状态 -> 持久化或页面展示 -> 失败兜底”的顺序排查。

小结:设置项要把显示和落盘分清

开关状态不是点一下就结束。页面可以先展示用户选择,但服务层必须串行写入、确认落盘、失败回滚,最后用重启验证。这样设置页才不会出现看似保存成功、实际重进复原的问题。

内容概要:本文通过一个三层交换机网络实验,演示了在未启用生成树协议(STP)时因二层环路导致的广播风暴现象,以及启用STP后的网络收敛过程。实验拓扑由三台交换机构成三角形连接,在未开启STP时,Wireshark抓包显示大量重复的ARP请求ICMPv6邻居请求报文,形成广播泛洪,表明存在数据链路层环路。通过关闭部分端口可临时消除风暴,但恢复连接后问题重现。随后开启STP协议,抓包捕获到周期性的STP配置BPDU报文TCN(拓扑变更通知)报文,说明STP已正常工作并成功阻断环路,选举出根桥(SW1),各交换机确定根端口、指定端口角色,实现网络稳定。实验还展示了不同交换机上的端口状态与角色分配情况,验证了STP的工作机制。; 适合人群:具备基本网络知识的大专院校学生、初级网络工程师或从事网络运维的技术人员。; 使用场景及目标:①理解二层环路的危害及其引发的广播风暴现象;②掌握STP协议的基本原理,包括根桥选举、端口角色划分及BPDU报文的作用;③学会使用Wireshark抓包分析STP协议行为,识别配置BPDUTCN报文;④通过实际配置观察交换机端口状态变化,加深对生成树算法的理解。; 阅读建议:学习者应结合实验拓扑动手配置设备,同步使用抓包工具观察网络行为变化,对比开启STP前后现象差异,深入理解协议工作机制,并参考命令行输出分析端口角色与状态转换过程。
内容概要:本文研究了基于多元宇宙优化算法的主动配电网优化调度方法,重点考虑“源--储”三者之间的协同互动关系,并以IEEE33节点标准系统为算例进行仿真验证。研究构建了一个涵盖分布式电源、可控负荷及储能系统的综合优化调度模型,旨在通过高效的智能优化算法提升配电网运行的经济性、可靠性与稳定性。多元宇宙优化算法被引入求解该复杂的非线性、多变量、强约束优化问题,展现出良好的全局搜索能力与收敛性能。通过对多种运行场景的对比分析,验证了所提方法在降低网络损耗、平抑负荷波动、提高新能源消纳率以及改善电压质量等方面的显著有效性与优越性。; 适合人群:具备电力系统分析、优化理论及智能算法基础的研究生、科研人员,以及从事智能配电网、能源互联网、分布式能源调度等相关领域的工程技术人员。; 使用场景及目标:①应用于主动配电网的日前或实时优化调度决策,提升系统运行效率与经济效益;②为高比例可再生能源接入背景下的配电网提供“源--储”协同控制策略的设计依据;③作为智能优化算法(如多元宇宙优化算法)在电力系统领域应用的教学与科研案例,促进算法实践与理论创新。; 阅读建议:读者应结合提供的Matlab代码深入理解模型的数学建模过程与算法实现细节,建议在复现过程中重点关注目标函数构建、约束条件处理、算法参数设置及优化结果的可视化分析,以全面掌握该方法的技术核心与工程应用价值。
内容概要:本文围绕2026年高教社杯全国大学生数学建模竞赛B题“无线电干扰源的快速自动定位与清除”展开,提供完整的数学建模方案、配套代码实现与论文撰资源。内容涵盖问题分析、模型构建、算法设计与仿真验证全过程,并延伸至多类相关科研方向的Matlab/Simulink仿真实例,如无人机路径规划、微电网优化调度、信号处理、电力系统无功优化、时频冲突消解等,充分展示复杂工程问题的建模与求解方法。资源通过百度网盘及微信公众号“荔枝科研社”免费共享,旨在为参赛学生与科研人员提供系统性技术支持与创新启发。; 适合人群:全国大学生数学建模竞赛参赛者,具备一定数学建模、编程基础(尤其是Matlab/Simulink)的本科生与研究生,以及从事智能优化、通信工程、电力系统、信号处理、路径规划等相关领域研究的科研人员。; 使用场景及目标:①辅助完成数学建模竞赛中关于无线电干扰源定位与清除等问题的建模、编程与论文撰;②获取多种科研课题的高质量代码实现与论文参考范例,提升科研效率与创新能力;③学习先进优化算法(如GWO、WOA、NSGA-III等)在复杂系统优化中的应用方法;④借鉴多学科交叉问题的建模思路与仿真技术。; 其他说明:所有资源均可通过提供的百度网盘链接及公众号免费获取,建议用户按照目录结构系统性地浏览与学习,结合代码运行与论文阅读进行实践,以深入掌握建模范式与算法实现细节,充分发挥资源的学习价值与科研参考价值。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值