跨设备听着很酷,做起来全是坑

HarmonyOS 最大的卖点之一就是分布式。手机、平板、手表、车机,数据无缝流转。听起来很美好,但真做起来,你会发现:设备发现不了、数据同步没回调、冲突了不知道怎么合并、权限漏配直接闪退。

很多同学第一次做分布式,直接 distributedDataManager.createKVManager() 一把梭,结果连设备都搜不到。问题不在 API,在于你没搞懂分布式数据管理的完整链路。

这篇就讲清楚:分布式数据库怎么建、数据怎么同步、冲突怎么解,以及那些让你 debug 到凌晨的坑。

https://placeholder-01.jpg/

一、分布式数据管理基础:别只会 createKVManager

分布式数据管理基于 KV 模型,核心是 KVManager。先看初始化:

typescript

import distributedDataManager from '@ohos.data.distributedDataManager';

const kvManagerConfig: distributedDataManager.KVManagerConfig = {
  context: getContext(this),
  bundleName: 'com.example.myapp'
};

let kvManager: distributedDataManager.KVManager;

async function initKvManager() {
  kvManager = await distributedDataManager.createKVManager(kvManagerConfig);
}

注意:bundleName 必须和 module.json5 里的一致,否则创建失败。

然后是创建或获取 KVStore:

typescript

const kvStoreConfig: distributedDataManager.KVStoreConfig = {
  storeId: 'myStore',
  securityLevel: distributedDataManager.SecurityLevel.S1
};

const kvStore = await kvManager.getKVStore(kvStoreConfig);

storeId 是数据库的唯一标识,同一应用的不同设备,只要 storeId 相同,就能同步。

权限别漏配。 分布式数据同步需要:

json

{
  "requestPermissions": [
    { "name": "ohos.permission.DISTRIBUTED_DATASYNC" }
  ]
}

这个权限是 user_grant 级别,需要动态申请。很多同学忘了申请,直接调同步接口,结果一直失败,还找不到原因。

二、数据同步:sync 不是万能的

同步接口很简单:

typescript

const deviceIds = ['device1', 'device2'];
await kvStore.sync(deviceIds, distributedDataManager.SyncMode.PUSH_PULL);

SyncMode 有三种:

  • PUSH:只推本地数据到远端。

  • PULL:只拉远端数据到本地。

  • PUSH_PULL:双向同步。

看起来很简单,但有两个大坑:

第一,deviceIds 从哪来? 你得先通过 @ohos.distributedDeviceManager 发现设备:

typescript

import deviceManager from '@ohos.distributedDeviceManager';

const dm = deviceManager.createDeviceManager('com.example.myapp');
const devices = dm.getTrustedDeviceListSync();
const deviceIds = devices.map(d => d.deviceId);

如果没有发现设备,deviceIds 就是空数组,sync 直接静默失败,没有任何报错。

第二,sync 是异步的,但没有回调。 它返回 Promise,resolve 只代表请求发出去了,不代表同步成功。真正的同步结果要通过 on('syncComplete') 监听:

typescript

kvStore.on('syncComplete', (data) => {
  console.info('同步完成: ' + JSON.stringify(data));
});

这个回调里会告诉你哪些设备同步成功、哪些失败。

https://placeholder-02.png/

三、数据订阅:别用轮询,用 on('dataChange')

数据同步过来之后,UI 怎么知道要刷新?很多人写个定时器轮询,不仅费电还延迟高。

正确做法是监听 dataChange:

typescript

kvStore.on('dataChange', distributedDataManager.SubscribeType.SUBSCRIBE_TYPE_REMOTE, (data) => {
  console.info('远端数据变化: ' + JSON.stringify(data));
  // 刷新 UI
  this.loadData();
});

SubscribeType 有三种:

  • SUBSCRIBE_TYPE_LOCAL:本地变化。

  • SUBSCRIBE_TYPE_REMOTE:远端变化。

  • SUBSCRIBE_TYPE_ALL:全部。

一般用 REMOTE 就够了,避免本地写入时也触发刷新,造成死循环。

坑来了:dataChange 回调里拿不到具体变化的数据。 它只告诉你“有变化”,不告诉你变了什么。所以你得在回调里重新查询一遍:

typescript

kvStore.on('dataChange', SubscribeType.SUBSCRIBE_TYPE_REMOTE, () => {
  kvStore.getEntries('').then((entries) => {
    this.dataList = entries;
  });
});

数据量大的时候,这个全量查询会很慢。更好的做法是用 getEntries 带前缀查询,或者维护一个本地版本号,只查变化的部分。

四、冲突解决:Last-Write-Wins 不是万能药

分布式最大的问题就是冲突。两台设备同时改同一条数据,同步的时候听谁的?

HarmonyOS 的 KVStore 默认采用 Last-Write-Wins(最后写入者胜出)策略,基于时间戳。但时间戳依赖设备时钟,如果设备时间不准,就会出问题。

比如手机时间快了 5 分钟,它写的任何数据都会“赢”,平板上的修改直接被覆盖。

解决办法有两个:

方案一:自己维护版本号。

typescript

interface DataItem {
  value: string;
  version: number;
  updateTime: number;
}

// 写入前先比较版本
const local = await kvStore.get(key) as DataItem;
if (remote.version > local.version) {
  await kvStore.put(key, remote);
} else {
  await kvStore.put(key, local);
}

方案二:用 CRDT(无冲突复制数据类型)。 但 HarmonyOS 原生不支持,需要自己实现,复杂度较高。

实际项目中,大部分场景用方案一就够了。关键是要在业务层定义好冲突规则,别指望框架帮你解决一切。

https://placeholder-03.png/

五、那些让你 debug 到凌晨的坑

坑一:设备发现不了。

检查三点:

  1. 两台设备是否登录了同一个华为账号。

  2. 是否在同一局域网。

  3. deviceManager 的 bundleName 是否和 KVManager 一致。

坑二:同步成功但数据没变。

syncComplete 回调里返回的是 networkId,不是 deviceId。如果你用 deviceId 去匹配,永远对不上。要用 networkId。

坑三:数据量大了同步慢。

KVStore 单条数据建议不超过 1MB,整个库不要超过 10MB。存大文件用分布式文件系统,别用 KVStore。

坑四:应用退出后数据丢失。

KVStore 默认不是持久化的。要持久化,得在 KVStoreConfig 里设置:

typescript

const config: distributedDataManager.KVStoreConfig = {
  storeId: 'myStore',
  securityLevel: distributedDataManager.SecurityLevel.S1,
  isBackup: true,
  isEncrypt: true
};

或者用 PersistentStorage 配合。

https://placeholder-04.jpg/

六、总结

这篇讲了分布式数据管理的初始化、同步、订阅和冲突解决。核心记住三点:

  1. 权限和设备发现是前提,deviceIds 为空则同步静默失败。

  2. syncComplete 回调里用 networkId,别用 deviceId。

  3. 冲突解决要在业务层定义规则,别完全依赖 Last-Write-Wins。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐