纹渊 HarmonyOS 7 工程实战(06):distributedKVStore 收藏与最近浏览同步
2026/7/21 17:58:08 网站建设 项目流程

一、问题与目标

收藏与最近浏览都属于体量很小、操作频率很高的状态。只保存在单机上时,手机里刚收藏的纹样无法在平板继续使用;全部依赖分布式存储时,设备未组网、权限未授予或 KV 服务暂时不可用,又会拖累本机体验。比较稳妥的做法是保留本地 Preferences 作为兜底,再把需要跨设备的 ID 列表同步到distributedKVStore

这个方案需要解决四个具体问题:KVStore 只能初始化一次;收藏与最近浏览必须使用稳定且互不干扰的键;远端变更到达后页面要及时刷新;同步失败不能清空已经存在的本地数据。HarmonyOS 分布式键值数据库的接口和约束可在分布式数据对象与键值数据库指南中查阅。

二、实现思路

数据链路分为本地层、分布式层和页面层。Preferences 保存本机可立即读取的副本;SingleKVStore保存允许跨设备传播的收藏 ID 与最近浏览 ID;页面只消费数组,不直接操作数据库实例。这样即使远端暂时不可用,收藏页仍能读取本地副本,不会因为同步服务失败而变成空白页。

环节输入与输出关键约束
页面交互长按收藏、打开详情只提交纹样 ID,不持有 KVStore
本地持久化JSON 字符串与 ID 数组写入后立即flush,保证离线可用
分布式同步favorite_idsrecent_ids两类状态分键保存,避免互相覆盖
数据回流dataChange通知只响应相关键,再重新加载页面数组
失败降级null或异常保留本地副本,不用空数组覆盖有效数据

最近浏览还需要限制长度并去重。每次打开详情时先移除旧位置上的相同 ID,再把它插入数组头部,最后截取前 20 项。这样同步的数据量始终可控,多个设备看到的顺序也能表达“最近一次访问优先”。

三、关键实现

3.1 串行化 KVStore 初始化

页面显示、订阅注册和首次数据迁移可能同时请求 KVStore。如果每个入口都调用createKVManager,容易重复创建实例或重复绑定监听。服务层使用storeReadystoreCreating表示初始化状态;创建中的请求短暂等待同一个结果,后续请求直接复用已有实例。

async function ensureStore(context: common.UIAbilityContext): Promise<distributedKVStore.SingleKVStore | null> { if (storeReady && kvStore !== null) { return kvStore } if (storeCreating) { for (let i = 0; i < 20; i++) { await new Promise<void>((resolve) => setTimeout(resolve, 100)) if (storeReady && kvStore !== null) return kvStore } return null } storeCreating = true try { const manager = await distributedKVStore.createKVManager({ bundleName: context.abilityInfo.bundleName, context }) kvStore = await manager.getKVStore('wenyuan_sync_v2', { createIfMissing: true, encrypt: false, autoSync: true, kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION, securityLevel: distributedKVStore.SecurityLevel.S1 }) storeReady = true return kvStore } catch { kvStore = null storeReady = false return null } finally { storeCreating = false } }

返回值允许为null是刻意保留的降级边界。上层可以继续使用本地 Preferences,而不是把初始化异常扩散到页面生命周期。同步开关开启时还要申请ohos.permission.DISTRIBUTED_DATASYNC;权限申请结果与 KVStore 初始化结果都应分别处理,不能把“用户暂未授权”误判为本地数据损坏。

3.2 分键存储与安全解析

收藏和最近浏览都使用数字 ID 数组,但语义不同,因此分别写入favorite_idsrecent_ids。数组序列化成 JSON 字符串后进入 KVStore,读取时必须验证类型和元素范围,避免旧版本数据或异常值进入页面。

function parseIds(raw: string): number[] { try { const values = JSON.parse(raw) as number[] if (!Array.isArray(values)) return [] return values.filter((id: number) => typeof id === 'number' && Number.isInteger(id) && id > 0) } catch { return [] } } async function saveIds(key: string, ids: number[]): Promise<void> { const store = await ensureStore(uiContext) if (store === null) return await store.put(key, JSON.stringify(ids)) await store.sync([''], distributedKVStore.SyncMode.PUSH_PULL) }

保存时先更新本地副本,再尝试写入分布式副本。远端写入失败只影响跨设备传播,不撤销用户刚完成的收藏操作。显式触发PUSH_PULL可以尽快交换两端数据,autoSync则负责后续自动同步,两者共同缩短页面看到远端变化的等待时间。

3.3 订阅相关键并回读

订阅使用SUBSCRIBE_TYPE_ALL接收插入与更新通知,但回调不会无条件刷新整个页面。先合并insertEntriesupdateEntries的键,再判断是否包含收藏或最近浏览;只有命中相关键时才调用页面提供的刷新函数。

store.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL, (change: distributedKVStore.ChangeNotification) => { const keys = change.insertEntries .concat(change.updateEntries) .map((entry: distributedKVStore.Entry) => entry.key) if (keys.some((key: string) => key === 'favorite_ids' || key === 'recent_ids')) { reloadFavoriteAndRecent() } })

回调只发出“数据已经变化”的信号,不直接修改@State数组。页面随后通过统一加载入口回读分布式数据、刷新本地副本,再更新 UI。这个顺序避免订阅回调和用户点击同时改写数组,也让页面重新进入时复用同一套加载逻辑。

四、失败场景与取舍

首次开启同步时,远端可能还没有数据,而本地已经积累收藏。此时只有在远端值不存在或为空、且本地数组非空时,才把本地副本推送到分布式存储。不能简单执行“双向覆盖”:空远端覆盖本地会丢数据,旧远端无条件覆盖本地也可能让用户刚完成的操作消失。

场景处理方式页面结果
KVStore 创建失败返回null,继续读取 Preferences收藏和最近浏览仍可使用
没有可信设备或网络不可用保留本地写入,记录同步失败当前设备操作立即生效
远端首次为空仅推送非空本地数组避免首次开启同步后数据消失
收到无关键变化不触发页面回读减少不必要的重绘
JSON 损坏或类型错误解析为安全空数组页面不崩溃,可继续产生新记录
重复注册订阅用绑定标志拦截一次远端变化只刷新一次

SINGLE_VERSION适合这种结构简单、数据量小的状态,但它不会自动理解“收藏集合合并”或“最近顺序冲突”的业务含义。当前实现采用后到数据覆盖同一键的简单策略,优点是行为清晰;如果后续需要多端同时编辑,应把收藏改成带操作时间的集合,把最近浏览改成带时间戳的记录,再在应用层完成去重和排序。

五、验证步骤

  1. 在设备 A 收藏两个纹样并依次打开详情,确认收藏区有两项,最近使用按最后访问顺序排列。
  2. 开启“跨设备同步(收藏/最近)”,授予分布式数据同步权限,再在设备 B 使用同一可信设备组进入收藏页。
  3. 在设备 B 取消一项收藏,观察设备 A 是否通过变更订阅刷新;重复切换页面,确认没有重复回调导致的闪烁。
  4. 断开设备连接后继续在设备 A 收藏纹样,确认本机立即更新;恢复连接后检查新增 ID 是否同步到设备 B。
  5. 清除远端初始数据但保留本地 Preferences,重新开启同步,确认本地非空列表被推送,而不是被空数据覆盖。
  6. 构造非法 JSON 或无效 ID,确认解析逻辑过滤异常值,收藏页仍能正常进入和继续操作。

六、总结

收藏与最近浏览的跨设备同步不应取代本地持久化,而应建立在本地可用的基础上。Preferences 负责离线体验,SingleKVStore负责跨设备传播,变更订阅负责通知页面回读,初始化锁和安全解析负责守住失败边界。把这几层职责分开后,同步服务不可用时页面仍可操作,远端数据到达时界面也能稳定刷新。

对于更复杂的多端并发场景,下一步重点不是继续增加页面判断,而是为数据补充时间戳、版本或操作记录,并明确冲突合并规则。只有让“空数据、旧数据和并发数据分别怎样处理”成为可验证的约定,跨设备状态才能长期保持一致。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询