校园嗨购 · 某购物节特别版: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 状态归并去重

这一篇依然有 三个反常识结论:

  1. 官方事件名是 'syncComplete',不是 'syncCompleted'。少一个字母 d,监听直接失效,而且不报错——这是本文最想让人记住的一条。
  2. sync() 的返回值是 void,不是 Promise<void>。它不是"等同步完成"的接口,完成结果走 on('syncComplete') 回调。
  3. 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.distributedKVStoreAPI version 9
distributedDeviceManager@ohos.distributedDeviceManagerAPI 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
SystemCapabilitySystemCapability.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):

错误码官方原文
15100000Parameter error.(必填缺失 / 类型错误 / 校验失败)
15100001Over max limits.(超过最大订阅数量:订阅 8 / 结果集 8)
15100002Open existed database with changed options.
15100003Database corrupted.
15100004Not found.
15100005Database or result set already closed.
15100006Failed to update the key.(出现在 rekey() 的错误码表中)

设备管理:

错误码官方原文
11600101Failed to execute the function.
11600102Failed to obtain the service.
11600103Authentication unavailable.
11600104Discovery unavailable.
11600105Publish unavailable.
11600106Failed to get data from the cloud.
11600107A login account is required.
11600108The device name contains non-compliant content.
201Permission verification failed.
401Parameter error.

⚠️ 关键勘误:官网查不到的高频写法

网传写法官方真值
on('syncCompleted', ...)'syncComplete'(无 d)
import deviceManager from '@ohos.distributedHardware.deviceManager'已废弃 → @ohos.distributedDeviceManager(@kit.DistributedServiceKit)
getTrustedDeviceListSync() / DeviceInfo / DeviceStateChangeAction已废弃 → getAvailableDeviceListSync() / DeviceBasicInfo / DeviceStateChange
distributedKVStore.createKVStore(...)不存在 → createKVManager(config)
createKVManager(config, callback)不存在(属旧 distributedData 模块)→ 只有 createKVManager(config)
KVOptions 类型不存在 → 类型名是 Options
kvStore.sync(...) 返回 Promise<void>返回 void,结果走 on('syncComplete')
on('distributedDataChange', ...)不存在 → on('dataChange', SubscribeType.X, ...)
dataChange 回调写成 (err, data)单参数 Callback<ChangeNotification>
kvStore.getDeviceId()不存在 → 用 DeviceBasicInfo.deviceId
KVStoreType.MULTI_VERSION不存在 → 只有 DEVICE_COLLABORATION = 0 / SINGLE_VERSION = 1
SingleKVStore.getKeys()不存在 → 用 getEntries / getResultSet
sync(deviceIds, mode) 里传 deviceId必须传 networkId
KV 库错误码用 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;
  }
}

四、易错点对照表

序号网传写法官方真值后果
1on('syncCompleted', ...)on('syncComplete', ...)同步完成回调永不触发,且不报错
2sync(deviceIds, mode) 里传 deviceId必须传 networkId同步静默失败 / 报未找到
3await kvStore.sync(...)sync() 返回 voidawait 一个 undefined,逻辑错位
4createKVManager(config, callback)只有 createKVManager(config): KVManager编译不通过
5KVOptions类型名是 Options编译不通过
6把 kvStoreType / securityLevel 放进 KVManagerConfig它们在 Options 里编译不通过
7on('dataChange', (err, data) => ...)Callback<ChangeNotification>(单参数)参数解构错误
8on('distributedDataChange', ...)用 on('dataChange', SubscribeType.SUBSCRIBE_TYPE_REMOTE/ALL, ...)监听注册不上
9kvStore.getDeviceId()用 DeviceBasicInfo.deviceId方法不存在
10KVStoreType.MULTI_VERSION只有 DEVICE_COLLABORATION = 0 / SINGLE_VERSION = 1编译不通过
11getTrustedDeviceListSync() / DeviceInfogetAvailableDeviceListSync() / DeviceBasicInfo老 API 已废弃
12DeviceStateChangeAction.ONLINEDeviceStateChange.AVAILABLE = 1枚举值语义不同
13KV 库报错用 14800011 / 14800013那是 RDB 错误码;KV 库用 15100003 / 15100004错误处理写错分支
14SingleKVStore.getKeys()用 getEntries / getResultSet方法不存在
15把大图 / 长文本直接 put 进去Value < 4MB,DeviceKVStore 的 Key ≤ 896 Byte报 15100000 或写入失败

五、错误码处置表

模块错误码官方含义线上处置
KVStore15100000参数错误Key/Value 类型或长度越界;DeviceKVStore 注意 Key ≤ 896 Byte
KVStore15100001超过最大订阅数量单库 on 回调 ≤ 8 个;aboutToDisappear 里必须 off
KVStore15100002用变化的 options 打开已存在的库固定 Options 组合;改配置走 deleteKVStore 重建
KVStore15100003数据库损坏移除本地库文件后重建,并从服务端拉回全量购物车
KVStore15100004未找到sync / deleteKVStore 目标不存在;检查 storeId 与 networkId
KVStore15100005数据库或结果集已关闭重复 close;用 store !== null 守卫
KVStore15100006更新密钥失败(rekey())API 26 能力,失败时重试一次
deviceManager11600101执行功能失败检查分布式服务是否可用;on('serviceDie') 里重建实例
deviceManager11600103认证业务不可用引导用户完成设备可信认证(组网)
deviceManager11600107需要登录账号引导登录华为账号后再组网
deviceManager201权限校验失败DISTRIBUTED_DATASYNC 未动态申请;补 requestPermissionsFromUser
deviceManager401参数错误bundleName 传错最常见

六、写在最后

  1. 'syncComplete' 少一个 d。这一条单独拎出来,是因为它造成的故障最"安静"——不报错、不崩溃,只是回调永远不来。项目 memory 里也记过这条。
  2. sync() 是「发起」而不是「等待」。它是 void 接口,完成靠回调。任何 await kvStore.sync() 的写法都是误解。
  3. deviceId 和 networkId 是两个东西:前者用于展示与去重,后者才是同步的地址。混用是最常见的静默失败来源。
  4. 14800000 不是这儿的错误码。分布式 KV 库是 1510000x,148xxxxx 属于关系型数据库。抄错误码处理逻辑之前先确认来源。
  5. 多设备不是"多存一份",而是"多一份一致性问题"。键值库不支持自定义冲突策略,所以设计上要尽量让每个 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 个结果集
Logo

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

更多推荐