羽球搭子 HarmonyOS 实战(22):备份恢复与长比赛容错
一、续打一场长比赛不该依赖页面还活着一场多人轮转可能持续数小时。应用被系统回收、设备临时重启、用户切到其他页面甚至比赛中途切换账号都不能让参赛者、对阵、比分和当前场次凭空消失。只把这些值放在页面State中最多能扛住一次正常渲染无法承担长比赛的连续性。羽球搭子把恢复分成两层。第一层是应用内持续持久化每次对局或比分变更都写入 Preferences启动时先恢复到 AppStorage再加载页面。第二层是系统备份入口模块声明 BackupExtensionAbility并允许系统备份恢复应用数据。前者负责“进程结束后继续比赛”后者负责“系统在设备级备份与恢复流程中调用应用扩展”。系统备份回调并不会自动替代业务快照设计。长比赛是否可恢复仍取决于应用在每次关键变化后有没有保存完整、可兼容的数据。二、先画出需要恢复的最小状态集合比赛恢复不是序列化整个页面。真正需要跨进程保存的是对局摘要、对局详情、当前对局 ID、进行中的草稿以及必要的云端映射。当前场次 ID属于运行期导航信息可以从活动对局和比赛状态推导也可以在需要时单独保存。状态保存频率恢复用途缺失时降级对局摘要列表新建、改名、删除、比分变化首页与历史入口显示空列表对局详情人员、对阵、比分变化恢复完整比赛跳过损坏项当前对局 ID用户切换当前对局时冷启动回到上下文选择列表第一项活动草稿编辑人员或赛制时恢复未完成设置使用空草稿云端会话别名与版本同步成功时避免重复建房和版本冲突重新拉取保存颗粒度以对局为单位比把所有内容塞进一个巨大 JSON 更容易局部恢复。某一场详情损坏时其他对局仍然可用。三、写入路径同时更新内存与磁盘页面需要立即看到新比分所以 Store 先更新 AppStorage进程恢复需要磁盘副本所以同一入口随后写 Preferences。所有页面都调用 Store 的受控方法不允许某个页面只改内存对象。function saveDetail(detail: SessionDetail): void { const normalized normalizeDetail(detail) const runtimeKey detailKey(normalized.id) AppStorage.setOrCreateSessionDetail(runtimeKey, normalized) persist(runtimeKey, JSON.stringify(normalized)) } function persist(key: string, value: string): void { const prefs SessionStore.preferences if (prefs undefined) { return } try { prefs.putSync(scopedKey(key), value) prefs.flush() } catch (_) { // 运行期状态仍保留页面可继续显示 } }写盘失败不能伪装成永久保存成功。当前实现选择保证运行期可用并在关键流程通过可见提示暴露异常若业务要求更强还需要增加写入结果、重试和磁盘空间诊断。四、启动恢复遵守“先摘要、后详情、再当前项”恢复时先清空运行期镜像防止残留值与磁盘数据混合然后读取摘要列表按摘要中的 ID 逐个恢复详情最后恢复当前对局和未完成草稿。这个顺序保证页面拿到的引用关系完整。function hydrateFromPreferences(): void { clearRuntimeMirror() const prefs SessionStore.preferences if (prefs undefined) { return } try { const sessionsJson readWithMigration(prefs, g_sessions) const sessions sessionsJson.length 0 ? JSON.parse(sessionsJson) as SessionSummary[] : [] AppStorage.setOrCreateSessionSummary[](g_sessions, sessions) sessions.forEach((summary) { const json readWithMigration(prefs, rawDetailKey(summary.id)) if (json.length 0) return const detail normalizeDetail(JSON.parse(json) as SessionDetail) AppStorage.setOrCreateSessionDetail(rawDetailKey(summary.id), detail) }) restoreActiveSession(prefs) restoreDraft(prefs) } catch (_) { keepRecoverableRuntimeState() } }把所有解析放进一个大try块实现简单但一个损坏详情可能阻断后续项目。更强的实现会对每个详情单独捕获并记录被跳过的 ID。文章中的验收也要覆盖损坏 JSON而不只是正常重启。五、旧键迁移与默认值负责版本兼容应用升级后字段和作用域键会变化。恢复函数先读当前账号作用域键找不到时再读旧的无作用域键和昵称作用域键读取成功后复制到新键。详情模型通过normalizeDetail补齐新增字段确保旧 JSON 不会因缺少participantRefs或云端身份字段而崩溃。function normalizeDetail(source: SessionDetail): SessionDetail { return { id: source.id, name: source.name ?? 未命名对局, participants: source.participants ?? [], participantRefs: normalizeParticipantRefs( source.participants ?? [], source.participantRefs, false ), matches: (source.matches ?? []).map((match) ({ ...match, scoreA: Math.max(0, match.scoreA ?? 0), scoreB: Math.max(0, match.scoreB ?? 0), finishedAt: match.finishedAt ?? 0 })), createdAt: source.createdAt ?? Date.now(), updatedAt: source.updatedAt ?? source.createdAt ?? Date.now() } }默认值的目标是恢复可用而不是掩盖所有错误。缺少可推导字段可以补齐主键为空、结构完全不符等问题应跳过并记录避免把损坏数据写回覆盖原始副本。六、比分保存要形成可恢复的原子语义一个比分变化会影响对局详情、摘要更新时间、完成状态和统计结果。虽然 Preferences 不是关系型事务Store 仍可通过固定顺序减少半更新先构造完整新详情再写详情随后更新摘要。恢复时若摘要存在而详情缺失页面应跳过或标记异常。function saveScore( sessionId: string, matchId: string, scoreA: number, scoreB: number ): ScoreChange | undefined { const current getDetail(sessionId) if (current undefined) { return undefined } const index current.matches.findIndex((item) item.id matchId) if (index 0) { return undefined } const matches current.matches.slice() const nextMatch { ...matches[index], scoreA: Math.max(0, scoreA), scoreB: Math.max(0, scoreB), updatedAt: Date.now() } matches[index] nextMatch saveDetail({ ...current, matches, updatedAt: Date.now() }) touchSummary(sessionId) return toScoreChange(sessionId, nextMatch) }长比赛中每次有效修改都调用这一入口应用被结束后最多丢失尚未进入 Store 的瞬时点击不会丢掉整场内存模型。七、系统备份扩展只负责设备级入口模块通过 backup 类型扩展注册EntryBackupAbility配置允许系统执行备份恢复。回调可以记录版本并为未来的数据迁移预留钩子。当前回调不自行打包一份业务 JSON因此不能把它描述成应用内“导出备份文件”功能。export default class EntryBackupAbility extends BackupExtensionAbility { async onBackup(): Promisevoid { hilog.info(DOMAIN, backup, system backup callback) await Promise.resolve() } async onRestore(bundleVersion: BundleVersion): Promisevoid { hilog.info( DOMAIN, backup, system restore callback %{public}s, JSON.stringify(bundleVersion) ) await Promise.resolve() } }能力应用内 Preferences 恢复系统 BackupExtensionAbility触发时机每次启动和账号切换系统备份/恢复流程主要目标进程结束后续打比赛设备级数据迁移入口数据组织应用自行定义键与模型受系统备份机制约束是否提供手工导出文件否否验收方式冷启动、崩溃恢复、数据兼容系统备份恢复测试系统备份机制的行为、范围和约束应以 HarmonyOS 应用数据备份恢复官方指南 为准并在目标设备和目标系统版本上验证。八、长比赛容错要做破坏性演练正常路径之外至少执行四组演练。第一比赛进行中结束应用进程并重启人员、对阵、比分和当前对局恢复。第二在保存后立即切换页面或锁屏再回到计分页显示与 Store 一致。第三构造旧版本缺字段 JSON升级后默认值正确补齐。第四构造某一场详情损坏其他对局仍能进入。还应验证账号切换账号 A 的长比赛保存后退出账号 B 登录不应看到 A 的数据A 再登录时恢复原比赛。系统备份恢复测试则独立进行不能用一次普通冷启动替代。演练期望结果不合格信号计分后强制结束进程重启后比分一致回到默认 0:0详情字段缺失使用兼容默认值页面解析崩溃单条详情损坏其他对局仍可访问全部历史为空切换账号数据严格分区看到上一账号比赛系统恢复旧版本数据按版本兼容读取恢复后无法启动九、总结备份恢复与长比赛容错由两层能力共同组成。应用内 Store 在每次关键变化后保存摘要、详情和当前上下文启动时按依赖顺序恢复并通过作用域、旧键迁移和模型归一化兼容历史数据系统备份扩展提供设备级备份恢复入口。两层边界越清楚验收越可靠冷启动成功证明应用内持续持久化系统备份回调成功证明设备级入口可用。只有分别验证才能避免把一个空回调误当完整业务快照也避免把普通 Preferences 恢复夸大成跨设备备份。

相关新闻

LLM与RAG技术解析:从原理到企业级应用实践

LLM与RAG技术解析:从原理到企业级应用实践

1. 从LLM到RAG:AI核心概念全景解析当ChatGPT在2022年底引爆全球AI热潮时,很多人第一次接触到了"大语言模型"这个概念。但真正在产业界掀起变革的,是随后出现的RAG技术架构。作为在AI领域深耕多年的从业者,我见证了从基础…

2026/10/9 6:08:12 阅读更多 →
大模型时代程序员转型指南:从基础到高薪岗位

大模型时代程序员转型指南:从基础到高薪岗位

1. 大模型技术浪潮下的程序员机遇2026年的大模型技术发展已经进入深水区,各大科技公司的招聘需求呈现出明显的结构性变化。根据行业调研数据显示,头部企业的大模型相关岗位薪资同比去年普遍上涨40%以上,部分核心岗位甚至出现薪资翻倍的情况。…

2026/10/9 6:37:40 阅读更多 →
【JAVA毕设源码分享】基于springboot小型家政服务管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

【JAVA毕设源码分享】基于springboot小型家政服务管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/10/9 6:37:42 阅读更多 →

最新新闻

H-Trp-Asn-OH ;175027-11-9

H-Trp-Asn-OH ;175027-11-9

基本性质英文名称:H-Trp-Asn-OH中文名称:色氨酰 - 天冬酰胺二肽CAS 号:175027-11-9类别:线性二肽分子式:C₁₅H₁₈N₄O₄分子量:318.33 Da等电点 pI:约 7.06溶解性:可溶于水、稀醋酸…

2026/10/14 19:12:22 阅读更多 →
Agent记忆实战:场景实测与选型

Agent记忆实战:场景实测与选型

Agent记忆实战:场景实测与选型 腾讯云数据库记忆服务(TencentDB Agent Memory)具备短期记忆上下文压缩卸载与长期记忆四层分层蒸馏、白盒可溯源、一键接入等特点,旨在解决大模型Agent在长会话中Token成本飙升与跨会话认知丢失的问…

2026/10/14 19:12:22 阅读更多 →
档案库房实现自动化的恒温恒湿消毒净化关键:恒湿消毒净化一体机

档案库房实现自动化的恒温恒湿消毒净化关键:恒湿消毒净化一体机

智慧档案十二防系统硬件对接:恒湿净化一体机 RS485 协议对接方案物联网 盛世宏博 十二防对接添加图片注释,不超过 140 字(可选)一、十二防里这台机器“接的是什么”十二防(防潮、防高温、防虫、防霉、防尘、防火、防…

2026/10/14 19:12:22 阅读更多 →
OmniSeek:原生工具集成的多模态推理系统

OmniSeek:原生工具集成的多模态推理系统

1. 项目概述:这不是一个“能听会看”的AI,而是一套让工具真正长进感官里的推理系统OmniSeek这个名字,第一眼容易被误读成“全能搜索”——但如果你真这么理解,就错过了它最颠覆性的设计内核。它不是在现有大模型上叠加语音识别图像…

2026/10/14 19:12:22 阅读更多 →
AI大模型教育行业白皮书撰写指南:从需求拆解到A/B测试验证

AI大模型教育行业白皮书撰写指南:从需求拆解到A/B测试验证

简介:《AI大模型教育行业白皮书》面向教育管理者、教育科技从业者、教研人员及关注AI教育落地的读者,系统梳理教育数字化从教育ICT建设、教育信息化、教育信息化2.0到数智教育时代的演进脉络,帮助读者理解AI大模型在基础教育、人才选拔、高等…

2026/10/14 19:12:22 阅读更多 →
浏览器多开怎么设置独立 IP?Profile、代理与核验流程

浏览器多开怎么设置独立 IP?Profile、代理与核验流程

浏览器多开不会自动让每个窗口拥有不同的公网 IP。想让不同浏览器环境使用不同出口,需要分别配置获授权的代理或网络线路,再从各个环境实际访问网络进行核验。要先分清两件事:Profile 隔离浏览器数据,代理配置影响网络出口。**它们…

2026/10/14 19:11:22 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →