ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

深入 MoneyManagerEx 离线优先同步架构:三态脏标记、Push/Pull 流程与冲突解决完全解析

深入 MoneyManagerEx 离线优先同步架构:三态脏标记、Push/Pull 流程与冲突解决完全解析 深入 MoneyManagerEx 离线优先同步架构三态脏标记、Push/Pull 流程与冲突解决完全解析【免费下载链接】android-money-manager-exLocal-first personal finance app. Encrypted, self-hosted, sync across devices.项目地址: https://gitcode.com/gh_mirrors/an/android-money-manager-exMoneyManagerEx 是一款开源的 Android 个人财务管理应用采用「本地优先Local-first」架构你的账户、交易等数据先保存在加密的本地 SQLite 数据库中再可选地与自托管服务器双向同步。本文带你通俗读懂它离线优先同步架构背后的核心机制——三态脏标记、Push/Pull 同步流程与冲突解决原理即使你是新手也能一次看懂。离线优先同步是什么先本地后云端 传统记账应用要么纯本地、要么纯云端而 MoneyManagerEx 选择了「本地优先」本地即权威所有数据都先写入手机本地的 SQLite 数据库断网也能记账云端仅同步服务器可自托管的 PocketBase只负责多台设备之间的数据交换数据主权在你自己手里ID 天然不冲突本地新增记录的主键由「毫秒时间戳 × 1000」生成见app/src/main/java/com/money/manager/ex/datalayer/RepositoryBase.java的newId()单调递增多台设备各自生成的 ID 几乎不可能撞车正因为本地 ID 全局唯一同步时就不需要复杂的 ID 重写这是整套架构能保持简单的前提。三态脏标记详解每个记录上的 3 个小数字 为了知道「哪些记录该上传」MoneyManagerEx 给每个同步表追加了 3 个辅助字段定义在app/src/main/java/com/money/manager/ex/domainmodel/EntityBase.java字段作用pb_id远端记录的 ID本地与远端的「绑定纽带」pb_updated_at远端的最后更新时间增量拉取的依据pb_is_dirty脏状态一个 0 / 1 / 2 的小数字pb_is_dirty只有三个状态构成一个极简状态机状态值含义何时设置0干净本地与远端一致初始状态、Push 成功后、一轮 Pull 结束后1本地脏本地改过等待上传本地数据发生变化时2远端脏远端版本已覆盖到本地Pull 时应用远端记录写入这个设计的巧妙之处在于用同一个字段区分「谁改的」Push 时查询条件是pb_is_dirty 1 OR pb_id IS NULL OR pb_id ——只上传「本地脏」记录和还没拿到远端 ID 的新记录Pull 完成后执行UPDATE ... SET pb_is_dirty 0 WHERE pb_is_dirty 2——只把「远端脏」的记录归零绝不误伤还没上传的本地修改三个状态各司其职一个整型字段就完成了整个变更追踪。Push/Pull 同步流程一次同步的完整旅程 同步的编排核心在app/src/main/java/com/money/manager/ex/sync/PocketBaseSyncEngine.java的synchronize()方法整个周期是**「先 Push后 Pull」且包在一个数据库事务里**——要么全部成功要么整体回滚不存在同步到一半的脏数据库。第一阶段Push上传本地修改先处理删除读取本地删除日志表pb_DELETED_RECORDS_LOG对每条记录调用远端「软删除」置_is_deleted 1成功后从日志表移除再处理脏记录按配置表顺序遍历构造 JSON 数据没有pb_id→ 调用远端CREATE新建记录有pb_id→ 调用远端UPDATE若 UPDATE 返回 404记录在服务器丢了自动降级为带原 ID 重新创建成功后把服务器返回的 ID 写回本地pb_id并将状态归零为0第二阶段Pull拉取远端更新计算增量起点上次成功同步时间往前回拨 5 秒作为缓冲区防止时间精度问题漏掉数据用_updated_at 起点过滤按时间排序、每页 200 条分页拉取逐条应用到本地applyRemoteRecord远端标记已删除 → 删除本地记录本地已有该pb_id→ 更新字段状态置2本地没有 → 插入新记录状态置2全部完成后将pb_is_dirty 2的记录批量归零并记录本次同步时间同步要覆盖哪些表、每张表同步哪些字段都由配置驱动app/src/main/res/raw/table_config.json中的SYNC_ORDER表顺序和SYNC_CONFIG主键与字段清单新增同步表无需改代码。冲突解决原理三道防线防止数据丢失 ⚔️「两台设备改了同一条记录」是所有同步系统都要回答的问题。MoneyManagerEx 分三层处理第一道防线409 冲突 → 远端胜出Push 时如果服务器返回 409该记录在服务器上被改过客户端会拉取服务器最新版本覆盖本地状态归零。简单直接服务器版本更新就以它为准。第二道防线唯一约束冲突 → 延迟合并如果推送触发唯一性校验失败比如两台设备各自建了「超市」这个分类系统会按本地主键去服务器找到已存在的那条记录把远端 ID 绑定到本地记录上但状态保持1updateLocalRecordAfterPushKeepingDirty。也就是说本次先完成「身份绑定」下一轮同步再真正合并数据——把一次复杂的即时合并拆成两次简单的单步操作最终必然收敛。第三道防线文件级同步的交互式合并老版本的文件级同步app/src/main/java/com/money/manager/ex/sync/merge/DataMerger.java采用更精细的策略把每条记录的「本地最后修改时间」「远端最后修改时间」与「上次同步时间」比较——只有远端改过 → 直接用远端只有本地改过 → 什么都不做两边都改过 → 弹出对话框让用户选择接受远端THEIRS、保留本地OURS或中止同步ABORT弹窗内容不是冷冰冰的「有冲突」而是getDiffString()逐字段生成的差异清单字段名本地值 | 远端值让普通用户也能看懂到底差在哪里再做决定。新手上手三步启用多设备同步 开启同步在主界面进入同步设置见文首截图选择存储方式——传统文件存储Google Drive / OneDrive / Nextcloud 等或自托管 PocketBase 服务器关联数据库在数据库设置中为当前数据库绑定远程路径首次同步会把整个库推送到远端享受同步手动点击同步按钮或开启定时心跳应用会在后台自动完成 Push/Pull 周期同步状态通过通知实时反馈相关入口代码app/src/main/java/com/money/manager/ex/sync/SyncManager.java、app/src/main/java/com/money/manager/ex/sync/SyncPreferenceFragment.java总结为什么这套架构值得学习 设计点收益三态脏标记0/1/2一个整型字段区分「本地改」与「远端改」逻辑一目了然先 Push 后 Pull 单事务自己的修改优先上云且同步永不产生半成品数据库增量拉取 5 秒回拨缓冲只传变化数据又快又不会漏远端胜出 延迟合并用「简单规则 多轮收敛」替代复杂的实时合并算法本地毫秒时间戳 ID多设备并发新增永不撞 IDMoneyManagerEx 没有引入 OT、CRDT 等重型理论而是用「三态脏标记 事务 分层冲突策略」这套朴素组合实现了离线优先场景下可靠、易理解、可自托管的多设备同步——这正是开源社区值得借鉴的工程智慧。【免费下载链接】android-money-manager-exLocal-first personal finance app. Encrypted, self-hosted, sync across devices.项目地址: https://gitcode.com/gh_mirrors/an/android-money-manager-ex创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表