HarmonyOS 7 ArkData + DistributedKVStore:跨设备 dataChange 回调风暴中的版本水位、批量刷新与订阅回收【鸿蒙心迹】
这次遇到的问题不是“同步失败”,而是同步成功以后页面刷得太勤。
KvPulseGuardLab 是一个跨设备笔记 Demo,手机和平板共享 note_sync_v2。最初每收到一次远端 dataChange,页面就重新查询一次 note/。数据量小时没感觉,后来一次编辑会同时改标题、正文、更新时间、标签和版本号,同步到本机后短时间连续出现多次变化通知,页面就开始反复 query 和重建列表。
我最后把链路改成三层:dataChange 只收集变化 Key;120 ms 窗口合并连续事件;真正刷新以前再检查业务版本水位,旧结果不能覆盖新结果。
本次固定数据:Session kv_watch_20261001_19,Store note_sync_v2,Subscribe REMOTE,Raw Events 14,Effective Refresh 3,Coalesced 11,Changed Keys 6,Last Version 42,Query Rows 6,Active Listeners 1,State COALESCED。

一、dataChange 只当变化信号,不直接当刷新命令
DistributedKVStore 的 ChangeNotification 会提供 insert、update、delete 条目。我的第一版代码在回调里直接刷新 UI,相当于底层一次同步拆成多少通知,页面就刷新多少次。
现在 observer 只做采集:
import { distributedKVStore } from '@kit.ArkData'
private store?: distributedKVStore.SingleKVStore
private changedKeys: Set<string> = new Set()
private rawEvents: number = 0
private activeListeners: number = 0
private readonly onRemoteChange = (
change: distributedKVStore.ChangeNotification
): void => {
this.rawEvents++
const entries = [
...change.insertEntries,
...change.updateEntries,
...change.deleteEntries
]
entries.forEach(item => this.changedKeys.add(item.key))
this.scheduleRefresh()
}
attach(store: distributedKVStore.SingleKVStore): void {
this.store = store
store.on(
'dataChange',
distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_REMOTE,
this.onRemoteChange
)
this.activeListeners = 1
}
这里专门订阅 SUBSCRIBE_TYPE_REMOTE,因为页面本地编辑已经由自己的 ViewModel 更新,不需要再绕一圈远端刷新链。
本地、远端都要监听时,更适合拆成两条管线。
二、120 ms 的作用,是让同一批远端同步先“落稳”
我用的是尾触发 debounce:120 ms 内只要还有新 dataChange,就继续延后真正查询。
private refreshTimer: number = -1
private coalesced: number = 0
private effectiveRefresh: number = 0
private lastVersion: number = 0
private scheduleRefresh(): void {
if (this.refreshTimer !== -1) {
clearTimeout(this.refreshTimer)
this.coalesced++
}
this.refreshTimer = setTimeout(() => {
this.refreshTimer = -1
void this.flushChangedKeys()
}, 120)
}
private async flushChangedKeys(): Promise<void> {
if (!this.store || this.changedKeys.size === 0) {
return
}
const versionValue = await this.store.get('meta/version')
const version = Number(versionValue.value)
if (version <= this.lastVersion) {
this.changedKeys.clear()
return
}
const rows = await this.store.getEntries('note/')
this.lastVersion = version
this.effectiveRefresh++
this.publishRowsToView(rows)
this.changedKeys.clear()
this.state = 'COALESCED'
}
最终 14 次原始事件只触发 3 次有效刷新,因此 Coalesced=11。
120 ms 不是系统规定,只是当前 Demo 的工程参数。关键点是把“数据通知频率”和“UI 刷新频率”拆开。
版本水位同样很重要。第 41 版 query 如果比第 42 版更晚返回,页面不能被旧结果覆盖。这里的 meta/version 是业务自己的页面水位,不是 DistributedKVStore 的自定义冲突策略。
三、Changed Keys 还能继续帮我们缩小查询范围
这次只查询 note/ 前缀下 6 条记录。数据规模更大时,应把 insert / update / delete 分开做局部刷新。
需要注意,deleteEntries 中的 Key 已经不存在,不能再无脑调用 get(key)。这也是为什么我保留 ChangeNotification 的三类条目,而不是只维护一个普通字符串数组。
四、页面退场必须把 observer 和 debounce timer 一起回收
回调风暴还有一种假象:实际上是页面进出几次后重复注册了 listener。
DistributedKVStore 提供成对的 on/off。我的解绑逻辑同时清 observer、timer 和临时 Key 集合:
detach(): void {
if (!this.store) {
return
}
this.store.off(
'dataChange',
this.onRemoteChange
)
if (this.refreshTimer !== -1) {
clearTimeout(this.refreshTimer)
this.refreshTimer = -1
}
this.changedKeys.clear()
this.activeListeners = 0
}
只 off listener 还不够。页面退出前已经挂起的 120 ms timer 仍可能醒来继续 query,所以 timer 也必须一起停。
如果此时已经有一次异步 query 在执行,还应再加 page generation,防止迟到结果写回已销毁页面。
五、调试时我只看“原始事件”和“真实刷新”的差值
工程拆成 KvPulsePage.ets、KvChangeCoalescer.ets、VersionWatermark.ets 和 NoteSnapshot.ets。
HiLog 固定保留:subscribe REMOTE listener=1、dataChange raw=14、collect changedKeys=6、coalesced=11 debounce=120ms、query prefix=note/ rows=6、lastVersion=42。

六、最终结果不是“同步成功”,而是 14 次事件只刷新 3 次
运行页里:
- Raw Events:
14 - Effective Refresh:
3 - Coalesced:
11 - Changed Keys:
6 - Last Version:
42 - Query Rows:
6 - Active Listeners:
1
Raw Events=14 证明底层变化确实很多;Effective Refresh=3 证明 UI 没跟着抖;Active Listeners=1 又能排除重复订阅。

七、从 Demo 到正式项目还要补什么
多设备同时编辑时,业务版本规则仍要单独设计;observer 也应集中管理,不要让每个卡片各订一份。
最后,跨设备同步和 UI 刷新是两回事。DistributedKVStore 负责把数据带到本机,dataChange 负责告诉应用“有变化”,而“什么时候刷、一次刷多少、旧结果能不能覆盖新结果”仍然是应用自己的工程职责。
最终链路固定为:
REMOTE_EVENT → BUFFERING → QUERYING → COALESCED
拆开这些层以后,跨设备同步就变成可量化、可限流、可验收的刷新管线。
更多推荐

所有评论(0)