分布式 MultiKit 多端同步落地:离线冲突自定义合并完整业务实践

一、落地背景:旧分布式数据方案核心痛点
鸿蒙7 API26 将原 DistributedDataKit 统一升级为 @kit.DistributedMultiKit,面向自助终端、多屏协同、政企多设备办公等离线高频场景,彻底解决旧版分布式能力三大硬伤:
- 冲突策略单一,仅支持时间戳覆盖
旧套件仅提供「最后写入覆盖」策略,多设备离线修改同一字段会直接丢失一方数据,笔记、待办、表单类业务无法接受数据丢失; - 全设备无差别同步,带宽浪费严重
旧方案组网后全部可信设备同步数据,不需要同步的副设备持续接收增量数据,折叠屏、多平板同账号场景流量与功耗飙升; - 离线修改丢失,重连直接覆盖本地变更
设备断网离线修改数据,重新组网后远端数据直接覆盖本地,离线操作全部失效,无缓存、合并兜底逻辑; - 与ArkUI V2割裂,深层对象同步不刷新
旧分布式对象不兼容V2响应式体系,嵌套对象修改后跨设备UI无法自动更新,需要手动重拉数据刷新页面。
DistributedMultiKit 新增自定义冲突合并、设备白名单分组同步、向量时钟溯源、增量差分传输、ArkUI V2原生联动五大核心能力,本文以「多端协同待办系统」为业务载体,完整实现离线断网修改、多设备冲突自定义合并、分层同步、UI自动联动刷新全流程落地。
二、前置工程配置与依赖规范
2.1 标准导入规范
import {
MultiDistributedObject,
MultiKVStore,
DeviceManager,
SyncMode,
ConflictPolicy,
SecurityLevel,
VectorClockInfo
} from '@kit.DistributedMultiKit';
import { ObservedV2, Trace, ComponentV2, Local } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG = "MultiKitSyncDemo";
const DOMAIN = 0x0009;
2.2 module.json5 权限与分布式能力配置
"requestPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC",
"reason": "$string:dist_sync_reason",
"usedScene": { "abilities": ["EntryAbility"], "when": "inuse" }
},
{
"name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE",
"reason": "$string:device_listen",
"usedScene": { "abilities": ["EntryAbility"], "when": "inuse" }
}
],
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"exported": true,
"metadata": [
{
"name": "ohos.app.ability.distributed.enable",
"value": "true"
}
]
}
]
2.3 核心概念说明
- 向量时钟 VectorClock:每一次数据变更自动生成版本向量,精准识别「互不冲突修改」「同字段冲突修改」,替代旧版单一时间戳判断;
- ConflictPolicy.CUSTOM_MERGE:自定义合并模式,触发冲突后执行业务自定义逻辑,可字段级分别取舍、合并数组;
- syncRange 设备白名单:指定仅同步目标设备ID,过滤不需要同步的终端;
- SyncMode.PUSH_PULL:离线缓存模式,断网修改写入本地日志,上线后批量差分同步,不会直接覆盖本地数据;
- MultiDistributedObject:配合ArkUI V2
@ObservedV2,深层属性跨设备同步后两端页面自动刷新。
三、业务实体设计(V2响应式+分布式兼容)
以多端待办业务为例,两层嵌套实体,全部使用@ObservedV2 + @Trace,自动兼容分布式同步与本地UI响应:
// 待办子项
@ObservedV2
class TodoItem {
@Trace id: string = "";
@Trace content: string = "";
@Trace finished: boolean = false;
// MultiKit自动维护向量时钟,用于冲突溯源
@Trace vectorClock: string = "";
// 修改时间戳,辅助合并判断
@Trace updateTs: number = Date.now();
}
// 全局分布式根存储
@ObservedV2
class DistributedTodoStore {
// 待办数组,多端离线增删改核心冲突点
@Trace todoList: TodoItem[] = [];
// 全局主题配置(简单单字段)
@Trace globalTheme: string = "light";
}
四、分布式同步管理器封装(可全局复用)
封装统一初始化、冲突合并、设备监听、资源销毁逻辑,核心为自定义冲突合并函数,解决离线多端修改数据丢失问题。
export class MultiSyncManager {
private distObject: MultiDistributedObject<DistributedTodoStore> | null = null;
private targetDeviceIds: string[] = [];
// 初始化分布式同步对象
async initSync(initData: DistributedTodoStore): Promise<DistributedTodoStore> {
try {
// 1. 获取当前组网可信设备列表
const deviceList = await DeviceManager.getTrustedDeviceList();
this.targetDeviceIds = deviceList.map(item => item.deviceId);
hilog.info(DOMAIN, TAG, `在线同步设备:${JSON.stringify(this.targetDeviceIds)}`);
// 2. 创建多设备分布式对象,开启自定义冲突合并
this.distObject = await MultiDistributedObject.create<DistributedTodoStore>({
objectName: "todo_global_sync_store",
initialData: initData,
// 同步范围:仅同步当前在线设备
syncRange: { deviceIds: this.targetDeviceIds },
// 离线缓存双向同步模式,断网修改本地持久化
syncMode: SyncMode.PUSH_PULL,
// 开启自定义冲突策略(核心,实现离线数据不丢失)
conflictPolicy: ConflictPolicy.CUSTOM_MERGE,
securityLevel: SecurityLevel.S1
});
// 3. 监听同步完成事件
this.distObject.on("syncComplete", (res) => {
hilog.info(DOMAIN, TAG, `同步完成,向量版本:${res.vectorClock}`);
});
// 4. 监听数据冲突,执行自定义合并逻辑
this.distObject.on("conflict", (conflictInfo) => {
this.handleCustomMerge(conflictInfo);
});
// 5. 监听设备上下线,动态更新同步白名单
DeviceManager.on("deviceChange", async () => {
const newList = await DeviceManager.getTrustedDeviceList();
this.targetDeviceIds = newList.map(d => d.deviceId);
hilog.info(DOMAIN, TAG, "设备状态变更,更新同步设备列表");
});
return this.distObject.getData();
} catch (err) {
const e = err as BusinessError;
hilog.error(DOMAIN, TAG, `分布式对象初始化失败:${e.code} ${e.message}`);
return initData;
}
}
/**
* 自定义冲突合并核心逻辑(业务落地关键)
* 规则:
* 1. 待办数组:本地、远端全部合并,根据id去重,同id取更新时间更新的一条
* 2. 主题字段:取时间戳更新较新的配置
*/
private resolveCustomMerge(localData: DistributedTodoStore, remoteData: DistributedTodoStore): DistributedTodoStore {
// 合并待办列表,去重,同ID对比更新时间
const allTodoMap = new Map<string, TodoItem>();
// 先存入本地所有待办
localData.todoList.forEach(item => allTodoMap.set(item.id, item));
// 遍历远端待办,冲突则对比时间戳保留更新版本
remoteData.todoList.forEach(remoteTodo => {
const localTodo = allTodoMap.get(remoteTodo.id);
if (!localTodo) {
// 本地无此待办,直接新增
allTodoMap.set(remoteTodo.id, remoteTodo);
} else {
// 同ID冲突,保留更新时间更新的条目
const winner = remoteTodo.updateTs > localTodo.updateTs ? remoteTodo : localTodo;
allTodoMap.set(remoteTodo.id, winner);
}
});
// 转为数组
const mergeTodoList = Array.from(allTodoMap.values());
// 主题字段:对比时间戳,取最新
const finalTheme = remoteData.globalTheme > localData.globalTheme
? remoteData.globalTheme
: localData.globalTheme;
// 返回合并后的完整数据,同步引擎自动覆盖本地存储
return {
todoList: mergeTodoList,
globalTheme: finalTheme
};
}
// 冲突事件入口,调用合并并提交结果
private handleCustomMerge(info: {
localData: DistributedTodoStore,
remoteData: DistributedTodoStore,
localTs: number,
remoteTs: number
}) {
const mergedData = this.resolveCustomMerge(info.localData, info.remoteData);
// 提交合并后的数据,完成冲突解决
this.distObject?.resolveConflict(mergedData);
hilog.info(DOMAIN, TAG, "多端冲突自定义合并完成");
}
// 修改分布式数据,自动触发增量同步
updateStore(handler: (store: DistributedTodoStore) => void) {
if (!this.distObject) return;
const data = this.distObject.getData();
handler(data);
// 写入变更,引擎自动计算差分推送组网设备
this.distObject.setData(data);
}
// 销毁释放所有监听,防止内存泄漏
destroy() {
if (this.distObject) {
this.distObject.offAll();
}
DeviceManager.off("deviceChange");
this.distObject = null;
}
}
五、页面业务层(ArkUI V2 自动跨设备UI同步)
页面使用@Local绑定分布式存储,任意设备修改待办、切换主题,其余组网设备无需刷新页面,UI自动渲染更新,完美适配离线修改后重连合并场景。
@Entry
@ComponentV2
struct MultiKitTodoSyncPage {
@Local store: DistributedTodoStore = new DistributedTodoStore();
private syncManager: MultiSyncManager = new MultiSyncManager();
@Local inputText: string = "";
aboutToAppear() {
this.initSyncStore();
}
aboutToDisappear() {
// 页面销毁释放监听,避免长运行OOM
this.syncManager.destroy();
}
// 初始化分布式同步存储
private async initSyncStore() {
const initData = await this.syncManager.initSync(this.store);
this.store = initData;
}
// 添加待办,离线修改本地缓存,上线自动同步合并
addTodoItem() {
if (!this.inputText.trim()) return;
const newTodo: TodoItem = {
id: Date.now().toString(),
content: this.inputText,
finished: false,
vectorClock: "",
updateTs: Date.now()
};
this.syncManager.updateStore(data => {
data.todoList.push(newTodo);
});
this.inputText = "";
}
// 修改待办完成状态,深层属性变更跨设备同步
toggleTodoFinish(targetId: string) {
this.syncManager.updateStore(data => {
const target = data.todoList.find(t => t.id === targetId);
if (target) {
target.finished = !target.finished;
target.updateTs = Date.now();
}
});
}
// 切换全局主题,多端同步
toggleTheme() {
this.syncManager.updateStore(data => {
data.globalTheme = data.globalTheme === "light" ? "dark" : "light";
});
}
build() {
Column({ space: 18 }) {
Text("MultiKit 离线冲突合并 · 多端待办同步")
.fontSize(22)
.fontWeight(FontWeight.Bold)
.margin({ top: 20 });
Text(`当前全局主题:${this.store.globalTheme}`)
.fontSize(16)
.fontColor("#0A59F7");
// 新增待办输入区
Row({ space: 10 }) {
TextInput({ text: this.inputText })
.layoutWeight(1)
.onChange(v => this.inputText = v);
Button("新增待办")
.onClick(() => this.addTodoItem());
}
.width("90%");
Button("切换全局主题(跨设备同步)")
.width("90%")
.onClick(() => this.toggleTheme());
List({ space: 10 }) {
ForEach(this.store.todoList, (item: TodoItem) => {
ListItem() {
Row({ space: 12 }) {
Text(item.content)
.layoutWeight(1)
.fontColor(item.finished ? "#999999" : "#000000");
Button(item.finished ? "未完成" : "已完成")
.onClick(() => this.toggleTodoFinish(item.id));
}
}
}, item => item.id)
}
.width("90%")
.height(300);
Blank();
Text("💡 断网离线修改待办,重连后自动自定义合并,不会丢失数据")
.fontSize(12)
.fontColor("#888888")
.margin({ bottom: 30 });
}
.width("100%")
.height("100%")
.padding({ left: 16, right: 16 })
.backgroundColor("#F8F9FA");
}
}
六、离线冲突完整业务测试流程
- 组网准备:两台同华为账号鸿蒙7真机,开启Wi-Fi/蓝牙/华为分享,完成设备组网;
- 在线基础同步验证:A设备新增待办,B设备页面实时自动刷新,双向同步正常;
- 制造离线场景:关闭其中一台设备Wi-Fi,断网离线;
- 两端离线修改:A离线新增待办A1,B离线新增待办B1,同时修改同一条待办完成状态;
- 恢复网络重连组网;
- 合并效果验证:两端自动触发自定义合并逻辑,A1、B1全部保留,同一条待办取更新时间更新的状态,无数据丢失,两端UI同步展示合并后完整列表。
七、高频踩坑与生产兜底方案
坑1:离线修改重连后数据直接覆盖,合并逻辑不执行
- 初始化必须设置
conflictPolicy: ConflictPolicy.CUSTOM_MERGE,默认是LAST_WRITE_WIN覆盖策略; - 同步模式必须使用
SyncMode.PUSH_PULL,开启离线缓存,REALTIME强一致模式离线修改不会缓存; - 实体变更必须调用
setData()提交,仅修改属性不提交不会生成向量时钟版本,无法触发冲突检测。
坑2:嵌套对象修改,远端设备UI不刷新
- 所有层级实体必须添加
@ObservedV2,可变更属性标记@Trace,保证V2响应式链路完整; - 禁止使用字面量
{}创建对象,必须通过new 类名()实例化,否则无分布式代理能力。
坑3:全设备同步,带宽、功耗过高
解决方案:初始化传入syncRange设备白名单,仅同步业务需要的终端,过滤无关设备;监听deviceChange动态更新白名单。
坑4:冲突合并后数据错乱、数组重复
- 合并数组使用Map根据唯一ID去重,不要简单数组拼接;
- 每条数据维护
updateTs时间戳,同条目冲突时以更新时间作为取舍依据; - 合并完成后必须调用
resolveConflict(mergedData)提交合并结果,否则变更不会落地。
坑5:页面反复创建同步实例,内存泄漏
页面aboutToDisappear必须执行destroy(),解绑conflict、syncComplete、deviceChange全部监听,长期运行自助终端必做。
坑6:模拟器无法调试分布式同步
模拟器无分布式软总线、设备认证、华为分享服务,必须两台真机同账号组网测试。
八、MultiKVStore 离线同步补充方案(海量结构化数据)
若业务存储上万条配置、日志类结构化数据,不适合MultiDistributedObject,选用MultiKVStore,同样支持自定义冲突合并、离线缓存:
// 初始化分布式KV
async function initUserKV() {
const kv = await MultiKVStore.createKVStore({
storeName: "user_setting_db",
autoSync: true,
defaultSyncMode: SyncMode.PUSH_PULL,
conflictPolicy: ConflictPolicy.CUSTOM_MERGE
});
// 监听KV冲突,自定义value合并逻辑
kv.on("kvConflict", (key, localVal, remoteVal, resolve) => {
// 自定义合并逻辑,返回最终值
const mergeVal = JSON.stringify({...JSON.parse(localVal), ...JSON.parse(remoteVal)});
resolve(mergeVal);
});
return kv;
}
九、商用落地最佳实践
- 业务分层选型
- 实时UI协同、页面状态同步 → MultiDistributedObject + ArkUI V2;
- 海量配置、离线表单、结构化数据 → MultiKVStore;
- 图片、文档跨设备互通 → MultiDfs分布式文件。
- 离线数据兜底
统一使用SyncMode.PUSH_PULL离线缓存模式,适配政务、门店自助终端经常断网场景; - 冲突合并标准化
统一以唯一ID去重、时间戳判断字段优先级,封装通用合并工具函数,减少重复业务代码; - 设备动态管控
监听设备上下线自动更新同步白名单,多门店、多平板场景减少无效同步; - 日志审计
同步完成、冲突合并、设备上下线全部写入hilog日志,线上问题可追溯; - 内存管控
单页面仅创建一个同步管理器实例,页面销毁彻底解绑监听,适配7×24小时无人值守商用终端。
十、方案总结
鸿蒙7 DistributedMultiKit 通过向量时钟冲突溯源、自定义字段合并、离线缓存双向同步、设备分组同步、ArkUI V2原生联动,彻底解决旧分布式套件离线修改丢失、多端数据冲突覆盖、同步带宽浪费三大商用痛点。
本文基于多端待办协同完整业务落地,实现断网离线修改、重连自动合并数据、两端UI无感刷新全流程,适配政务一体机、零售多屏导购、企业多设备办公、自助终端等超级终端离线高频使用场景。存量分布式项目迁移时优先替换底层同步工具层,统一使用CUSTOM_MERGE自定义冲突策略,从根源避免多端同步数据丢失线上故障。
更多推荐



所有评论(0)