弱网环境下HarmonyOS 7 离线消息队列架构实战
前几天周末,我在一个偏远郊区的农家乐团建。去卫生间的时候,顺手在咱们自己的 App 里发了条带图的动态。
结果提示“网络错误”,图片没发出去。更气人的是,我辛辛苦苦敲的百来字文案,因为页面退出,直接没了。
回来之后我就跟开发组拍桌子:“用户环境千变万化,谁规定发动态的时候一定得满格 5G?必须做一个离线优先的架构,发送按钮点下去,不管有没有网,立刻给我变成‘已发送’。”
这就是“乐观更新(Optimistic UI)”和“离线队列”的由来。这篇记录下我是怎么在鸿蒙里用 RDB 构建一条可靠的本地消息队列的。
为什么不能用网络优先?
我之前的垃圾写法是这样的:
typescript
// 错误示范:网络请求成功后才更新 UI
async function postComment(text: string) {
this.isLoading = true;
try {
await api.postComment(text); // 没网就卡在这里,最后报错
this.commentList.push(text);
} catch (err) {
promptAction.showToast({ message: '网络异常' });
} finally {
this.isLoading = false;
}
}
这种模式在没网的时候,会让用户感到极度的挫败,而且一旦发生异常,用户的心血(输入的文字)全白费了。
正确的思路是:把本地关系型数据库(RDB)当做唯一的数据真相来源(Single Source of Truth)。 UI 只认数据库,网络请求只是后台的同步动作。
设计本地消息队列表
我们需要一张表来记录用户的每一次“发送”动作。
typescript
import relationalStore from '@ohos.data.relationalStore';
const SQL_CREATE_QUEUE = `
CREATE TABLE IF NOT EXISTS message_queue (
id TEXT PRIMARY KEY, -- 客户端生成的唯一 ID
content TEXT, -- 消息内容
status INTEGER, -- 状态:0=待发送, 1=发送中, 2=发送成功, 3=发送失败
create_time INTEGER -- 创建时间
)
`;
当用户点击“发送”时,我们根本不发网络请求,直接往数据库里插一条 status = 0 的记录。
typescript
async function onSendClick(content: string) {
const id = generateUUID();
// 1. 写入数据库,状态为待发送 (0)
await rdbStore.insert('message_queue', {
id: id,
content: content,
status: 0,
create_time: Date.now()
});
// 2. 界面上立刻显示这条消息,打上一个“转圈圈”的 Loading 图标
// 因为 UI 是双向绑定或监听 RDB 的,所以这一步非常快
// 3. 触发后台同步任务
triggerSync();
}
同步引擎与网络监听
接下来,要写一个后台同步引擎。它不断地从队列里拿 status = 0 的数据去发请求。
typescript
let isSyncing = false;
async function triggerSync() {
// 加锁,防止用户狂点导致多个同步任务并发执行
if (isSyncing) return;
// 检查当前网络状态,如果是离线状态,直接返回
if (!isNetworkAvailable()) return;
isSyncing = true;
try {
// 取出所有待发送的数据
const predicates = new relationalStore.RdbPredicates('message_queue');
predicates.equalTo('status', 0);
const resultSet = await rdbStore.query(predicates, ['id', 'content']);
while (resultSet.goToNextRow()) {
const id = resultSet.getString(0);
const content = resultSet.getString(1);
// 更新状态为“发送中”(可选)
await updateStatus(id, 1);
try {
// 真正发网络请求
await api.postMessage(id, content);
// 成功了,把状态改成 2
await updateStatus(id, 2);
} catch (err) {
// 失败了,把状态改成 3,或者根据重试次数决定是否放弃
await updateStatus(id, 3);
}
}
resultSet.close();
} finally {
isSyncing = false;
}
}
网络恢复时怎么办?
如果用户发送时没网,消息存在了本地。等用户走到有 Wi-Fi 的地方,我们必须自动触发同步。这就需要监听系统的网络变化:
typescript
import connection from '@ohos.net.connection';
// 监听网络可用事件
const netConnection = connection.createNetConnection();
netConnection.on('netAvailable', (data) => {
console.info('网络恢复了,开始执行积压的消息队列');
triggerSync();
});
处理极端情况:幂等性
这套架构虽然牛,但有个巨大的隐患:如果请求已经到达了服务器,但服务器返回响应的途中网断了,客户端会把状态标记为失败,下次重试时会重复发送。
解决这个问题的关键在于:幂等性(Idempotency)。
我们在客户端生成的唯一 id,必须作为请求的一部分传给服务器。服务器收到这个 id 时,先查一下数据库,如果这个 id 已经处理过了,直接返回成功;没处理过才落库。
离线优先的架构,会让用户觉得你的应用“快得不可思议”。即使是在高铁隧道里,他们点发送按钮的反应也是瞬间的。
忠告:
-
本地 RDB 是绝对的核心,千万别再搞什么网络成功后才更新界面的老套路了。
-
队列必须要有重试上限。如果一条消息一直报 500 错误,重试个 3 次就该放弃了,别让一条死数据堵死整个队列。
更多推荐



所有评论(0)