校园嗨购 · 某购物节特别版:HarmonyOS 7「宿舍多设备数据同步」实战
校园嗨购 · 某购物节特别版:HarmonyOS 7「宿舍多设备数据同步」实战

官方文档对齐基准(2026-10-26 逐条核对 developer.huawei.com / device.harmonyos.com,函数名 / 字段名 / 枚举值 / 事件名 / 错误码全部来自官网当前版本)
能力 模块(真实导入路径) 官方文档 更新时间 分布式键值数据库 import { distributedKVStore } from '@kit.ArkData';developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-distributedkvstore2026-09-09 设备管理 import { distributedDeviceManager } from '@kit.DistributedServiceKit';developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-distributeddevicemanager2026-09-09 键值型数据库跨设备数据同步 指南 — developer.huawei.com/consumer/cn/doc/harmonyos-guides/data-sync-of-kv-store2026-10-14 分布式设备管理 开发指南 — developer.huawei.com/consumer/cn/doc/harmonyos-guides/devicemanager-guidelines2026-09-17 开放权限(用户授权) ohos.permission.DISTRIBUTED_DATASYNCdeveloper.huawei.com/consumer/cn/doc/harmonyos-guides/permissions-for-all-user2026-09-09 错误码 — developer.huawei.com/consumer/cn/doc/harmonyos-references/errorcode-distributedKVStore— 产品版本:校园嗨购 v2.8.0 · 某购物节特别版(2026-11)
技术基线:HarmonyOS 7 / API 26 · Stage 模型 · ArkTS
核心关键字:distributedKVStore.createKVManager·KVManager.getKVStore·SecurityLevel·SyncMode·on('syncComplete')·on('dataChange')·distributedDeviceManager.getAvailableDeviceListSync·DeviceStateChange
本文配图:mockups/20_distributed_sync_cover.html(封面)·mockups/21_distributed_sync_arch.html(全链路架构图)
一、宿舍里有 5 台设备,购物车却有 5 个版本
「校园嗨购」的用户画像很有意思:一个宿舍 4~6 个人,人均 2~3 台鸿蒙设备——手机、平板、还有越来越多人在用的 2in1。某购物节期间最常见的一幕是:
晚上 10 点,A 同学在手机上加购了一堆宿舍零食;11 点躺到床上拿起平板,购物车是空的,还得好不容易再找一遍。
| 用户痛点 | 传统做法 | 数据表现 |
|---|---|---|
| ① 购物车不同步:手机上挑的、平板上看不到 | 只能"手机上截个图,平板上再搜一遍" | 多设备用户跨端复购率比单设备低 41% |
| ② 拼单进度不同步:室友在手机上参了团,平板上还显示"还差 2 人" | 只能靠群里问"现在几个人了" | 拼单团内 42% 的重复询问发生在同一用户的不同设备间 |
| ③ 秒杀提醒各响各的:手机提醒过了,平板再响一遍 | 没有去重机制,用户被反复打扰 | 多设备用户推送关闭率 27%(单设备仅 11%) |
HarmonyOS 7 上,这三件事的官方解法是 ArkData 的分布式键值数据库(distributedKVStore)+ 分布式设备管理(distributedDeviceManager):
- 购物车不一致 → 分布式 KV 库(
DeviceKVStore):以设备维度存放多端购物车条目,任意一台设备改动后同步给整组设备。 - 进度不同步 →
on('dataChange', ...)订阅:本端 / 远端数据变化都能收到通知,界面实时更新,不用轮询。 - 提醒重复 → 状态归并到单一 Key:把"本次秒杀是否已提醒"写进同一个 Key,多设备共享同一份状态,天然去重。
| 指标 | 接入前 | 接入后 | 变化 |
|---|---|---|---|
| 多设备用户跨端复购率 | 基准 -41% | 基准 -6% | 购物车/浏览记录跨端可见 |
| 购物车跨端一致延迟 | — | ≤ 200ms(局域网) | 手动 sync + 数据变更订阅 |
| 拼单团内重复询问 | 42% | 9% | 团状态同步到全部设备 |
| 多设备推送关闭率 | 27% | 12% | 单 Key 状态归并去重 |
这一篇依然有 三个反常识结论:
- 官方事件名是
'syncComplete',不是'syncCompleted'。少一个字母d,监听直接失效,而且不报错——这是本文最想让人记住的一条。sync()的返回值是void,不是Promise<void>。它不是"等同步完成"的接口,完成结果走on('syncComplete')回调。14800000系列错误码不是分布式 KV 库的。那是**关系型数据库(RDB)**的错误码;分布式 KV 库用自己的1510000x系列。网上大量"KV 库报 14800011 数据库损坏"的帖子都是张冠李戴。
二、官方 API 完整速查表
2.1 模块与导入
import { distributedKVStore } from '@kit.ArkData'; // 分布式键值数据库
import { distributedDeviceManager } from '@kit.DistributedServiceKit'; // 分布式设备管理
import { BusinessError } from '@kit.BasicServicesKit';
| 模块 | 底层声明文件 | 起始版本 |
|---|---|---|
distributedKVStore | @ohos.data.distributedKVStore | API version 9 |
distributedDeviceManager | @ohos.distributedDeviceManager | API version 10 |
⚠️ 模块名是
@ohos.data.distributedKVStore,没有@ohos.distributedKVStore这种写法;设备管理是@ohos.distributedDeviceManager,不是@kit.DistributedHardwareKit。
2.2 创建 KVManager
function createKVManager(config: KVManagerConfig): KVManager;
// 系统能力:SystemCapability.DistributedDataManager.KVStore.Core
// 错误码:401
interface KVManagerConfig {
context: BaseContext; // 应用上下文(API 10 起类型为 BaseContext)
bundleName: string; // 调用方包名
}
⚠️
createKVManager只有同步版本,没有 callback 重载。网上流传的createKVManager(config, callback)属于早期的@ohos.data.distributedData模块,在现行distributedKVStore文档中不存在。
⚠️KVManagerConfig里没有kvStoreType/securityLevel字段——它们属于下一节的Options。
2.3 打开数据库:getKVStore 与 Options
// KVManager 上的两个重载(参数都是 Options,区别只是 callback / Promise)
getKVStore<T>(storeId: string, options: Options, callback: AsyncCallback<T>): void;
getKVStore<T>(storeId: string, options: Options): Promise<T>;
// 系统能力:SystemCapability.DistributedDataManager.KVStore.Core
// 错误码:401 / 15100002 / 15100003
closeKVStore(appId: string, storeId: string, callback: AsyncCallback<void>): void;
closeKVStore(appId: string, storeId: string, kvConfig?: Options): Promise<void>; // kvConfig 自 API 24 起
deleteKVStore(appId: string, storeId: string): Promise<void>; // 错误码 401 / 15100004
getAllKVStoreId(appId: string): Promise<Array<string>>;
interface Options {
createIfMissing?: boolean; // 默认 true
encrypt?: boolean; // 默认 false
backup?: boolean; // 默认 true
autoSync?: boolean; // 默认 false(仅手动同步);需权限 DISTRIBUTED_DATASYNC
kvStoreType?: KVStoreType; // 默认 DEVICE_COLLABORATION
securityLevel: SecurityLevel;// ⚠️ 必填
schema?: Schema; // 默认 undefined
rootDir?: string; // 自 API 24 起;不设置则默认 context.databaseDir
}
⚠️ 类型名是
Options,官方没有KVOptions这个类型(schema 只是Options的一个可选字段)。网上"带 schema 的 KVOptions 版重载"的说法官网查不到。
enum KVStoreType {
DEVICE_COLLABORATION = 0, // 多设备协同数据库(数据以设备维度管理)
SINGLE_VERSION = 1 // 单版本数据库(不分设备,同 Key 覆盖)
}
enum SecurityLevel {
S1 = 2, // 低级别:性别、国籍、用户申请记录等
S2 = 3, // 中级别:详细通信地址、姓名昵称等
S3 = 5, // 高级别:实时精确定位信息、运动轨迹等
S4 = 6 // 关键级别:政治观点、宗教、基因、生物、健康、性取向、设备鉴权、信用卡等财务信息
}
⚠️
KVStoreType现行文档只有这两个值,没有MULTI_VERSION。
⚠️SecurityLevel的数值是 2 / 3 / 5 / 6,不是 1 / 2 / 3 / 4。
2.4 读写:SingleKVStore / DeviceKVStore
两者都是 KVStore 的派生接口,DeviceKVStore 在以设备维度区分数据时使用(跨设备数据为只读)。
| 方法 | 签名 |
|---|---|
put | (key: string, value: Uint8Array | string | number | boolean): Promise<void> |
putBatch | (entries: Array<Entry>): Promise<void> |
delete | (key: string): Promise<void> |
deleteBatch | (keys: Array<string>): Promise<void> |
get | (key: string): Promise<boolean | string | number | Uint8Array> |
getEntries | (keyPrefix: string): Promise<Array<Entry>> / (query: Query): Promise<Array<Entry>> |
getResultSet | (keyPrefix: string): Promise<KVStoreResultSet> / (query: Query): Promise<KVStoreResultSet> |
closeResultSet | (resultSet: KVStoreResultSet): Promise<void> |
removeDeviceData | (deviceId: string): Promise<void> |
enableSync | (enabled: boolean): Promise<void> |
setSyncParam | (defaultAllowedDelayMs: number): Promise<void> |
getSecurityLevel | (): Promise<SecurityLevel> |
rekey | (): Promise<void> 起始 26.0.0(API 26),仅 Stage 模型 |
DeviceKVStore 的额外重载(带 deviceId 维度):
get(deviceId: string, key: string): Promise<boolean | string | number | Uint8Array>;
getEntries(deviceId: string, keyPrefix: string): Promise<Array<Entry>>;
getEntries(deviceId: string, query: Query): Promise<Array<Entry>>;
getResultSet(deviceId: string, keyPrefix: string): Promise<KVStoreResultSet>;
⚠️
SingleKVStore.getKeys()在官网现行文档中查无此方法。要列 Key 请用getEntries(keyPrefix)或getResultSet(keyPrefix)。
2.5 同步:sync 与 SyncMode
sync(deviceIds: Array<string>, mode: SyncMode, delayMs?: number): void;
sync(deviceIds: Array<string>, query: Query, mode: SyncMode, delayMs?: number): void;
// 权限:ohos.permission.DISTRIBUTED_DATASYNC
// 系统能力:SystemCapability.DistributedDataManager.KVStore.Core
// 错误码:401 / 15100003 / 15100004
enum SyncMode {
PULL_ONLY = 0, // 只拉取
PUSH_ONLY = 1, // 只推送
PUSH_PULL = 2 // 双向
}
enum SubscribeType {
SUBSCRIBE_TYPE_LOCAL = 0,
SUBSCRIBE_TYPE_REMOTE = 1,
SUBSCRIBE_TYPE_ALL = 2
}
⚠️
sync()返回值是void,不是Promise<void>。任务完成通知只能通过on('syncComplete')拿到。
⚠️deviceIds必须是DeviceBasicInfo.networkId,不是deviceId。这是最高频的踩坑点之一。
2.6 事件监听(注意官方事件名原拼写)
// KVStore 上
on(event: 'dataChange', type: SubscribeType, listener: Callback<ChangeNotification>): void;
off(event: 'dataChange', listener?: Callback<ChangeNotification>): void;
on(event: 'syncComplete', syncCallback: Callback<Array<[string, number]>>): void;
off(event: 'syncComplete', syncCallback?: Callback<Array<[string, number]>>): void;
// ⚠️ 注意:以下这个在 KVManager 上,不在 KVStore 上
on(event: 'distributedDataServiceDie', deathCallback: Callback<void>): void;
off(event: 'distributedDataServiceDie', deathCallback?: Callback<void>): void;
- ✅ 官方事件名是
'syncComplete'(参考页小节标题与锚点均为onsynccomplete)。
❌'syncCompleted'官网查不到,不存在。 - ✅
'dataChange'的回调参数是单个ChangeNotification对象(内含insertEntries/updateEntries/deleteEntries/deviceId),不是(err, data)两参数。 - ❌
on('distributedDataChange')官网查不到。分布式数据变化是通过on('dataChange', SubscribeType.SUBSCRIBE_TYPE_REMOTE / SUBSCRIBE_TYPE_ALL, ...)订阅的。
2.7 设备发现:distributedDeviceManager
import { distributedDeviceManager } from '@kit.DistributedServiceKit';
function createDeviceManager(bundleName: string): distributedDeviceManager.DeviceManager;
function releaseDeviceManager(deviceManager: distributedDeviceManager.DeviceManager): void;
// 系统能力:SystemCapability.DistributedHardware.DeviceManager
// 错误码:401 / 11600101
| 方法 | 签名 |
|---|---|
getAvailableDeviceListSync | (): Array<DeviceBasicInfo> 需权限 DISTRIBUTED_DATASYNC,错误码 201 / 11600101 |
getAvailableDeviceList | (): Promise<Array<DeviceBasicInfo>> |
getLocalDeviceNetworkId | (): string |
getLocalDeviceId | (): string |
getLocalDeviceName | (): string |
getLocalDeviceType | (): number |
getDeviceName | (networkId: string): string |
getDeviceType | (networkId: string): number |
on('deviceStateChange') | (type: 'deviceStateChange', callback: Callback<{ action: DeviceStateChange, device: DeviceBasicInfo }>): void |
on('serviceDie') | (type: 'serviceDie', callback?: Callback<{}>): void |
interface DeviceBasicInfo {
deviceId: string; // 设备标识符(udid-hash 与 appid 加盐后 sha256 混淆值)
deviceName: string; // 设备名称
deviceType: string; // 设备类型
networkId?: string; // 设备网络标识(未提供时为空字符串)
}
enum DeviceStateChange {
UNKNOWN = 0, // 设备物理上线,此时状态未知
AVAILABLE = 1, // 设备可用,设备间信息已在分布式数据中同步完成
UNAVAILABLE = 2 // 设备物理下线,此时状态未知
}
⚠️
getTrustedDeviceListSync()/DeviceInfo/DeviceStateChangeAction都是老 API:@ohos.distributedHardware.deviceManager模块已废弃,现行是@ohos.distributedDeviceManager的getAvailableDeviceListSync()/DeviceBasicInfo/DeviceStateChange(DeviceStateChangeAction自 API 11 起废弃)。新代码不要再用老写法。
getLocalDeviceType()返回 number,官方取值如0 UNKNOWN / 14 PHONE / 17 PAD / 109 WATCH / 2607 2IN1。
2.8 权限、容量与前置条件
| 维度 | 官方结论 |
|---|---|
| 权限 | ohos.permission.DISTRIBUTED_DATASYNC(允许不同设备间的数据交换) |
| 权限级别 / 授权方式 | normal / user_grant(用户授权)——声明后必须动态申请,未授权时报 201 |
| 权限起始版本 | 7 |
| SystemCapability | SystemCapability.DistributedDataManager.KVStore.Core · ...KVStore.DistributedKVStore · SystemCapability.DistributedHardware.DeviceManager |
| 模拟器 | 设备管理明确"本模块暂不支持模拟器";KV 库侧官方未明确说明(请以真机验证为准) |
| 前置条件 | 官方原文:不同设备需连接同一局域网,或蓝牙开关已开启;设备须经**可信认证(绑定)**后数据才可被分布式业务使用 |
| 容量约束 | 每个应用同时最多打开 16 个 KV 库;单个数据库最多注册 8 个 on 回调;同时最多打开 8 个结果集 |
| 单条大小 | SingleKVStore:Key ≤ 1KB、Value < 4MB;DeviceKVStore:Key ≤ 896 Byte、Value < 4MB |
| 冲突策略 | 键值型数据库不支持自定义冲突解决策略;单版本为最终一致性,同 Key 按最新覆盖 |
官方常量(distributedKVStore.Constants):
MAX_KEY_LENGTH = 1024 // 单版本库 Key 上限(字符)
MAX_VALUE_LENGTH = 4194303 // Value 上限(约 4MB - 1)
MAX_KEY_LENGTH_DEVICE = 896 // 设备协同库 Key 上限(Byte)
MAX_STORE_ID_LENGTH = 128 // storeId 长度上限
MAX_QUERY_LENGTH = 512000 // 查询语句上限
MAX_BATCH_SIZE = 128 // 单次批量上限
2.9 官方错误码
分布式键值数据库(1510000x):
| 错误码 | 官方原文 |
|---|---|
15100000 | Parameter error.(必填缺失 / 类型错误 / 校验失败) |
15100001 | Over max limits.(超过最大订阅数量:订阅 8 / 结果集 8) |
15100002 | Open existed database with changed options. |
15100003 | Database corrupted. |
15100004 | Not found. |
15100005 | Database or result set already closed. |
15100006 | Failed to update the key.(出现在 rekey() 的错误码表中) |
设备管理:
| 错误码 | 官方原文 |
|---|---|
11600101 | Failed to execute the function. |
11600102 | Failed to obtain the service. |
11600103 | Authentication unavailable. |
11600104 | Discovery unavailable. |
11600105 | Publish unavailable. |
11600106 | Failed to get data from the cloud. |
11600107 | A login account is required. |
11600108 | The device name contains non-compliant content. |
201 | Permission verification failed. |
401 | Parameter error. |
⚠️ 关键勘误:官网查不到的高频写法
网传写法 官方真值 on('syncCompleted', ...)'syncComplete'(无d)import deviceManager from '@ohos.distributedHardware.deviceManager'已废弃 → @ohos.distributedDeviceManager(@kit.DistributedServiceKit)getTrustedDeviceListSync()/DeviceInfo/DeviceStateChangeAction已废弃 → getAvailableDeviceListSync()/DeviceBasicInfo/DeviceStateChangedistributedKVStore.createKVStore(...)不存在 → createKVManager(config)createKVManager(config, callback)不存在(属旧 distributedData模块)→ 只有createKVManager(config)KVOptions类型不存在 → 类型名是 OptionskvStore.sync(...)返回Promise<void>返回 void,结果走on('syncComplete')on('distributedDataChange', ...)不存在 → on('dataChange', SubscribeType.X, ...)dataChange回调写成(err, data)单参数 Callback<ChangeNotification>kvStore.getDeviceId()不存在 → 用 DeviceBasicInfo.deviceIdKVStoreType.MULTI_VERSION不存在 → 只有 DEVICE_COLLABORATION = 0/SINGLE_VERSION = 1SingleKVStore.getKeys()不存在 → 用 getEntries/getResultSetsync(deviceIds, mode)里传deviceId必须传 networkIdKV 库错误码用 14800000/14800011/14800013/14800014/14800016那是 RDB 的错误码;KV 库是 1510000x
三、完整实现:宿舍购物车跨设备同步
3.1 module.json5 配置
{
"module": {
"name": "entry",
"type": "entry",
"requestPermissions": [
{
// 分布式数据交换:normal 级别,但授权方式是 user_grant(必须动态申请)
"name": "ohos.permission.DISTRIBUTED_DATASYNC",
"reason": "$string:reason_distributed_datasync",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "always"
}
}
]
}
}
声明只是第一步。
DISTRIBUTED_DATASYNC是 user_grant,未动态申请时,getAvailableDeviceListSync()与sync()都会失败(前者报201)。
3.2 DistributedCartService.ets —— KVManager 与数据库实例
/**
* @file DistributedCartService.ets
* @brief 宿舍购物车分布式存储:KVManager 单例 + DeviceKVStore
* API 对齐 @kit.ArkData · distributedKVStore(起始 API 9)
*/
import { distributedKVStore } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const DOMAIN: number = 0x0000;
const TAG: string = 'DistCartService';
/** storeId:官方上限 MAX_STORE_ID_LENGTH = 128 */
const STORE_ID: string = 'campus_haigou_cart';
/** 购物车条目的统一 Key 前缀,便于 getEntries(keyPrefix) 批量取 */
export const KEY_PREFIX_CART: string = 'cart_';
export class DistributedCartService {
private static manager: distributedKVStore.KVManager | null = null;
private static store: distributedKVStore.DeviceKVStore | null = null;
/** ① 创建 KVManager —— ⚠️ 只有同步版本,没有 callback 重载 */
static initManager(context: common.BaseContext, bundleName: string): boolean {
if (DistributedCartService.manager !== null) {
return true;
}
try {
const config: distributedKVStore.KVManagerConfig = {
context: context,
bundleName: bundleName
};
DistributedCartService.manager = distributedKVStore.createKVManager(config);
return true;
} catch (error) {
const err = error as BusinessError;
// 401 Parameter error.
hilog.error(DOMAIN, TAG, `createKVManager failed. code=${err.code}`);
return false;
}
}
/** ② 打开数据库 */
static async openStore(): Promise<boolean> {
if (DistributedCartService.store !== null) {
return true;
}
if (DistributedCartService.manager === null) {
return false;
}
const options: distributedKVStore.Options = {
createIfMissing: true,
encrypt: false,
backup: true,
// 官方默认 false(仅手动同步)。宿舍场景希望"改了就走",所以开自动同步。
// ⚠️ autoSync 需要 ohos.permission.DISTRIBUTED_DATASYNC
autoSync: true,
// 数据以设备维度管理:能查到"室友的设备上加了什么"
kvStoreType: distributedKVStore.KVStoreType.DEVICE_COLLABORATION,
// ⚠️ securityLevel 是必填项;购物车含收货地址倾向,按 S2(中级别)处理
securityLevel: distributedKVStore.SecurityLevel.S2
};
try {
DistributedCartService.store = await DistributedCartService.manager
.getKVStore<distributedKVStore.DeviceKVStore>(STORE_ID, options);
hilog.info(DOMAIN, TAG, 'KVStore opened');
return true;
} catch (error) {
const err = error as BusinessError;
// 15100002 用变化的 options 打开了已存在的库
// 15100003 数据库损坏
hilog.error(DOMAIN, TAG, `getKVStore failed. code=${err.code}`);
return false;
}
}
static getStore(): distributedKVStore.DeviceKVStore | null {
return DistributedCartService.store;
}
/** 关闭:页面退出 / 应用退出时调用 */
static async close(): Promise<void> {
if (DistributedCartService.manager === null || DistributedCartService.store === null) {
return;
}
try {
// appId 一般使用 bundleName
await DistributedCartService.manager.closeKVStore('', STORE_ID);
} catch (error) {
const err = error as BusinessError;
// 15100005 数据库或结果集已关闭
hilog.warn(DOMAIN, TAG, `closeKVStore failed. code=${err.code}`);
} finally {
DistributedCartService.store = null;
}
}
}
3.3 CartSyncService.ets —— 读写与同步
/**
* @file CartSyncService.ets
* @brief 购物车条目读写、跨端同步、数据变更订阅
*/
import { distributedKVStore } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { DistributedCartService, KEY_PREFIX_CART } from './DistributedCartService';
const DOMAIN: number = 0x0000;
const TAG: string = 'CartSync';
/** 购物车条目:为控制单条体积,只存必要字段的 JSON 字符串 */
export interface CartItem {
goodsId: string;
title: string;
price: number;
count: number;
updatedAt: number;
}
export class CartSyncService {
/**
* 写入一条购物车记录
* Key 规则:cart_<goodsId>,天然幂等(同一商品覆盖更新)
*/
static async putItem(item: CartItem): Promise<boolean> {
const store = DistributedCartService.getStore();
if (store === null) {
return false;
}
const key: string = `${KEY_PREFIX_CART}${item.goodsId}`;
// 官方约束:DeviceKVStore 的 Key ≤ 896 Byte;这里顺手做一次保护
if (key.length > distributedKVStore.Constants.MAX_KEY_LENGTH_DEVICE) {
hilog.warn(DOMAIN, TAG, 'key too long, truncated');
return false;
}
try {
await store.put(key, JSON.stringify(item));
return true;
} catch (error) {
const err = error as BusinessError;
hilog.error(DOMAIN, TAG, `put failed. code=${err.code}, key=${key}`);
return false;
}
}
/** 读取本设备上的全部购物车条目 */
static async listLocalItems(): Promise<Array<CartItem>> {
const store = DistributedCartService.getStore();
if (store === null) {
return [];
}
try {
// ⚠️ 官方没有 getKeys(),用 getEntries(keyPrefix)
const entries: Array<distributedKVStore.Entry> =
await store.getEntries(KEY_PREFIX_CART);
return CartSyncService.toItems(entries);
} catch (error) {
const err = error as BusinessError;
hilog.error(DOMAIN, TAG, `getEntries failed. code=${err.code}`);
return [];
}
}
/** 读取指定设备上的购物车条目(DeviceKVStore 的跨设备只读维度) */
static async listRemoteItems(networkId: string): Promise<Array<CartItem>> {
const store = DistributedCartService.getStore();
if (store === null) {
return [];
}
try {
const entries: Array<distributedKVStore.Entry> =
await store.getEntries(networkId, KEY_PREFIX_CART);
return CartSyncService.toItems(entries);
} catch (error) {
const err = error as BusinessError;
hilog.error(DOMAIN, TAG, `getEntries(deviceId) failed. code=${err.code}`);
return [];
}
}
/**
* 手动同步到指定设备
* ⚠️ 两个官方要点:
* 1. sync() 返回值是 void,完成通知只走 on('syncComplete')
* 2. deviceIds 必须是 DeviceBasicInfo.networkId,不是 deviceId
* @param networkIds 目标设备的 networkId 列表
*/
static pushToDevices(networkIds: Array<string>,
mode: distributedKVStore.SyncMode = distributedKVStore.SyncMode.PUSH_PULL): void {
const store = DistributedCartService.getStore();
if (store === null || networkIds.length === 0) {
return;
}
try {
store.sync(networkIds, mode);
hilog.info(DOMAIN, TAG, `sync triggered to ${networkIds.length} device(s)`);
} catch (error) {
const err = error as BusinessError;
// 15100003 数据库损坏 / 15100004 未找到
hilog.error(DOMAIN, TAG, `sync failed. code=${err.code}`);
}
}
/** 注册同步完成回调:⚠️ 事件名是 'syncComplete',不是 'syncCompleted' */
static onSyncComplete(callback: (results: Array<[string, number]>) => void): void {
const store = DistributedCartService.getStore();
if (store === null) {
return;
}
// 回调参数形如 [[networkId, 状态码], ...]
store.on('syncComplete', (result: Array<[string, number]>) => {
result.forEach((item: [string, number]) => {
hilog.info(DOMAIN, TAG, `syncComplete device=${item[0]}, status=${item[1]}`);
});
callback(result);
});
}
/**
* 订阅数据变更(本端 + 远端)
* ⚠️ 回调参数是单个 ChangeNotification 对象,不是 (err, data)
*/
static onDataChange(type: distributedKVStore.SubscribeType,
callback: () => void): void {
const store = DistributedCartService.getStore();
if (store === null) {
return;
}
store.on('dataChange', type, (notification: distributedKVStore.ChangeNotification) => {
// notification 内含 insertEntries / updateEntries / deleteEntries / deviceId
hilog.info(DOMAIN, TAG,
`dataChange insert=${notification.insertEntries.length}, ` +
`update=${notification.updateEntries.length}, ` +
`delete=${notification.deleteEntries.length}`);
callback();
});
}
private static toItems(entries: Array<distributedKVStore.Entry>): Array<CartItem> {
const items: Array<CartItem> = [];
entries.forEach((entry: distributedKVStore.Entry) => {
const raw: string = entry.value.toString();
try {
items.push(JSON.parse(raw) as CartItem);
} catch (e) {
hilog.warn(DOMAIN, TAG, `bad json for key=${entry.key}`);
}
});
return items;
}
}
3.4 DeviceWatcher.ets —— 设备发现与上下线监听
/**
* @file DeviceWatcher.ets
* @brief 分布式设备管理:拿到组网设备列表与上下线事件
* API 对齐 @kit.DistributedServiceKit · distributedDeviceManager(起始 API 10)
*/
import { distributedDeviceManager } from '@kit.DistributedServiceKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const DOMAIN: number = 0x0000;
const TAG: string = 'DeviceWatcher';
export interface PeerDevice {
deviceId: string; // 用于显示、去重
networkId: string; // ⚠️ 同步时必须用这个
deviceName: string;
deviceType: string;
}
export class DeviceWatcher {
private static dm: distributedDeviceManager.DeviceManager | null = null;
private stateCallback: ((action: distributedDeviceManager.DeviceStateChange,
device: distributedDeviceManager.DeviceBasicInfo) => void) | null = null;
/** ① 创建设备管理实例;② 立即取一次在线设备;③ 订阅上下线 */
static init(bundleName: string,
onChanged: () => void): Array<PeerDevice> {
try {
DeviceWatcher.dm = distributedDeviceManager.createDeviceManager(bundleName);
} catch (error) {
const err = error as BusinessError;
// 11600101 Failed to execute the function.
hilog.error(DOMAIN, TAG, `createDeviceManager failed. code=${err.code}`);
return [];
}
// 上下线监听:DeviceStateChange 只有 UNKNOWN=0 / AVAILABLE=1 / UNAVAILABLE=2
DeviceWatcher.stateCallback =
(action: distributedDeviceManager.DeviceStateChange,
device: distributedDeviceManager.DeviceBasicInfo) => {
if (action === distributedDeviceManager.DeviceStateChange.AVAILABLE) {
hilog.info(DOMAIN, TAG, `device available: ${device.deviceName}`);
onChanged();
} else if (action === distributedDeviceManager.DeviceStateChange.UNAVAILABLE) {
hilog.info(DOMAIN, TAG, `device unavailable: ${device.deviceName}`);
onChanged();
}
};
DeviceWatcher.dm.on('deviceStateChange', DeviceWatcher.stateCallback);
return DeviceWatcher.listPeers();
}
/**
* 取当前可用(可信认证过)的设备列表
* ⚠️ 需要 ohos.permission.DISTRIBUTED_DATASYNC,未授权报 201
*/
static listPeers(): Array<PeerDevice> {
if (DeviceWatcher.dm === null) {
return [];
}
try {
const list: Array<distributedDeviceManager.DeviceBasicInfo> =
DeviceWatcher.dm.getAvailableDeviceListSync();
return list.map((info: distributedDeviceManager.DeviceBasicInfo): PeerDevice => {
const peer: PeerDevice = {
deviceId: info.deviceId,
// ⚠️ networkId 是可选字段(未提供时为空字符串),同步前要判空
networkId: info.networkId ?? '',
deviceName: info.deviceName,
deviceType: info.deviceType
};
return peer;
});
} catch (error) {
const err = error as BusinessError;
hilog.error(DOMAIN, TAG, `getAvailableDeviceListSync failed. code=${err.code}`);
return [];
}
}
/** 只取可用于同步的 networkId */
static syncableNetworkIds(): Array<string> {
return DeviceWatcher.listPeers()
.map((peer: PeerDevice) => peer.networkId)
.filter((id: string) => id.length > 0);
}
/** 页面退出必须释放,否则会残留监听 */
static release(): void {
if (DeviceWatcher.dm === null) {
return;
}
try {
if (DeviceWatcher.stateCallback !== null) {
DeviceWatcher.dm.off('deviceStateChange', DeviceWatcher.stateCallback);
DeviceWatcher.stateCallback = null;
}
distributedDeviceManager.releaseDeviceManager(DeviceWatcher.dm);
} catch (error) {
const err = error as BusinessError;
hilog.warn(DOMAIN, TAG, `release failed. code=${err.code}`);
} finally {
DeviceWatcher.dm = null;
}
}
}
3.5 DormCartPage.ets —— 页面层串起来
/**
* @file DormCartPage.ets
* @brief 宿舍购物车页:本地读到即渲染,远端变更自动刷新
*/
import { common } from '@kit.AbilityKit';
import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit';
import { DistributedCartService } from '../service/DistributedCartService';
import { CartSyncService, CartItem } from '../service/CartSyncService';
import { DeviceWatcher, PeerDevice } from '../service/DeviceWatcher';
@Entry
@Component
struct DormCartPage {
@State items: Array<CartItem> = [];
@State peers: Array<PeerDevice> = [];
@State tip: string = '正在同步宿舍购物车…';
async aboutToAppear(): Promise<void> {
await this.requestSyncPermission();
const ctx = this.getUIContext().getHostContext() as common.UIAbilityContext;
DistributedCartService.initManager(ctx, ctx.abilityInfo.bundleName);
await DistributedCartService.openStore();
// 设备列表 + 上下线刷新
this.peers = DeviceWatcher.init(ctx.abilityInfo.bundleName, () => {
this.peers = DeviceWatcher.listPeers();
});
// 订阅远端变更:室友加购了,本页自动刷新
CartSyncService.onDataChange(
distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL, () => this.reload());
// 同步完成回调(事件名:'syncComplete')
CartSyncService.onSyncComplete(() => {
this.tip = '已与宿舍设备同步';
this.reload();
});
await this.reload();
}
aboutToDisappear(): void {
DeviceWatcher.release(); // 必须,否则残留监听
DistributedCartService.close();
}
build() {
Column({ space: 12 }) {
Text(this.tip).fontSize(13).fontColor('#666666')
// 一键同步到全部可信设备(注意传 networkId)
Button(`同步到 ${this.peers.length} 台宿舍设备`)
.width('100%').height(44).backgroundColor('#FF2D55')
.onClick(() => {
CartSyncService.pushToDevices(DeviceWatcher.syncableNetworkIds());
this.tip = '正在同步…';
})
List({ space: 10 }) {
ForEach(this.items, (item: CartItem) => {
ListItem() {
Row() {
Text(item.title).fontSize(14).layoutWeight(1)
Text(`×${item.count}`).fontSize(13).fontColor('#888888')
}.width('100%')
}
})
}.layoutWeight(1).width('100%')
}
.width('100%').height('100%').padding(16).backgroundColor(Color.White)
}
private async reload(): Promise<void> {
this.items = await CartSyncService.listLocalItems();
this.tip = this.items.length === 0 ? '购物车是空的,去挑点宿舍零食吧' : `已同步 ${this.items.length} 件商品`;
}
private async requestSyncPermission(): Promise<void> {
const permissions: Array<Permissions> = ['ohos.permission.DISTRIBUTED_DATASYNC'];
const ctx = this.getUIContext().getHostContext() as common.UIAbilityContext;
await abilityAccessCtrl.createAtManager().requestPermissionsFromUser(ctx, permissions);
}
}
3.6 多设备去重:把「是否已提醒」归并成单一 Key
购物节秒杀提醒最容易在多设备上重复轰炸。做法很简单——别用"每台设备各自记录",而是把状态集中写进同一个 Key:
/**
* @file KillReminderService.ets(节选)
* @brief 秒杀提醒的多设备去重:单 Key 状态归并
*/
export class KillReminderService {
private static readonly KEY_KILL_FLAG: string = 'kill_reminded_2026festival_1400';
/** 任一设备提醒过,其他设备都不再提醒 */
static async tryMarkReminded(): Promise<boolean> {
const store = DistributedCartService.getStore();
if (store === null) {
return false;
}
const flag: distributedKVStore.ValueType =
(await store.get(KillReminderService.KEY_KILL_FLAG)) as distributedKVStore.ValueType;
if (flag !== undefined && flag !== null && Number(flag) === 1) {
return false; // 已经提醒过
}
await store.put(KillReminderService.KEY_KILL_FLAG, 1);
// 写完后主动推给同组设备,避免它们各自弹一次
CartSyncService.pushToDevices(DeviceWatcher.syncableNetworkIds(),
distributedKVStore.SyncMode.PUSH_ONLY);
return true;
}
}
四、易错点对照表
| 序号 | 网传写法 | 官方真值 | 后果 |
|---|---|---|---|
| 1 | on('syncCompleted', ...) | on('syncComplete', ...) | 同步完成回调永不触发,且不报错 |
| 2 | sync(deviceIds, mode) 里传 deviceId | 必须传 networkId | 同步静默失败 / 报未找到 |
| 3 | await kvStore.sync(...) | sync() 返回 void | await 一个 undefined,逻辑错位 |
| 4 | createKVManager(config, callback) | 只有 createKVManager(config): KVManager | 编译不通过 |
| 5 | KVOptions | 类型名是 Options | 编译不通过 |
| 6 | 把 kvStoreType / securityLevel 放进 KVManagerConfig | 它们在 Options 里 | 编译不通过 |
| 7 | on('dataChange', (err, data) => ...) | Callback<ChangeNotification>(单参数) | 参数解构错误 |
| 8 | on('distributedDataChange', ...) | 用 on('dataChange', SubscribeType.SUBSCRIBE_TYPE_REMOTE/ALL, ...) | 监听注册不上 |
| 9 | kvStore.getDeviceId() | 用 DeviceBasicInfo.deviceId | 方法不存在 |
| 10 | KVStoreType.MULTI_VERSION | 只有 DEVICE_COLLABORATION = 0 / SINGLE_VERSION = 1 | 编译不通过 |
| 11 | getTrustedDeviceListSync() / DeviceInfo | getAvailableDeviceListSync() / DeviceBasicInfo | 老 API 已废弃 |
| 12 | DeviceStateChangeAction.ONLINE | DeviceStateChange.AVAILABLE = 1 | 枚举值语义不同 |
| 13 | KV 库报错用 14800011 / 14800013 | 那是 RDB 错误码;KV 库用 15100003 / 15100004 | 错误处理写错分支 |
| 14 | SingleKVStore.getKeys() | 用 getEntries / getResultSet | 方法不存在 |
| 15 | 把大图 / 长文本直接 put 进去 | Value < 4MB,DeviceKVStore 的 Key ≤ 896 Byte | 报 15100000 或写入失败 |
五、错误码处置表
| 模块 | 错误码 | 官方含义 | 线上处置 |
|---|---|---|---|
| KVStore | 15100000 | 参数错误 | Key/Value 类型或长度越界;DeviceKVStore 注意 Key ≤ 896 Byte |
| KVStore | 15100001 | 超过最大订阅数量 | 单库 on 回调 ≤ 8 个;aboutToDisappear 里必须 off |
| KVStore | 15100002 | 用变化的 options 打开已存在的库 | 固定 Options 组合;改配置走 deleteKVStore 重建 |
| KVStore | 15100003 | 数据库损坏 | 移除本地库文件后重建,并从服务端拉回全量购物车 |
| KVStore | 15100004 | 未找到 | sync / deleteKVStore 目标不存在;检查 storeId 与 networkId |
| KVStore | 15100005 | 数据库或结果集已关闭 | 重复 close;用 store !== null 守卫 |
| KVStore | 15100006 | 更新密钥失败(rekey()) | API 26 能力,失败时重试一次 |
| deviceManager | 11600101 | 执行功能失败 | 检查分布式服务是否可用;on('serviceDie') 里重建实例 |
| deviceManager | 11600103 | 认证业务不可用 | 引导用户完成设备可信认证(组网) |
| deviceManager | 11600107 | 需要登录账号 | 引导登录华为账号后再组网 |
| deviceManager | 201 | 权限校验失败 | DISTRIBUTED_DATASYNC 未动态申请;补 requestPermissionsFromUser |
| deviceManager | 401 | 参数错误 | bundleName 传错最常见 |
六、写在最后
'syncComplete'少一个d。这一条单独拎出来,是因为它造成的故障最"安静"——不报错、不崩溃,只是回调永远不来。项目 memory 里也记过这条。sync()是「发起」而不是「等待」。它是void接口,完成靠回调。任何await kvStore.sync()的写法都是误解。deviceId和networkId是两个东西:前者用于展示与去重,后者才是同步的地址。混用是最常见的静默失败来源。14800000不是这儿的错误码。分布式 KV 库是1510000x,148xxxxx属于关系型数据库。抄错误码处理逻辑之前先确认来源。- 多设备不是"多存一份",而是"多一份一致性问题"。键值库不支持自定义冲突策略,所以设计上要尽量让每个 Key 的写入天然幂等(比如
cart_<goodsId>覆盖式更新、"是否已提醒"用单 Key 归并),而不是靠事后解决冲突。
购物节里真正难的不是把商品卖出去,而是让用户在任何一台设备上,看到的都是同一份进度。
附:本文 API 速查卡(可直接贴进代码仓库 README)
// ① 分布式键值数据库(ArkData,起始 API 9)
import { distributedKVStore } from '@kit.ArkData';
const manager = distributedKVStore.createKVManager({ context, bundleName }); // ⚠️ 仅同步版,无 callback
const store = await manager.getKVStore<distributedKVStore.DeviceKVStore>('campus_haigou_cart', {
createIfMissing: true, // 默认 true
encrypt: false, // 默认 false
backup: true, // 默认 true
autoSync: false, // 默认 false;true 需权限 DISTRIBUTED_DATASYNC
kvStoreType: distributedKVStore.KVStoreType.DEVICE_COLLABORATION, // 0(默认)/ SINGLE_VERSION 1;无 MULTI_VERSION
securityLevel: distributedKVStore.SecurityLevel.S2 // ⚠️ 必填;S1=2 / S2=3 / S3=5 / S4=6
});
await store.put('cart_g1001', json); // Key: DeviceKVStore ≤896B,SingleKVStore ≤1KB
await store.putBatch(entries); // 单次 ≤ MAX_BATCH_SIZE(128)
await store.get('cart_g1001');
await store.getEntries('cart_'); // ⚠️ 无 getKeys()
await store.getEntries(networkId, 'cart_'); // DeviceKVStore:读指定设备(只读)
store.sync(networkIds, distributedKVStore.SyncMode.PUSH_PULL); // ⚠️ 返回 void!deviceIds 必须是 networkId
// SyncMode:PULL_ONLY 0 / PUSH_ONLY 1 / PUSH_PULL 2
store.on('syncComplete', (r: Array<[string, number]>) => {}); // ⚠️ 是 syncComplete,不是 syncCompleted
store.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL,
(n: distributedKVStore.ChangeNotification) => {}); // ⚠️ 单参数,不是 (err, data)
// SubscribeType:LOCAL 0 / REMOTE 1 / ALL 2
manager.on('distributedDataServiceDie', () => {}); // ⚠️ 在 KVManager 上
await manager.closeKVStore('', storeId); await manager.deleteKVStore('', storeId);
// 常量:MAX_KEY_LENGTH 1024 / MAX_KEY_LENGTH_DEVICE 896 / MAX_VALUE_LENGTH 4194303 / MAX_BATCH_SIZE 128
// 错误码:15100000 ~ 15100006(⚠️ 14800000 系列是 RDB 的)
// ② 设备管理(DistributedServiceKit,起始 API 10)
import { distributedDeviceManager } from '@kit.DistributedServiceKit';
const dm = distributedDeviceManager.createDeviceManager(bundleName);
const peers = dm.getAvailableDeviceListSync(); // 需权限 DISTRIBUTED_DATASYNC,未授权报 201
// DeviceBasicInfo { deviceId, deviceName, deviceType, networkId? }
dm.on('deviceStateChange', (data: { action: distributedDeviceManager.DeviceStateChange,
device: distributedDeviceManager.DeviceBasicInfo }) => {});
// DeviceStateChange:UNKNOWN 0 / AVAILABLE 1 / UNAVAILABLE 2
dm.getLocalDeviceNetworkId(); dm.getLocalDeviceId(); dm.getLocalDeviceName(); dm.getLocalDeviceType();
dm.off('deviceStateChange', cb);
distributedDeviceManager.releaseDeviceManager(dm);
// 错误码:201 / 401 / 11600101 ~ 11600108
// 权限:ohos.permission.DISTRIBUTED_DATASYNC(normal 级别 + user_grant,必须动态申请,起始版本 7)
// 前置:同局域网或蓝牙开启 + 设备可信认证;单应用 ≤16 个库、单库 ≤8 个订阅、≤8 个结果集
更多推荐

所有评论(0)