Android 离线优先数据同步设计
Android 离线优先数据同步设计
摘要:移动网络会断开、切换或在提交过程中超时。离线优先的应用需要让用户先完成本地操作,再在条件允许时同步到服务端。本文用本地数据库、待上传队列和 WorkManager 组合一个可恢复的同步流程。
一、先明确本地数据的角色
离线优先不只是给网络错误弹个提示。界面读取本地数据作为主要数据源;用户操作先写入本地事务;后台同步再把变化发送给服务端。这样应用重启后仍能看到已经保存的内容。
数据库中的记录可包含业务字段、更新时间、同步状态和服务端版本标识。不要仅用一个全局布尔值表示“需要同步”,因为它无法说明哪条数据、哪次修改尚未提交。
二、用事务记录待同步操作
一个简单的操作表可以保存操作 ID、目标记录、操作类型、负载和状态。更新业务表与插入待同步操作应处于同一数据库事务:
suspend fun updateNote(note: Note) = database.withTransaction {
noteDao.upsert(note.copy(syncState = SyncState.PENDING))
outboxDao.insert(
OutboxOperation(
operationId = UUID.randomUUID().toString(),
entityId = note.id,
operation = "UPDATE",
createdAt = clock.nowMillis()
)
)
}
如果只保存本地记录而没有保存待同步标记,进程可能在两步之间退出,造成数据永远不会上传。事务保证这两项本地变化一起成功或一起失败。
三、分批处理待同步队列
同步任务读取有限数量的待处理操作,按稳定顺序提交。成功后,在一个本地事务中标记操作已完成并更新服务器版本;失败时保留待处理状态,等待重试。每次只处理有限批次,避免任务耗时无限增长。
服务端请求应携带稳定的操作 ID。若客户端发送成功但响应丢失,下一次重试可以由服务端识别重复操作,避免重复创建记录。客户端重试本身并不能保证“只执行一次”。
四、选择冲突解决规则
同一条记录可能同时在两台设备或本地与服务端发生修改。常见规则包括:
- 最后更新时间优先,简单但设备时钟不准时可能出错。
- 服务端版本号比较,检测到冲突后提示用户或合并字段。
- 按字段合并,对互不相关的字段保留双方修改。
- 追加型数据只追加不覆盖,适合事件或日志。
冲突策略应符合业务语义。财务记录与简单草稿不能用同一套盲目覆盖规则。
五、触发与调度同步
用户提交后可以安排一次性同步;约束网络可用,由 WorkManager 在满足条件时执行。应用启动或回到前台时也可以检查待处理队列。多个触发点使用唯一工作名称,避免重复排队。
触发工作只是调度信号,队列和状态仍以数据库为准。即使后台任务被取消,下一次触发也能重新读取未完成操作。后台执行时间有限,需支持取消、批量上限和失败重试。
六、观察同步状态
界面可展示“待同步”“同步中”“已同步”或“需要处理”。状态来自本地数据库并随操作更新。错误提示要区分暂时离线与需要用户介入的问题,例如身份失效、内容冲突或数据校验失败。
不要在每条列表数据上高频闪烁同步动画。状态变化只应反映真实任务进展,任务重试也不应清空用户刚输入的内容。
七、数据迁移与隐私
离线数据可能比当前应用版本存活更久。数据库迁移要保留未同步队列,必要时给操作格式加版本字段并提供转换逻辑。上传前应检查用户登录状态和数据授权;退出账号时应定义本地草稿是清除、保留还是隔离。
日志不要记录令牌、完整私密内容或个人信息。同步失败日志可记录操作 ID、错误分类与重试次数,以便排查而不泄露数据。
八、常见问题
- 不能只靠内存队列保存待上传操作。
- 网络连接可用不代表请求一定成功,读取、上传和确认都要处理失败。
- 重试必须考虑幂等和退避,避免故障时集中请求。
- 本地删除也需要表达成可同步操作,必要时使用软删除标记。
- 队列需要清理已完成记录,并监控长期积压。
九、总结
离线优先流程的可靠性来自本地事务、持久化操作队列、幂等服务端接口和明确冲突规则。WorkManager 负责在合适时机唤醒同步;数据库才是重启后恢复工作的依据。先定义失败和冲突语义,再实现调度,系统才容易验证和维护。
- 点赞
- 收藏
- 关注作者
评论(0)