鸿蒙架构进阶 02:离线优先架构与本地队列同步实战
开篇:地铁里没网,用户点个赞按钮直接转圈圈
很多应用的体验极差,就是因为它是“网络优先”的。
用户在电梯里、地铁上,点个收藏,或者发个评论,按钮直接开始转圈,转了几秒钟弹个 Toast:“网络异常”。用户刚才的编辑内容全丢了,或者操作直接失败。
在这个网络无处不在又随时可能断开的时代,“离线优先(Offline First)” 架构才是留住用户的王道。
用户不管有没有网,点按钮立刻成功(UI 立刻更新),数据先存本地。等网络恢复了,再悄悄在后台把数据同步到服务器。这就是所谓的“乐观更新(Optimistic UI)”和“本地队列”。
一、架构设计:本地数据库是“唯一真相”
在这套架构里,UI 渲染的数据永远来自本地数据库(RDB),而不是网络请求的直接返回。
核心流程:
-
用户操作 -> 写入本地 RDB -> 状态标记为
PENDING(待同步)。 -
UI 监听 RDB,数据一变,界面立刻刷新。
-
网络恢复(或定时器触发) -> 后台读取
PENDING的数据 -> 发送网络请求。 -
请求成功 -> 更新 RDB 状态为
SYNCED(已同步)。
二、建表与状态设计
我们需要一个表来存业务数据,以及一个表来存操作队列。
typescript
// 1. 业务表
const SQL_TODO = `
CREATE TABLE IF NOT EXISTS todo (
id TEXT PRIMARY KEY,
title TEXT,
isCompleted INTEGER,
syncStatus INTEGER -- 0: 待同步, 1: 已同步
)
`;
// 2. 操作队列表(处理复杂的增删改顺序)
const SQL_QUEUE = `
CREATE TABLE IF NOT EXISTS sync_queue (
id INTEGER PRIMARY KEY AUTOINCREMENT,
actionType TEXT, -- 'INSERT', 'UPDATE', 'DELETE'
entityId TEXT,
payload TEXT, -- JSON数据
retryCount INTEGER DEFAULT 0
)
`;
三、写入与触发同步
当用户添加一条待办:
typescript
async function addTodo(title: string) {
const id = generateUUID();
// 1. 写业务表
await rdbStore.insert('todo', { id, title, isCompleted: 0, syncStatus: 0 });
// 2. 写同步队列
await rdbStore.insert('sync_queue', {
actionType: 'INSERT',
entityId: id,
payload: JSON.stringify({ id, title })
});
// 3. 通知 UI 刷新(如果没用 DataObserver,可以用 EventHub)
eventHub.emit('TODO_CHANGED');
// 4. 尝试触发同步
triggerSync();
}
triggerSync 是核心。你不能同步队列里有一万条数据就发一万个请求,得批量。
typescript
import networkObserver from '@ohos.net.connection';
let isSyncing = false;
async function triggerSync() {
if (isSyncing) return;
// 检查网络
if (!hasNetwork()) return;
isSyncing = true;
try {
// 从队列中取一批(比如 10 条)
const queueItems = await getQueueItems(10);
if (queueItems.length === 0) return;
// 组装成批量接口发给服务端
const response = await api.syncBatch(queueItems);
if (response.success) {
// 删掉已成功的队列数据
await removeQueueItems(queueItems.map(i => i.id));
// 更新业务表状态
await updateTodoStatusToSynced(queueItems);
}
} catch (err) {
// 失败处理:增加 retryCount
await incrementRetry(queueItems);
} finally {
isSyncing = false;
// 如果还有数据,继续同步
triggerSync();
}
}
注意那个 if (isSyncing) return;。 如果你不加这个锁,用户疯狂点按钮,会触发几百次同步,服务端直接把你 IP 封了。
四、冲突解决与重试机制
坑一:网络断了,同步队列一直发。
监听系统的网络状态变化(@ohos.net.connection)。如果没网,停止发请求,等网络恢复了(on('netAvailable'))再触发。
坑二:服务器返回 400(数据不合法)怎么办?
如果一条数据服务端死活返回 400,你无限重试会把队列卡死。必须在队列里设置 retryCount,如果超过 3 次,说明这条数据有严重问题,把它移到“死信队列”,或者打个日志放弃同步,别影响其他正常数据。
坑三:多端同步冲突。
用户手机离线加了条数据(版本 v1),平板在线也改了这个数据(版本 v2)。手机上来同步时,服务端得能判断版本。这就是大名鼎鼎的乐观锁。服务端要检查客户端传来的版本号,如果比服务端旧,直接拒绝,让客户端拉取最新数据合并。
五、总结
离线优先架构,是提升应用健壮性的核武器。核心记住三点:
-
UI 永远只信本地库,操作立刻落库,给用户最快反馈。
-
维护一个同步队列,网络恢复后批量推送,并加锁防止并发重试。
-
做好最坏的打算,设置重试上限,处理版本冲突,防止队列死锁。
小任务: 用 RDB 写一个最简单的离线点赞功能。点击时往数据库里加一条记录,界面上数字立刻 +1。然后开个定时器,每 10 秒检查有没有网络,有网络就把未同步的记录推送到一个虚拟 API。
更多推荐




所有评论(0)