鸿蒙 ArkTS 离线优先笔记手记:本地持久化与云端增量同步的取舍

举报
yd_215301972 发表于 2026/11/14 01:28:39 2026/11/14
【摘要】 笔记应用看起来比待办清单更轻,写几行字、存几个文件就能跑起来。可是一旦加上“离线也能写、换设备接着写、断网回来不丢字”这三个要求,它立刻变成一道很扎实的端云一体练习题:端侧要保证任何时候都能打开、能编辑、能保存;云端要保证多台设备的数据最终收敛到同一份真相;中间的网络随时可能中断、重复、乱序。这篇手记记录一次鸿蒙端云一体离线优先笔记的实战过程:端侧用 ArkTS 与 ArkUI,本地用文件直...

笔记应用看起来比待办清单更轻,写几行字、存几个文件就能跑起来。可是一旦加上“离线也能写、换设备接着写、断网回来不丢字”这三个要求,它立刻变成一道很扎实的端云一体练习题:端侧要保证任何时候都能打开、能编辑、能保存;云端要保证多台设备的数据最终收敛到同一份真相;中间的网络随时可能中断、重复、乱序。这篇手记记录一次鸿蒙端云一体离线优先笔记的实战过程:端侧用 ArkTS 与 ArkUI,本地用文件直读做持久化,云侧用 FunctionGraph 云函数加 API 网关做增量同步,重点讲数据模型、同步协议与几个真实踩过的坑。

一、为什么笔记适合练离线优先

离线优先的意思是:没有网络时应用照常用,有网络时再悄悄同步。它和“在线应用加个缓存”正好反过来——缓存是锦上添花,离线优先是把本地当成第一数据源。笔记特别适合练这个,因为它的写操作高频、单条数据小、冲突面窄:一篇笔记同一时间通常只有一台设备在改,冲突可以靠版本号与更新时间解决,不必一开始就上复杂的协同编辑算法。

我给这次实战定了三条验收线。第一,断网时新建、编辑、删除笔记全部可用,重启应用后数据还在;第二,联网后离线期间的修改全部同步到云端,不重复、不丢失;第三,两台设备各改各的,同步后两边看到同一份内容,冲突的那一篇有明确的裁决结果,而不是悄悄丢掉一边的修改。这三条线定下来之后,架构选型就有了判断标准:任何让第一条变难的方案都不选。

二、整体架构:端侧是缓存,云端是裁决

架构分三层。端侧是 HarmonyOS 应用,ArkTS 写业务逻辑,ArkUI 写声明式界面,本地文件是第一数据源,界面只读本地数据。同步层是独立的模块,负责把本地变更排队发往云端,再把云端的增量拉回来合并到本地。云侧入口是 API 网关,对外只暴露同步相关的几个接口;网关后面是 FunctionGraph 云函数,数据存在云数据库里。

这条链路的职责要分清楚:端侧负责交互与离线体验,云端负责存储与裁决,同步层负责在两者之间搬运变更。界面永远不直接等网络返回,打开笔记列表先读本地文件立即渲染,同步在后台进行,成功了刷新一下,失败了下次再试。用户感知到的就是“随时能写”,而不是“先转圈再写字”。

三、数据模型:笔记、版本与删除标记

笔记表只保留必要的字段:笔记编号、归属用户、标题、正文、创建时间、更新时间与版本号。版本号是关键:每次编辑版本号加一,同步时云端拿版本号判断这条变更是不是最新的。更新时间用 UTC 时间戳,只做排序与展示,不做唯一的裁决依据——两台设备的时钟可能差几秒,单靠时间裁决会误判。

删除用软删除:删除操作只把笔记标记为已删除并更新版本号与更新时间,不真正删掉记录。这样做的好处是同步时删除本身也是一条变更,能传到另一台设备;如果直接在本地删掉记录,同步层就不知道这条笔记曾经存在过,另一台设备上的旧版本会在下次同步时“复活”。软删除的记录定期清理,清理只发生在确认所有设备都已收到删除变更之后。

四、本地持久化:文件直读、索引与快照

本地持久化我沿用了之前数独案例里验证过的做法:用 @ohos.file.fs 直接读写 JSON 文件,每次读取都取磁盘最新值,不走带单例缓存的偏好设置。笔记列表存在一个索引文件里,只存编号、标题、更新时间与版本号;每篇笔记的正文单独存一个文件。列表页只需要读索引文件就能渲染,不用把所有正文都读进内存。

写操作分两步:先写正文文件,再更新索引文件,最后把这条变更记进待同步队列。顺序不能反——先记队列再写文件,万一写文件失败,队列里会多一条不存在的变更;先写文件再记队列,失败时最多是这次修改没进队列,下次编辑还会再进一次。队列本身也要落盘,应用被杀掉重启后,未同步的变更不能丢。

五、增量同步协议:拉取游标与推送队列

同步分两个方向。推送是把本地待同步队列里的变更按顺序发往云端,每条变更带一个幂等键,云端用幂等键去重,网络重试导致重复发送时不会产生两条记录。拉取是向云端要“自上次游标之后变了什么”,云端按更新时间返回增量列表与新的游标,端侧把增量合并进本地文件,再把游标存下来。下一次拉取从这个游标继续,全量拉取只在首次登录或本地数据损坏时才做。

同步的触发点有三个:应用启动后联网时同步一次;用户主动下拉刷新时同步一次;本地产生新变更且网络可用时,延迟几秒再同步一次,避免用户连续打字时每敲一个字都发一次请求。同步失败不弹窗打断用户,只在列表顶部留一个很轻的“同步失败,稍后重试”提示,后台按退避间隔重试。

六、冲突处理:字段级合并与后写胜出

同一篇笔记在两台设备都被改过,同步时就会撞上。我的处理分两步:先比版本号,版本号大的胜出;版本号相同再比更新时间,更新时间大的胜出。胜出的一方覆盖另一方,失败的一方不直接丢弃,而是把它的正文另存为一篇名为“原标题(冲突副本)”的新笔记,留在列表里让用户自己决定要不要合并回去。

字段级合并只用在标题与正文分开改的场景:如果一台设备只改了标题,另一台只改了正文,两边的变更可以各取所需合并成一篇。但这个优化要小心,正文的合并不能按行拼,笔记正文不是代码,按行合并很容易拼出读不通的句子。所以我的规则很保守:只有标题与正文互不相干时才做字段级合并,其余一律整篇裁决加冲突副本兜底。

七、云函数分工:四个函数管同步

云侧只用了四个 FunctionGraph 云函数。推送函数接收一批变更,按幂等键去重后写入数据库;拉取函数按游标返回增量;注册与登录两个函数管账号,端侧只保存令牌,不保存密码。网关做鉴权、路由与限流,拉取接口的限额单独设得宽一点,推送接口设得严一点,防止离线攒了几百条变更的设备一次把函数打满。

函数全部无状态,不缓存用户数据,每次调用都从数据库读最新值。返回体统一格式:成功返回业务数据与新游标,失败返回错误码与可读提示。端侧按错误码分支:鉴权失败先刷新令牌再重试一次;参数错误直接记日志不重试;服务端错误等退避间隔后再试。

八、全文检索:端侧先行,云端补齐

笔记多了之后,找一篇旧笔记比写一篇新笔记更花时间。检索分两层:端侧在索引文件里按标题做前缀匹配,响应快、无网络也能用;正文全文检索也在端侧做,打开应用时在后台把索引文件里的关键词表建好,搜索时先查关键词表再定位正文。云端检索只作为补充,给新设备首次同步完成后重建索引用。关键词表要控制体积,只对标题与正文前两千字建索引,超长的笔记只索引前面的部分,并在界面上注明“仅检索了正文前部”。

九、容易踩的坑

第一个坑是编辑器里的光标跳动。同步合并在后台改了本地文件,编辑页如果正打开着这篇笔记,界面一刷新光标就会跳到开头。解决办法是:正在编辑的笔记不同步覆盖,同步层先把它加进“编辑中”名单,等用户保存并退出编辑页后再合并,合并前先提示“云端有新版本,是否查看”。

第二个坑是版本号回退。新设备首次登录拉取全量时,如果它本地恰好有一篇同名笔记的旧草稿,版本号比云端小,推送时会把云端的新版本覆盖成旧版本。解决办法是:首次同步只拉取不同步推送,等拉取完成、本地与云端对齐之后,再把本地新增的草稿作为新变更推送,顺序一定是先拉后推。

第三个坑是队列越攒越多。离线一周回来,待同步队列里可能有上百条变更,一次性全推过去既慢又容易超时。解决办法是按笔记编号合并队列:同一篇笔记的多次编辑在推送前合并成一条最终状态的变更,只发最后一次的结果,中间过程不需要同步。

十、验证清单

做完之后,我按这份清单逐项验证:断网新建三篇笔记,重启应用后三篇都在;联网后三篇出现在云端,且没有重复;两台设备各改同一篇笔记的不同段落,同步后胜出方完整,失败方以冲突副本保留;断网删除一篇笔记,联网后另一台设备上这篇笔记消失而不是复活;新设备登录后先拉取再推送,云端数据没有被旧草稿覆盖。五项全部通过之后,我才认为这次离线优先的闭环是真的跑通了。

十一、小结

离线优先笔记的核心不是同步算法有多复杂,而是把三件事定下来:本地是第一数据源,界面永远先读本地;变更先落盘再进队列,队列按笔记合并、带幂等键推送;冲突用版本号裁决,失败方留冲突副本而不是悄悄丢弃。这三件事定下来之后,再复杂的网络情况都有章可循:断网能写、弱网能等、多设备能收敛。下一步我打算给它加上附件同步:图片先存本地、后台慢慢上传,上传失败不阻塞正文同步,正文与附件走两条队列,互不拖累。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。