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

羽球搭子 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.setOrCreate<SessionDetail>(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.setOrCreate<SessionSummary[]>('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.setOrCreate<SessionDetail>(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(): Promise<void> { hilog.info(DOMAIN, 'backup', 'system backup callback') await Promise.resolve() } async onRestore(bundleVersion: BundleVersion): Promise<void> { 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 恢复夸大成跨设备备份。

http://www.jsqmd.com/news/1248966/

相关文章:

  • C++后端开发:基于libcpr实现HTTP断点续传的完整方案
  • 如何借助节气装置,实现氩弧焊40%-60%节气率?
  • 每日热门skill:12亿微信用户一夜打通AI!openclaw-weixin到底强在哪?卡兹克带你拆透官方插件
  • 基因编辑载体伯远生物基因编辑载体
  • 冲压自动化解决方案应用科普 智能制造落地优势与工艺适配
  • 连锁回收品牌实力PK|绍兴奢侈品回收优质门店盘点,6家持证机构回款速度与估价方式横评 - 商业资讯新知
  • Agent Skills技术:构建智能编程助手的技能包革命
  • Spring AI Alibaba Graph智能工作流-顺序执行
  • Unity射线检测与LineRenderer可视化实战:从原理到性能优化
  • PyTorch实战:从零构建与优化大语言模型
  • 视频去模糊技术:轻量化DSTNet的创新与实践
  • 前端工程师收藏!2026年大模型风口,你的进阶“逃生”指南
  • 数字人口型同步不是调参游戏!真正决定商业落地的3个硬件感知阈值与2个不可绕过的物理延迟硬边界
  • 深入解析Tiva TM4C123BH6ZRB设备能力寄存器:实现硬件自识别与驱动健壮性
  • CDN能力边界的技术探讨与实践
  • Webpack5 完整性能调优实战:分包、缓存、tree-shaking、压缩、CDN
  • 2026北京彩钢房回收 制冷设备回收 电线电缆回收处理指南 - LYL仔仔
  • 实测 Doubao-Seed-Evolving:把 Windows 桌面图标做成一个会自己运转的小世界
  • 2026津南区低压电气公司哪家好|德力西DZ47S、德力西CJX2S口公司碑推荐,施耐德电气一站式供货公司推荐 - mobible
  • Codex + cc-switch + GPT-5.5 国内免魔法使用教程:从注册 API 到接入 Windows/macOS 桌面版,小白也能看懂
  • TM4C123深度睡眠时钟门控与外设就绪控制实战指南
  • MediaPipe手部追踪与Rerun可视化实战
  • HarmonyOS《柚兔学伴》项目实战19-云数据库——端云数据同步
  • SwiftUI:iOS 常用视图组件速查
  • 数字同事:RPA+AI如何提升团队生产力
  • 静态原生IP代理配置指南:从原理到VMLogin实战应用
  • AI公司技术架构演进与商业化路径全解析
  • 具身视觉语言导航与问答技术解析与应用
  • 帝舵青岛售后门店核验指南|权威公告最新认证网点(2026年7月最新) - 帝舵售后服务中心官网
  • Java面试八股文1000问(2026金九银十版),涵盖基础/网络/算法/设计模式/多线程