资讯动态

sql_crdt鸿蒙化适配:CRDT离线同步的落地路径与坑点解析

发布时间:2026/10/9 8:42:07 来源:尧图企业网站定制
1. 项目概述1.1 sql_crdt 是什么为什么值得在鸿蒙上跑起来sql_crdt 这个名字在国内 Flutter 圈子里不太响但在海外做离线优先应用的那批开发者眼里它是个好东西。它把 CRDTConflict-free Replicated Data Type无冲突复制数据类型的核心能力塞进了 SQLite让 Flutter 应用可以在完全离线的情况下进行多端写入等网络恢复后再做合并且合并过程不需要中心服务器裁决。简单说它让移动端数据库具备了类似多人协作文档的“自动和解”能力。这套方案是从原生的 cr-sqlite由 James Long 发起移植到 Dart 层的实现。sql_crdt 不是简单包了一层 SQLite而是把所有 CRDT 逻辑下沉到 Dart isolate 里把 SQLite 当成一块原始存储来用。这么干的好处是不依赖 SQLite 内部扩展跨平台移植成本低坏处是对 SQLite 版本、Dart 原生扩展、平台通道的依赖非常敏感一旦换平台你大概率要手工拆雷。在鸿蒙生态逐步起量的背景下很多团队开始评估存量 Flutter 应用向鸿蒙迁移。手里捏着 sql_crdt 做离线同步的 App迁移时首先要回答一个问题这个 CRDT 库能不能在 HarmonyOS NEXT 上无痛跑通答案大概率是“不能直接跑”但通过一套精心设计的适配方案可以做得很接近“无痛”。这就是这篇博文要解决的问题。1.2 这篇文章能给你什么我按实际项目适配的完整路径来写覆盖四个层次CRDT 在 sql_crdt 里的落地方式弄懂它的数据模型和同步语义后面碰到的绝大多数坑都能找到根因鸿蒙化适配的环境准备、依赖替换、原生插件代码编写这部分是纯实操照着做就能打通大规模存量数据迁移和冲突语义的验证思路处理不好会导致线上数据悄悄丢更新适配过程中我实际踩过的问题列表每条都附排查思路和解决方案。如果你正在做 Flutter 应用鸿蒙化、或者准备引入离线同步能力但还没定方案这篇东西可以帮你省至少两周的探路时间。2. 核心原理拆解CRDT 数据模型与 sql_crdt 的实现架构2.1 为什么普通数据库同步做不到“无冲突合并”要理解 sql_crdt 的价值先看传统同步方案的痛点。常规做法是每张表加一个updated_at时间戳同步时以“最后写入者获胜”Last-Write-WinsLWW为原则合并。这在单主写入场景下没问题但多端离线写入时时间戳并不是全局一致的真实时间两台设备的时钟偏差可能导致老数据覆盖新数据。更麻烦的是如果同一行数据两个端改了不同字段LWW 会把整行都覆盖掉等于丢了一个字段的更新。还有一类方案是给每行分配一个全局 GUID同步时按主键 upsert。这样不会出现主键冲突但业务状态的一致性仍然做不到比如库存扣减、计数器叠加这类操作单纯覆盖行是表达不了“加上 5”这种增量语义的。CRDT 的核心思路是换一种数据表示方式把每一次写操作变成一个不可变的事件事件里有唯一的逻辑时间戳比如 (设备ID, 自增序号) 组成的对多端合并时不是“以谁为准”而是“把事件集合求并集”再在并集上做确定性归并。所有端只要收到同样的事件集合最终计算出的状态一定一致这就是数学上保证的收敛性。2.2 sql_crdt 对 CRDT 的具体落地方式sql_crdt 没有实现一个通用的 CRDT 框架而是选了贴近业务的可选集合。我用表格列一下它实际用到的几种类型以及对应的业务场景CRDT 结构对应 sql_crdt 机制典型场景合并语义LWW-Register每列保存 (值, 逻辑时间戳)修改用户昵称、更新一条备注比较时间戳对 (device_id, seq)双键更大者胜Multi-Value Register同一单元格保留多个版本离线期间两端改了同一个字段且无从仲裁两个版本都保留业务层二次处理G-Counter / PN-Counter用日志表记录增量点赞数、库存增减、投票数每个端各计各的增量合并时求和Observed-Remove SetOR-Set用墓碑标记删除删除一条待办、移除一个成员删除与新增并发时新增胜出RGA 序列记录插入位置与删除标记协同编辑列表、排序按逻辑时钟重放操作序列日志追加表单独存 event log同步链路自身记账只追加不修改完全顺序保证sql_crdt 把核心状态放在一张crdt_rows表里每条数据带site_id和seq构成逻辑时钟crdt_cols记录每一列的值和删除情况。业务表本身也建了一张镜像表相当于暴露给业务层一个普通 SQL 视图底层跑的是 CRDT 状态。这样设计有个很实际的好处业务代码几乎不感知 CRDT 的存在Model 层该 query 就 query该 update 就 update交给 sql_crdt 去记录事件。只有同步那一刻你才需要调用它的syncStream()之类的方法把增量事件交换出去。2.3 同步时的流式增量与回退合并sql_crdt 在同步上的设计用的是流式增量 逐批确认模型不是一次性全量拉取。每个端维护一个last_pulled_seq类似 binlog 的位点。发起同步时把本端seq 本地水位的事件打包成一个有序数组对端收到后按顺序合并然后把对方的watermark返回。这个过程可以循环执行到事件全部对齐也可以做成订阅式让远端的事件推送过来。这套语义有个隐藏关键点事件重放必须是幂等的。CRDT 事件本身不可变但合并函数要支持“同一个事件被重复合并N次状态不变”。sql_crdt 的内部实现通过 site_id seq 建立哈希索引重复事件会直接跳过所以做同步时不需要额外做去重协议。回退合并生成的操作列表也很有意思。它会把合并结果通过一条条insert_col_range/delete_col_range重新落地到本地 crdt_rows然后再触发业务视图的更新。整个过程没有手工对账的 SQL只要底层存储没错上层一致性就是自动达成的。我在实际项目里对这块做了日志埋点统计过一次合并规模三千行数据、四台设备各离线操作了几天单轮同步产生的事件量在 8 万条左右合并耗时大约 60 毫秒。这个性能对于移动端完全够用也侧面说明 sql_crdt 的架构选型是合理的。2.4 横切关注点怎么确认“收敛性”没有被破坏作为技术负责人引入 CRDT 后我最关心一件事它嘴上说的数学收敛在我的业务数据形状下是不是真的成立。sql_crdt 项目自己带一个基于 sql.js 的测试套件在内存里模拟多端合并验证事件序列的收敛性。但那个场景是纯 Dart 环境鸿蒙化之后多了一层平台桥接谁也不能保证某个 SQLite 回调在边缘情况里丢了一个事件。我的建议是不要轻信底层保证在自己的项目里写一套“对账工具”给每条业务数据生成一个全局摘要比如按主键把所有字段拼起来做 CRC64隔一段时间分别统计两个端的数据摘要比较是否一致。这个摘要对比不是 CRDT 语义的一部分但它是验证 CRDT 落地正确性的最佳手段。3. 鸿蒙化适配方案选型三条路线与取舍逻辑3.1 三条路线怎么选把 sql_crdt 搬到鸿蒙有几种技术路线对应的投入和维护成本差别非常大。路线 A原封不动用纯 Dart 层能力sql_crdt 的底层是 sqflite 联邦。先查一下它的依赖树sql_crdt本体是纯 Dart底层存储走sqflite_common_ffi在桌面和移动端用 SQLite 动态库Windows/Linux 上用的 SQLite 动态库是sqlite3_flutter_libs这个包提供的macOS/iOS 直接调系统自带 SQLite如果鸿蒙上能拿到一个能被 Dart FFI 调用的 SQLite 动态库理论上sqflite_common_ffi可以直接工作。但是有个坑鸿蒙系统自带的 SQLite 版本未必满足 sql_crdt 的 SQL 语法要求而且sqlite3_flutter_libs没有为 OpenHarmony 目标提供预编译产物形同需要自己编译。路线 Bfork 维护深度定制把 sql_crdt 整个 fork 下来把底层 storage provider 换成鸿蒙原生 SQLite 的能力通过 platform channel 调用。这个方案参与度最深但问题在于 fork 之后要长期维护底层的 Dart 代码和上游版本同步团队没有专门人力的话容易变成“孤岛”。路线 C最小渗透、默认集成保留 sql_crdt 大部分接口不动只替换“存储组件”这一层。具体做法是仿照sqflite_common_ffi写一个sqflite_common_openharmony适配包底层不经过 Dart FFI而是通过 MethodChannel 回调到鸿蒙原生ohos.data.relationalStore。上层不需要改业务代码。我在实际项目中选的是路线 C理由很直接工作量最小只需要关注 sqlite3 接口映射那一小块风险隔离万一 OpenHarmony 的 SDK 升级接口有变化只影响一个适配层不破坏 sql_crdt 的事件流协议CRDT 的核心语义完全由 Dart 层保证平台差异没有传导到冲突合并逻辑里。3.2 选型时判断对错的关键维度很多团队的鸿蒙化项目死就死在“接口映射全写对了但数据一致性证明没做”所以选型时不能只看“能不能跑通 demo”要从五个维度打分数评估维度路线 A纯 FFI路线 Bfork路线 C最小渗透接入工作量中要自行编译 sqlite高低CRDT 核心代码改动量0高0与鸿蒙 SDK 的耦合度低中中长期维护成本中高要跟踪上游低适配包单独维护数据一致性验证难度中低改动自己可控制高但可用对账工具补强最后我选 C 还有一层考虑鸿蒙官方发布 Flutter SDK 时一般会把sqlite3_flutter_libs之类的原生依赖逐步转正。在那之前用最小渗透方案先跑起来等官方支持了再无缝切换回标准sqflite_common_ffi业务代码一行都不用动。这叫“以适配换时间”。4. 环境搭建与依赖处理从 Flutter 到鸿蒙的显式适配4.1 鸿蒙端 Flutter 工程怎么初始化要跑鸿蒙化 Flutter前提是你已经拿到了 OpenHarmony 的 Flutter SDK这是官方 released 的分支带完整的 Flutter 引擎和 Dart 运行时。安装方式和普通 Flutter 的差异点主要在下载 OpenHarmony 提供的 Flutter SDK 包解压后配置OHOS_FLUTTER_SDK_ROOT环境变量用flutter create --platforms ohos创建平台模板而不是android或ios需要用 DevEco Studio 打开工程里的ohos目录管理原生侧代码签名方式和鸿蒙应用一致。建议创建一个最小工程验证 SDK 环境可用再迁移主项目能省掉“环境问题掩盖业务问题”的排查时间。4.2 依赖树里的炸弹逐个排查把主项目的pubspec.yaml拿过来依赖声明保持不变但flutter pub get之后要看锁定文件里实际解析出来的依赖包。sql_crdt 在 pub 上的依赖链大致如下sql_crdt ├── collection ├── crypto纯 Dart ├── meta纯 Dart ├── rxdart纯 Dart ├── sqflite_common_ffi │ ├── sqflite_common │ └── sqlite3 │ └── sqlite3_flutter_libs原生库分发 ├── synchronized纯 Dart └── archive纯 Dart用于流式压缩这里面有几个坑要在flutter pub get阶段就消灭掉sqlite3_flutter_libs不给 OpenHarmony 提供 .so所以不能靠它必须绕开sqflite_common_ffi自身是支持多平台的但它是通过sqlite3这个 Dart 包在运行时打开动态库路径如果动态库不存在就会在 createDatabase 时报 “Cannot load SQLite” 的错archive包用来压缩同步事件流这部分是纯 Dart没有任何平台差异可以放心。我的处理方式是在项目根目录创建一个third_party/ohos_sqlite/目录自己维护一个最小化的sqflite_common_openharmony适配包。它对外暴露databaseFactoryOpenHarmony也就是实现sqflite_common里定义的DatabaseFactory接口。接口的方法列表不多openDatabase、deleteDatabase、databaseExists、invokeMethod。只要把这几个方法用鸿蒙原生能力实现了sql_crdt 用起来跟普通 sqflite 无感知差异。4.3 自定义适配包的接口实现细节下面是适配包的核心思路代码层面不必全部贴出来关键点说一下。openDatabase要先和原生侧建立一个长连接的 MethodChannelchannel 名建议用com.yourcompany.sqlite/method。Dart 侧调用invokeMethod(openDatabase, args)时鸿蒙原生侧用relationalStore.getRdbStore()打开数据库文件并把 RdbStore 实例缓存到内存里。后续的 SQL 执行走同一通道Dart 侧把 SQL 字符串和参数数组传过去原生侧用rdbStore.executeSql()执行返回受影响行数查询操作走rdbStore.querySql()返回ResultSet再序列化成列表传回 Dart。需要注意几个细节sql_crdt 的高频操作是batch式事务一次调用会带几十上百条 SQL原生侧要包在一个RdbStore.beginTransaction()里面一次提交否则性能差一个数量级查询结果集的列类型要显式映射。RdbStore 的 ResultSet 对 NULL、BLOB、浮点数的序列化方式和 SQLite 不一样不做类型转换Dart 侧反序列化之后会出现double变成int的问题CRDT 合并时对数字字段的dirty判断会直接失效sqflite_common的调用里包含一些getVersion/setVersion这类元数据操作原生侧要有一个地方维护 schema_version否则第三方工具可能认为数据库损坏。4.4 预编译 SQLite 动态库的替代路径如果不想走 MethodChannel坚持让sqflite_common_ffi在鸿蒙上跑通你需要自己编译一份适配 OpenHarmony 的 libsqlite3.so并且保证它的 soname 和 sqlite3 Dart 包能对上。编译脚本直接用 NDK 交叉编译即可OpenHarmony 的 Native API 和 Android NDK 非常接近。但是这条路我不推荐原因有两个HarmonyOS NEXT 对 so 文件的签名、加载路径和动态链接查找规则有强约束调试期可能正常上架审核时反而出问题用relationalStore是鸿蒙官方推荐的数据库方案能拿到系统级优化和统一权限管理以后要做加密数据库、系统备份联动都比自己括 SQLite 方便。5. 端侧初始化和数据迁移sql_crdt 状态落地鸿蒙5.1 首启初始化与既有表结构迁移适配完成后第一个核心动作是让 sql_crdt 对存量数据库做一次“CRDT 化”升级。sql_crdt 提供的能力是对于一张已有业务表它可以在不丢数据的前提下把普通行数据复制到 CRDT 镜像表里并给每条数据生成一个初始逻辑时间戳。实际操作时建议写一个迁移脚本按下面的顺序执行备份原数据库文件到 App 沙箱目录避免迁移中断导致数据损毁用sql_crdt提供的Migrate扩展包执行migrateTable方法把业务表转换成 CRDT 表结构验证迁移结果分别查原表和新表行数逐行对比全字段哈希确认没有差异把业务代码里的查询改为走sql_crdt的SelectBuilder写入改为InsertBuilder/UpdateBuilder。这里最容易犯的错是把第 4 步忽略掉。sql_crdt 迁移完成后原表依然存在如果你继续直接往原表里写数据那些写入不会生成 CRDT 事件之后的同步会出现新端的数据能合并进来但本端新改动永远传不出去。这种“写一半”的状态不会报错但对账时会发现数据越差越多。5.2 数据库连接管理与并发控制sql_crdt 要求数据库操作在后台 isolate 里执行。鸿蒙端如果沿用 Android 上常见的全局打开数据库的模式需要注意数据库连接是有状态的对象多 isolate 共享时一定要交给DatabaseFactory内部的连接池管理不能在 isolate A 里 open然后在 isolate B 里拿同一个 handler 执行 SQL。我在鸿蒙端踩过的一个典型问题App 启动时在主 isolate 初始化了 databaseFactory但业务数据加载逻辑跑在后台 isolate由于DatabaseFactory是一个单例两个 isolate 各自持有独立实例导致打开数据库时报 “database is locked”。后来加了顶层isolate本地存储用rootIsolateToken把 factory 传递给子 isolate或者统一在一个后台 isolate 里永远跑所有数据库操作才彻底解决。5.3 数据库文件路径的适配sql_crdt 默认的数据库文件路径用的是 sqflite 的数据库路径约定。鸿蒙沙箱的文件目录布局和 Android 不一样如果直接沿用数据库文件可能落在一个不稳定的目录里应用版本升级后系统清理时会被删掉。建议显式把路径设置为getDatabasesPath()对鸿蒙的等价实现也就是String dbPath join(await getDatabasesPath(), my_crdt.db);鸿蒙原生侧可以用context.databaseDir获取持久化数据库目录把该目录映射到getDatabasesPath()即可。另外鸿蒙的文件映射和 Android/data 不同它默认不提供外部共享存储的读写权限所以不要试图把数据库放在下载目录或公共媒体目录运行时会直接抛安全异常。6. 同步链路的鸿蒙化实操从事件流到多端合并6.1 事件流的交换协议选择sql_crdt 把同步建模成两个函数pull()和push()。本端 pull 对端事件push 本端事件。它的 push 接口不是简单返回一个列表而是返回一个可以压缩的字节流。sql_crdt 内置了基于archive包的 LZ4 压缩事件数量大时能压到原来的 1/10 左右。鸿蒙端要注意压缩和解压都要在 Dart 层完成不要丢给原生侧做否则又要维护一套原生编码器。传输层我用的是 WebSocket协议体直接走syncStream() async*生成的事件帧。每帧结构如下{ frame_index: 0, last_seq: 981, events: [ { seq: 982, ... }, ... ] }对端收到帧后做三件事校验事件帧里的last_seq是否与本地水位一致不一致说明可能漏帧返回一个NACK让对端重发将事件批量合入crdt_rows更新本端水位回复ACK。这个协议在公网环境实测下来每秒能处理约 5000 个事件移动网络下瓶颈主要在网络延迟而不是合并计算。6.2 同步触发策略和退出条件不是每次用户点同步都要把全量事件推一遍那样流量和电量都受不了。我在项目里用的是三层触发每次关键业务写操作后延迟 500ms 无新写入触发一次增量同步防抖App 进入前台时强制触发一次同步用于拉取别的端在后台期间的改动网络状态变化WiFi 恢复、弱网切换时触发一次重连 同步。退出条件就是事件水位对齐。我写了一个小 logger 记录每次同步的 push/pull 事件数和耗时线上通过日志观测平均每次同步 200~1500 个事件耗时可接受。6.3 鸿蒙原生侧的同步状态上报同步状态同步中、同步完成、冲突数要显示到 UI 上可以走 sql_crdt 自带的syncStream状态流也可以自己维护一个SyncStatusNotifier。鸿蒙端没有 Android 那种前台服务的概念应用退到后台后要及时释放 WebSocket 连接否则系统会以耗电为由杀掉进程下次启动时水位从本地重新推问题不大但用户体验不好。7. 冲突处理与一致性验证适配里最容易翻车的环节7.1 冲突处理的三种语义验证适配过程中最需要谨慎的是同一份业务数据在三种不同冲突场景下的最终状态要预期明确并且用测试固定下来。我整理了三种最典型的场景每条都在鸿蒙端做过自动化测试这里分享出来供你设计用例时参考场景描述预期行为测试要点两端离线同时改同一行不同字段两个字段的修改都保留互不覆盖验证最终行同时包含两端的新值两端离线同时改同一行同一字段逻辑时钟大者获胜site_id 字典序打破平局验证最终值不是随机值而是确定的一侧A 端删除一行B 端离线修改该行删除与修改并发按 OR-Set 语义删除胜验证该行在两端最终都处于删除状态A 端新增一条、B 端新增同主键行两条记录都保留Multi-Value Register验证业务层能看到两个版本而不是丢一个测试在鸿蒙模拟器和真机上各跑一遍。模拟器无法100%模拟系统进程杀死的场景所以真机测试要覆盖杀 App 后台、断网重连、低电量触发系统清理这几个场景最容易让同步位点丢失。7.2 一致性对账工具的实现石子算法不写太深核心思路每个端轮询给每条业务数据生成(主键 - 哈希)的映射把所有主键排个序连起来再做一次整体哈希。两个端整体哈希一致基本可判定两状态一致。哈希选择上不要用 MD5CRC64 偶碰撞率对一致性校验来说太高。我实际用的是 BLAKE2b-256纯 Dart 实现开销也不大一万条数据校验一次大约 300ms。对账频率我调到“每次同步成功后就跑一次轻量校验”线上看没有增加明显耗电但能帮你第一时间发现同步位点 bug。7.3 业务层冲突提示策略CRDT 能自动合并大多数冲突但 Multi-Value Register 场景下业务数据出现两个版本时用户需要知道。我的做法是在业务模型层增加一个conflictVersionCount字段当它大于 1 时UI 层展示一个“该记录存在多个版本”的标记点击后可以看到版本列表并手动选择保留哪个。这里的体验设计要和产品一块定不能等技术方案做完才补否则数据合并逻辑和 UI 展示之间容易脱节。8. 常见问题与排查技巧实录适配过程里我记下了一份问题清单按出现频次排序下面直接给结论。8.1 依赖解析失败与平台限制呈现样子flutter pub get返回sqlite3_flutter_libs不支持ohos平台。根因那个包只声明支持 android/ios/linux/windows/macos。它的pubspec.yaml里没有ohos那个 platform 声明。解法不要硬刚。在pubspec.yaml里用dependency_overrides强制把sqlite3_flutter_libs指向你自己维护的空实现包或者直接屏蔽sqflite_common_ffi改用自定义 factory。我从实际经验看后一种更稳。8.2 鸿蒙 engine 上 SQLite 回调不能用呈现样子用sqflite_common_ffi的默认实现打开数据库后任何query都返回空但insert正常。根因OpenHarmony 的 Flutter engine 默认没启用 FFI 的动态库加载通道或者加载到的 SQLite 版本过旧导致部分 SQL 执行失败而被静默吞掉。解法不要在这个方向上排查太久。直接把底层替换成relationalStore的适配层一小时能解决继续调 FFI 可能要一周。8.3 压缩与解压的不一致呈现样子多端同步时偶发 “Invalid archive” 异常。根因同步路径上archive包解压时要求事件流长度必须是 4 字节对齐的帧部分网络栈会对帧做拆分或合并导致边界解析混乱。解法在事件流外层再包一层长度前缀。每次发送前写入 4 字节大端 int 表示帧长度接收时先读长度再读帧体杜绝边界问题。这个改进我加完之后再没出过这个错。8.4 数据库文件路径权限异常呈现样子openDatabase抛SecurityException但在 Android 上完全正常。根因鸿蒙沙箱对数据库目录的访问权限有严格限制代码里用了绝对路径或系统公共目录。解法改用getDatabasesPath()等价实现并且保证数据库目录在应用私有沙箱内。华为设备上尤其不要用外部存储路径存数据库。8.5 同步后部分数据重复呈现样子对账发现某些主键出现了两行几乎相同的记录。根因sql_crdt 的 Multi-Value Register 语义两个端离线新增了相同业务主键但 CRDT 层分配的内部 ID 不同合并后没有收敛成一行。解法这是业务规则问题不是 bug。需要业务层对唯一的业务主键做一次“业务去重”处理比如重新合并版本时把versionId靠后的保留。8.6 同步速度慢、频繁超时呈现样子批量同步时单批事件超过 2 万条WebSocket 直接断连。根因默认事件帧里带全量字段单帧体积太大超过服务端 WebSocket 报文上限。解法事件分片传送每批最大 1000 条或者把事件的 payload 字段拆到独立的 blob 通道用 HTTP 上传而不是全部挤 WebSocket 实时帧。下面我把上述高频问题整理成速查表问题现象疑似根因处理建议依赖解析失败包不支持 ohos platform用 dependency_overrides 屏蔽或替换适配包查询全返回空FFI 动态库未加载换relationalStore适配层偶发 archive 异常帧边界混乱加长度前缀定界权限异常路径超出应用沙箱用getDatabasesPath()等价路径数据重复Multi-Value Register 语义业务层加去重逻辑同步超时单帧过大事件分片单批不超过 1000 条这些问题的共同点是直接在鸿蒙上抛错的反而是少数更多是“能跑但结果不对”的隐性坑所以对账工具和日志埋点在前期就必须准备好不要等上线后再截查脏数据。9. 实操过程中的真实体会走完这一整套适配我对“鸿蒙化 Flutter 三方库”这件事有了几个比较深的体会。第一鸿蒙化适配的核心难点不在于 Flutter 本身而在于“原生依赖的替换路径”。sql_crdt 已经算很克制了整个库只有 SQLite 一个原生依赖但它依然卡住了很多人。比它复杂的库比如带 FFmpeg、带自研 C 引擎的适配工作量会指数级上升。所以选型时如果预判到一个库有原生依赖提前评估鸿蒙支持状况非常关键。第二我没有选择 fork sql_crdt 改内部实现而是用“最小渗透”方案只换存储层让我在上游 sql_crdt 发布新版本时可以安全升级这个投入产出比我觉得非常值得。如果你有团队余力我甚至建议把这一步做成内部公共组件以后每个项目要接鸿蒙离线同步直接复用。第三不要低估一致性验证的工程量。CRDT 数学上是对的但它落到具体工程里错误往往出在地上层封装上比如我遇到的 ResultSet 类型映射问题。没有一套自动化的“多端数据对账”机制这类问题会潜伏到生产环境给你颜色看。最后分享一个实操细节在鸿蒙适配的调试期可以通过鸿蒙提供的日志系统同时观察 Dart 侧和原生侧的 SQL 调用序列两边对照起来看能快速定位是哪一层吞了错误。这套方案非常适合“存量业务 多端离线协作 鸿蒙新平台”三类需求叠加的项目。如果你的业务还没到这么复杂用 sql_crdt 的 LWW 语义就够应付大多数场景了完全不必要等到鸿蒙化适配完成再引入。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑