鸿蒙百篇系列第 62 篇 · 专家
第 9 篇我们讲过分布式能力的大图景。这一篇落到"分布式数据管理"这条主线:鸿蒙到底给了几套同步 API,它们之间的关系是什么,什么时候用哪个。很多团队上来就一句"我们要做跨设备同步",然后直接用了同步方案,等上线才发现选错。下面我把鸿蒙提供的几套同步机制整理出来,按"谁做什么"的维度拆开。
一、先理解三层数据模型
鸿蒙的分布式数据分三层,它的设计跟文件系统无关,跟传统 RDB 无关:
DDO 适合"对象级别的状态共享",KV 适合"配置、Token、计数器",关系数据库适合"业务表"。选错层的代价是后面迁移数据非常麻烦。
二、DDO:跨设备同步一个内存对象
DDO 是"为单条对象服务的同步通道"。它做的事情非常克制:
把一个 ArkTS 类的实例同步到同一组网下的其他设备
只同步有
@SyncField/@SyncIgnore标记的字段增量同步、字段级合并
import distributedDataObject from '@ohos.data.distributedDataObject';
class TodoItem extends distributedDataObject.Object {
@SyncField({ changeDetector: 'auto' }) public title: string = '';
@SyncField({ changeDetector: 'auto' }) public completed: boolean = false;
@SyncIgnore() public localOnly: string = '';
}
@Entry
@Component
struct TodoPage {
private todo: TodoItem = new TodoItem();
aboutToAppear(): void {
this.todo.title = '提交鸿蒙适配';
this.todo.completed = false;
// 创建同步通道,sessionId 用于标识同一个会话
this.todo.setSessionId('todo_global_session');
// 监听其他设备对本对象的修改
this.todo.on('change', (sessionId, fields) => {
console.info(`字段变更: ${fields.join(',')}`);
});
}
build() {
Column() {
Text(this.todo.title)
Toggle({ isOn: this.todo.completed })
.onChange((v) => { this.todo.completed = v; })
}
}
}
DDO 用起来简单,但它有三个限制,业务方容易踩:
不能存太复杂的数据结构。DDO 处理 plain object 很好,但塞进去一个包含循环引用的对象会失败
sessionId 决定同步域。两台设备用同一个 sessionId 才同步,默认按同账号+同应用来归类
冲突策略单一。只支持"最后写入胜出"或"自定义字段合并",不支持 CRDT 那种版本向量
三、分布式 KV 存储:团队协作的"事件总线"
KV 存储比 DDO 更底层、更灵活。鸿蒙把它叫做 DistributedKVStore,核心特点:
数据是 KV 对,按 storeId 分组
设备之间增量同步 key/value(不是全量)
可订阅"远端写入事件",知道是哪台设备改的
初始化代码(API 12 一致):
import distributedKVStore from '@ohos.data.distributedKVStore';
const kvManager = distributedKVStore.createKVManager({
bundleName: 'com.example.todo',
context: getContext(this)
});
const options: distributedKVStore.Options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: true,
kvStoreType: distributedKVStore.KVStoreType.DEVICE_COLLABORATION,
securityLevel: distributedKVStore.SecurityLevel.S2
};
const kv = await kvManager.getKVStore('todo_store', options);
// 写入
await kv.put('task:001', '提交报告');
// 订阅远端写入
kv.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL, (data) => {
for (const change of data.insertEntries) {
console.info(`设备 ${change.deviceId} 写了 ${change.key} = ${change.value.getValue()}`);
}
});
DEVICE_COLLABORATION 是最常用的类型,专为多设备协作场景设计。autoSync: true 会在写入时自动同步到已信任设备。
四、DataShare:跨应用数据共享
DataShare 跟前面两个完全不同的方向,它解决的是"多个应用之间共享同一份数据"。它的形态是 DataShareExtensionAbility,跟 UIAbility 一类但作用是提供数据访问接口:
// 数据提供方:定义共享的 URI
import { DataShareExtensionAbility, ColumnsAndValues } from '@ohos.data.dataShare';
export default class TodoDataShare extends DataShareExtensionAbility {
async query(uri: string): Promise<Array<any>> {
if (uri === 'datashare://com.example.todo/tasks') {
// 从数据库读,支持跨进程访问
return this.queryTasks();
}
return [];
}
async insert(uri: string, values: ColumnsAndValues): Promise<number> {
if (uri === 'datashare://com.example.todo/tasks') {
return this.insertTask(values);
}
return -1;
}
async update(uri: string, predicates, values: ColumnsAndValues): Promise<number> {
return this.updateTask(predicates, values);
}
async delete(uri: string, predicates): Promise<number> {
return this.deleteTask(predicates);
}
}
访问方用 dataShare.createDataShareHelper(uri) 创建帮助类,然后在 UI 侧直接当个数据源用。这是多端、多应用协作的统一接入口。
五、五种同步方案对比
工程上经常被问"我到底用哪个",下面这张表整理了下决策路径:
我自己经验里的几个判断:
对象属性 + 实时可见:用 DDO
配置 + 多设备生效 + 不挑剔冲突:用分布式 Preferences
状态总线 + 跨应用订阅:用分布式 KV
跨应用数据查询 + SQL 风格:用 DataShare
业务表 + 端云 + 严格一致性:用分布式 RDB
六、实战:用户配置中心
我用一个小例子把 KV + DDO 用起来,看看它们怎么搭配。需求:
一个用户的"是否开启暗黑模式"配置同步到他所有设备
一个 List 的"是否展开"是按 List 共享的,不和别的 List 混
import Preferences from '@ohos.data.preferences';
import distributedPreferences from '@ohos.data.distributedPreferences';
import distributedKVStore from '@ohos.data.distributedKVStore';
class ConfigCenter {
// 1. 跨设备共享的偏好
private distPref: distributedPreferences.Preferences | null = null;
// 2. 按 listId 共享的多设备 KV
private kv: distributedKVStore.KVStore | null = null;
async init() {
// 分布式 Preferences:用户级、key-value,自动同步到同账号的所有设备
this.distPref = await distributedPreferences.getPreferences(getContext(this), 'user_config');
// 分布式 KV:按 listId 隔离
const mgr = distributedKVStore.createKVManager({
bundleName: 'com.example.app'
});
this.kv = await mgr.getKVStore('shared_state', {
createIfMissing: true,
autoSync: true,
kvStoreType: distributedKVStore.KVStoreType.DEVICE_COLLABORATION,
securityLevel: distributedKVStore.SecurityLevel.S2
});
}
async setDarkMode(on: boolean) {
await this.distPref?.put('dark_mode', on);
await this.distPref?.flush();
}
async isDarkMode(): Promise<boolean> {
return (await this.distPref?.get('dark_mode', false)) as boolean;
}
async setListExpanded(listId: string, expanded: boolean) {
await this.kv?.put(`expand:${listId}`, expanded ? '1' : '0');
}
isListExpanded(listId: string): Promise<boolean> {
return this.kv?.get(`expand:${listId}`).then(v => v === '1') || Promise.resolve(false);
}
}
七、几个工程要点
autoSync别乱开。开了之后每次写入都触发同步,如果你的写入是高频请求(滚动日志、埋点),成本吃不消。要么手动sync(syncMode, delayMs)控制节奏大对象不要走 DDO。DDO 整对象同步虽然增量,框架会做一次 hash 对比,对象很大时 hash 计算本身贵
DataShare 必须显式声明权限。
module.json5里的dataShare字段没配好,跨进程调用会直接失败冲突不要赌运气。即使用了"最后写入胜出",UI 上也要带个"合并"按钮,让用户主动解决,不要让他们看到内容来回变
测试一定要用真机。模拟器的分布式能力是大大缩水甚至不工作的,拿模拟器验证同步逻辑几乎没用
八、和前几篇的衔接
第 9 篇讲了分布式大图景。本篇是把"分布式数据"那一支挖到底
第 56 篇讲了 IPC 与 DataShareExtension。这一篇把它和 KV、DDO 拼起来看,就清楚各自的边界
第 35 篇"动态化"里提的卡片数据下发,本质上是端云协同 + DataShare 配合
九、小结
分布式数据管理在鸿蒙里是一套分层方案:DDO 同步对象、KV 同步键值、DataShare 跨进程共享数据、RDB + 云跨端云协同。选错层不只是性能问题,还会影响后续能不能扩展。下次被问"我要做跨设备同步",别急着动手,先问一句"你到底是同步一个对象、一组 KV、还是一张表"。
下一篇转向"安全沙箱",讨论应用间为什么天然隔开、目录边界怎么定、权限链怎么校验。