消息没停,未读角标就不更新?
聊天 App 会在收到消息后更新本地数据,再查询所有会话的未读总数,显示到角标上。查询是异步的,消息多的时候,上一轮还没回来,下一条消息就到了。
这里遇到的现象是:消息一直在收,角标却不怎么变;等消息停下来,数字又恢复正常。没有报错,最终数量也对,单看最后一帧很难发现问题。
查询有结果,为什么界面收不到?
原来的实现用版本号防止旧结果覆盖新数据。每次收到消息就增加版本,查询开始时记住版本,回来时发现不一致便丢弃,再查一次。
假设一次查询要 750 ms,新消息每 400 ms 到一条。第一轮查到一半版本就变了,750 ms 返回时失效;第二轮查询也会被后续消息打断。只要消息不停,查询就一直有结果,却始终没有结果能交给界面。
这种防旧数据的办法保护了准确性,却让界面失去了持续更新的机会。
防抖、取消旧查询,能解决吗?
如果业务只关心输入停止后的最终结果,防抖很合适。但聊天消息可能连续到达,每来一条就重新计时,仍然可能长时间不刷新。每次取消旧查询再启动新查询,也有类似问题。
另一个选择是让查询串行执行,期间的变化合并成下一次刷新。这里还需要区分两类变化:新消息来了,当前数量可能少几条,短暂显示后再补查可以接受;用户刚把会话读完,旧查询却把角标重新点亮,就不能接受。切换账号时,旧账号的数量更不能显示。
因此,刷新请求不必全部导致当前结果作废。
这个项目怎样区分这两类变化?
项目保留两个版本。一个表示“查询期间又有变化,需要再查”,另一个表示“当前结果已经不能使用”。新增消息通常只推动前者,已读、清理缓存或账号切换,则按实际影响决定是否作废结果。
查询循环的核心逻辑如下,省略了账号、初始化和错误处理:
while (!disposed) {
final startedAt = revision;
final validAt = invalidationRevision;
final count = await queryCount();
if (disposed) return;
if (validAt == invalidationRevision && count != lastCount) {
lastCount = count;
publish(count);
}
if (startedAt == revision) return;
}
有查询在跑时,新的刷新请求只更新版本,不再为每条消息单独开查询。当前结果仍有效,就先发布;期间还有变化,再补一轮。如果调用方仍然把每次新增消息都标成失效,两个版本号也救不了原来的问题。
自动已读也要一起调整。当前打开的会话收到消息后,会自动更新阅读状态;如果数据库的未读状态实际上没有变化,就没必要反复作废正在进行的计数查询。
怎么确认修到的是持续刷新?
回归用例让两个会话持续收消息,同时延迟计数查询。查询替身在开始时就取走快照,稍后才返回,才能模拟真实的旧结果。断言放在消息还没停的时候,检查角标是否已多次更新,最后再核对总数是否收敛。
另外还有一个恢复问题:界面曾显示 8,查询出错后隐藏角标,恢复时查到的仍是 8。若只与上次发布值比较,就不会重新显示。实现会在有效查询失败后清掉发布基线,让恢复后的相同数字也能发出去。
查询返回前退出登录、切账号或销毁页面,都需要拒绝旧结果。账号身份还要单独保存,不能指望一个会被原地修改的配置对象继续提供旧值。这些替身用例检查的是应用时序,真实数据库筛选、设备生命周期和双端同步仍需对应环境验证。