【OpenHarmony/HarmonyOS】项目实战:分布式数据同步与本地优先策略设计

摘要

全场景学习体验的基础是数据连续:手机上背过的单词,平板上应该能继续;今天完成的复习,另一个设备不应该重复提醒。本文结合「英语视界 YingYu」项目,分享如何设计分布式数据同步与本地优先策略。🔄

项目中已经有 DistributedSync.etsYingYuPreferences.tsStorageService.tsEntryAbility.ets 的跨端续接能力。本文会围绕这些代码展开,并补充冲突处理和数据分层设计。

一、为什么要本地优先?

学习 App 不能完全依赖网络:

  • 用户可能在地铁、学校等弱网环境;
  • 背单词和复习需要随时可用;
  • 学习数据不应频繁上传;
  • 本地响应更快;
  • 隐私风险更低。

所以推荐策略是:

本地优先
  -> 本地读写
  -> 关键状态同步
  -> 网络或设备可用时再跨端同步

二、Preferences 封装

项目中使用 YingYuPreferences.ts 封装 Preferences:

let pref: preferences.Preferences | null = null

export function initYingYuUserData(context: common.UIAbilityContext): void {
  if (pref) {
    return
  }
  try {
    pref = preferences.getPreferencesSync(context, { name: 'yingyu_user_data_v1' })
  } catch (e) {
    console.error('initYingYuUserData failed:', JSON.stringify(e))
  }
}

读取时优先 Preferences:

export function yingyuPrefGet(key: string): string | undefined {
  if (pref) {
    const v = pref.getSync(key, '') as string
    return v.length > 0 ? v : undefined
  }

  const fallback = AppStorage.get<string>(key)
  if (fallback !== undefined && fallback.length > 0) {
    return fallback
  }
  return undefined
}

写入时支持 AppStorage 兜底:

export function yingyuPrefSet(key: string, value: string): void {
  if (!pref) {
    AppStorage.setOrCreate(key, value)
    return
  }
  pref.putSync(key, value)
  void pref.flush()
}

这是一种稳妥的本地持久化封装。

三、分布式 KV 初始化

项目中的分布式同步初始化:

export async function initDistributedStore(context: Context): Promise<void> {
  const kvManagerConfig: distributedKVStore.KVManagerConfig = {
    bundleName: 'ying.yu.app',
    context: context
  }
  const kvManager = distributedKVStore.createKVManager(kvManagerConfig)

  const options: distributedKVStore.Options = {
    createIfMissing: true,
    encrypt: false,
    backup: false,
    autoSync: true,
    kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
    securityLevel: distributedKVStore.SecurityLevel.S1
  }

  kvStore = await kvManager.getKVStore<distributedKVStore.SingleKVStore>(
    STORE_ID,
    options
  )
}

关键配置:

  • autoSync: true:支持自动同步;
  • SINGLE_VERSION:适合轻量键值状态;
  • backup: false:避免不必要备份;
  • SecurityLevel.S1:适合基础学习数据。

四、哪些数据适合同步?

适合同步:

  • 用户设置;
  • 已学单词 ID;
  • 每日目标;
  • 成就状态;
  • 复习记录摘要;
  • 学习进度;
  • 自定义生词。

不建议同步:

  • 临时 UI 状态;
  • 大量缓存;
  • 可重新计算的数据;
  • 敏感身份信息;
  • 未经用户确认的剪贴板内容。

五、跨设备续接数据

项目中 onContinue() 只传必要状态:

onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {
  const settings = AppStorage.get<string>('yingyu_settings') || ''
  const progress = AppStorage.get<string>('yingyu_progress') || ''
  const learnedWords = AppStorage.get<string>('yingyu_learned_words') || ''

  wantParam['yingyu_settings'] = settings
  wantParam['yingyu_progress'] = progress
  wantParam['yingyu_learned_words'] = learnedWords
  wantParam['yingyu_timestamp'] = Date.now().toString()

  return AbilityConstant.OnContinueResult.AGREE
}

目标设备恢复:

if (settings) AppStorage.setOrCreate('yingyu_settings', settings)
if (progress) AppStorage.setOrCreate('yingyu_progress', progress)
if (learnedWords) AppStorage.setOrCreate('yingyu_learned_words', learnedWords)

这体现了“最小必要”原则。

六、建议增加数据版本号

随着项目升级,数据结构可能变化。建议给本地数据增加版本:

interface SyncPayload<T> {
  version: number
  updatedAt: number
  deviceId: string
  data: T
}

例如用户设置:

const payload: SyncPayload<UserSettings> = {
  version: 1,
  updatedAt: Date.now(),
  deviceId: currentDeviceId,
  data: getUserSettings()
}

这样后续可以做迁移和冲突判断。

七、冲突处理策略

学习数据可能在多个设备上同时修改。常见策略:

1. 最后写入优先

适合设置项:

dailyGoal
notificationsEnabled
soundEnabled
uiVersion

谁更新时间更晚,就采用谁。

2. 集合合并

适合已学单词:

const merged = Array.from(new Set([...localLearned, ...remoteLearned]))

学习过的单词不应该因为设备冲突而丢失。

3. 按日期合并

适合学习进度:

date -> LearningProgress

同一天数据可以取更大值或合并词数。

4. 成就只增不减

成就一旦解锁,不应该因为同步冲突被锁回去。

unlocked = local.unlocked || remote.unlocked

八、同步触发时机

项目当前在后台和销毁时调用同步:

onBackground(): void {
  syncToRemote().catch(() => {})
}

onDestroy(): void {
  syncToRemote().catch(() => {})
}

后续还可以在这些时机触发:

  • 完成今日任务;
  • 完成复习;
  • 修改设置;
  • 添加生词;
  • 解锁成就;
  • 应用进入后台。

不要每次输入都同步,避免过于频繁。

九、同步状态提示

可以在设置页或个人中心展示同步状态:

interface SyncStatus {
  lastSyncAt?: string
  syncing: boolean
  failed: boolean
  message: string
}

文案示例:

  • 数据已同步;
  • 正在同步学习进度;
  • 当前离线,稍后自动同步;
  • 同步失败,请稍后重试。

用户知道数据状态,会更安心。

十、隐私注意点

分布式同步也要遵循隐私原则:

  • 只同步学习必要数据;
  • 不同步敏感身份信息;
  • 不同步原始剪贴板内容;
  • 不同步完整 AI 输入日志;
  • 提供关闭同步入口;
  • 提供清除本地数据入口。

学习项目的数据虽然看起来不敏感,但长期学习记录也能反映用户习惯,需要谨慎对待。

十一、小结

本文结合「英语视界 YingYu」项目,梳理了分布式数据同步和本地优先策略:

  • 使用 Preferences + AppStorage 做本地持久化;
  • 使用 DistributedKVStore 预留跨设备同步;
  • onContinue() 只传必要学习状态;
  • 设置适合最后写入优先;
  • 已学单词适合集合合并;
  • 成就状态只增不减;
  • 同步应绑定关键学习事件;
  • 数据同步必须控制隐私边界。

全场景体验的底层不是“页面能打开”,而是数据能自然跟着用户走。对学习项目来说,本地优先 + 轻量同步,是更稳也更可信的方案。🔄

img

img

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐