目标与准备
目标: 构建一个分布式购物车应用,实现商品列表在多台设备间的实时、双向同步。
核心流程:
- 设备组网: 用户在应用内发现并信任其他设备,建立分布式网络。
- 数据操作: 用户在任一设备上添加或删除购物车商品。
- 自动同步: 数据变更通过分布式 KV Store 自动同步到所有已入网设备。
- UI 刷新: 所有设备上的 UI 自动响应数据变化,实时更新显示。
开发环境与准备: - 硬件: 至少两台搭载 OpenHarmony 标准系统的设备(如手机、平板)。
- 软件:
- OpenHarmony SDK API 12+ (Full SDK)。
- 重要: 分布式相关 API 属于系统级接口,必须 在 DevEco Studio 中配置并使用 Full SDK,否则编译会因找不到符号而失败。
第一部分:项目准备与权限配置
步骤 1:创建新项目
在 DevEco Studio 中,选择 "Create Project",创建一个 "Empty Ability" 模板的工程,Language 选择 ArkTS,Compatibility SDK 选择 12以上。
步骤 2:配置 module.json5 权限
分布式功能需要申请敏感权限。打开 entry/src/main/module.json5 文件,在 requestPermissions 数组中添加以下权限:
json
{
"module": {
// ...其他配置
"requestPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC",
"reason": "$string:permission_reason_distributed_datasync",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
},
{
"name": "ohos.permission.ACCESS_SERVICE_DM",
"reason": "$string:permission_reason_access_service_dm",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
}
]
}
}
DISTRIBUTED_DATASYNC: 用于分布式数据同步。ACCESS_SERVICE_DM: 用于设备发现和管理。
同时,在resources/base/element/string.json中添加权限说明文本:
json
{
"string": [
// ...其他字符串
{
"name": "permission_reason_distributed_datasync",
"value": "用于在不同设备间同步您的购物车数据,提供无缝体验"
},
{
"name": "permission_reason_access_service_dm",
"value": "用于发现和连接附近的信任设备,以实现分布式功能"
}
]
}
步骤 3:动态请求权限
在应用启动时,需要动态请求这些权限。在 EntryAbility.ets 的 onCreate 方法中添加权限请求逻辑。
typescript
// EntryAbility.ets
import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import { abilityAccessCtrl, bundleManager, Permissions } from '@ohos.abilityAccessCtrl';
import { BusinessError } from '@ohos.base';
export default class EntryAbility extends UIAbility {
async onRequestPermissions() {
const atManager = abilityAccessCtrl.createAtManager();
const bundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
const tokenId = bundleInfo.appInfo.accessTokenId;
const permissions: Array<Permissions> = [
'ohos.permission.DISTRIBUTED_DATASYNC',
'ohos.permission.ACCESS_SERVICE_DM'
];
const result = await atManager.requestPermissionsFromUser(this.context, permissions);
hilog.info(0x0000, 'PermissionTag', `Permission request result: ${JSON.stringify(result)}`);
}
onCreate(want, launchParam) {
hilog.info(0x0000, 'AbilityTag', '%{public}s', 'Ability onCreate');
this.onRequestPermissions();
}
// ...其他生命周期方法
}
第二部分:核心服务封装
为了实现关注点分离,我们将所有分布式业务的逻辑封装到一个独立的服务类 DistributedCartService 中。这使得 UI 代码更纯粹,业务逻辑更易于复用和测试。
步骤 1:定义数据模型
创建 model/Product.ets 文件,定义商品数据结构。使用 @Observed 装饰器,使其成为可被深度观察的对象。
typescript
// model/Product.ets
import { Observed } from '@arkts.observer';
@Observed
export class Product {
id: string;
name: string;
price: number;
constructor(id: string, name: string, price: number) {
this.id = id;
this.name = name;
this.price = price;
}
}
步骤 2:创建分布式服务
创建 service/DistributedCartService.ets。这个单例服务将负责设备管理和 KV Store 的所有操作。
typescript
// service/DistributedCartService.ets
import { BusinessError } from '@ohos.base';
import distributedDevice from '@ohos.distributedDevice';
import distributedData from '@ohos.data.distributedData';
import { Product } from '../model/Product';
import hilog from '@ohos.hilog';
import promptAction from '@ohos.promptAction';
const TAG = 'CartService';
const BUNDLE_NAME = 'com.example.distributedcart'; // 务必与你的App BundleName一致
const STORE_ID = 'shopping_cart_store';
export class DistributedCartService {
private static instance: DistributedCartService = new DistributedCartService();
private kvManager: distributedData.KVManager | null = null;
private kvStore: distributedData.SingleKVStore | null = null;
private deviceList: distributedDevice.DeviceInfo[] = [];
public cartItems: Map<string, Product> = new Map();
private constructor() {}
public static getInstance(): DistributedCartService {
return this.instance;
}
// --- 设备管理 ---
async initDeviceManager(): Promise<void> {
try {
const deviceManager = distributedDevice.createDeviceManager(BUNDLE_NAME);
deviceManager.on('deviceStateChange', (data) => {
hilog.info(TAG, `Device state changed: ${JSON.stringify(data)}`);
});
deviceManager.on('deviceFound', (data) => {
if (!this.deviceList.some(d => d.networkId === data.networkId)) {
this.deviceList.push(data);
hilog.info(TAG, `New device found: ${data.deviceName}`);
}
});
deviceManager.on('discoverFail', (data) => {
hilog.error(TAG, `Device discovery failed: ${JSON.stringify(data)}`);
});
hilog.info(TAG, 'Device Manager initialized.');
} catch (err) {
hilog.error(TAG, `Failed to init device manager. Code: ${(err as BusinessError).code}, Message: ${(err as BusinessError).message}`);
}
}
async startDeviceDiscovery(): Promise<void> {
try {
const deviceManager = distributedDevice.createDeviceManager(BUNDLE_NAME);
const extraInfo = {
'targetPkgName': BUNDLE_NAME,
'appName': 'DistributedCart'
};
deviceManager.startDeviceDiscovery(extraInfo);
hilog.info(TAG, 'Start discovering devices...');
} catch (err) {
hilog.error(TAG, `Failed to start discovery. Code: ${(err as BusinessError).code}`);
promptAction.showToast({ message: '启动设备发现失败' });
}
}
stopDeviceDiscovery(): void {
try {
const deviceManager = distributedDevice.createDeviceManager(BUNDLE_NAME);
deviceManager.stopDeviceDiscovery();
this.deviceList = [];
hilog.info(TAG, 'Stop device discovery.');
} catch (err) {
hilog.error(TAG, `Failed to stop discovery. Code: ${(err as BusinessError).code}`);
}
}
getDiscoveredDevices(): distributedDevice.DeviceInfo[] {
return this.deviceList;
}
// --- KV Store 管理 ---
async initKVStore(context: Context): Promise<void> {
if (this.kvStore) {
hilog.info(TAG, 'KV Store already initialized.');
return;
}
try {
const kvManagerConfig: distributedData.KVManagerConfig = {
context: context,
bundleName: BUNDLE_NAME
};
this.kvManager = distributedData.createKVManager(kvManagerConfig);
const options: distributedData.KVStoreOptions = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: true,
kvStoreType: distributedData.KVStoreType.SINGLE_VERSION,
schema: undefined,
securityLevel: distributedData.SecurityLevel.S1
};
this.kvStore = await this.kvManager.getKVStore(STORE_ID, options) as distributedData.SingleKVStore;
hilog.info(TAG, 'Succeeded in getting KV store.');
// 订阅数据变化
this.kvStore.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_ALL, (data) => {
hilog.info(TAG, `Data change received: ${JSON.stringify(data)}`);
this.loadCartData(); // 重新加载数据以更新UI
});
await this.loadCartData(); // 初始化时加载数据
} catch (err) {
hilog.error(TAG, `Failed to init KV store. Code: ${(err as BusinessError).code}, Message: ${(err as BusinessError).message}`);
promptAction.showToast({ message: '分布式数据初始化失败' });
}
}
private async loadCartData(): Promise<void> {
if (!this.kvStore) return;
try {
const entries = await this.kvStore.getEntries('product_') as distributedData.Entry[];
this.cartItems.clear();
entries.forEach(entry => {
const product = JSON.parse(entry.value.toString()) as Product;
this.cartItems.set(entry.key, product);
});
hilog.info(TAG, `Cart data loaded. Item count: ${this.cartItems.size}`);
} catch (err) {
hilog.error(TAG, `Failed to load cart data. Code: ${(err as BusinessError).code}`);
}
}
// --- 购物车操作 ---
async addProduct(product: Product): Promise<void> {
if (!this.kvStore) {
promptAction.showToast({ message: '数据服务未就绪' });
return;
}
try {
await this.kvStore.put(`product_${product.id}`, JSON.stringify(product));
hilog.info(TAG, `Product added: ${product.name}`);
promptAction.showToast({ message: `已添加: ${product.name}` });
} catch (err) {
hilog.error(TAG, `Failed to add product. Code: ${(err as BusinessError).code}`);
promptAction.showToast({ message: '添加商品失败' });
}
}
async removeProduct(productId: string): Promise<void> {
if (!this.kvStore) {
promptAction.showToast({ message: '数据服务未就绪' });
return;
}
try {
await this.kvStore.delete(`product_${productId}`);
hilog.info(TAG, `Product removed: ${productId}`);
promptAction.showToast({ message: '商品已移除' });
} catch (err) {
hilog.error(TAG, `Failed to remove product. Code: ${(err as BusinessError).code}`);
promptAction.showToast({ message: '移除商品失败' });
}
}
}
第三部分:UI 界面开发
现在,UI 层变得非常简洁。它只需要调用 DistributedCartService 的方法,并响应其数据变化即可。
步骤 1:构建主界面
修改 pages/Index.ets,实现购物车列表和设备发现列表。
typescript
// pages/Index.ets
import { DistributedCartService } from '../service/DistributedCartService';
import { Product } from '../model/Product';
import { distributedDevice } from '@ohos.distributedDevice';
import promptAction from '@ohos.promptAction';
@Entry
@Component
struct Index {
@State cartItems: Map<string, Product> = new Map();
@State discoveredDevices: distributedDevice.DeviceInfo[] = [];
@State isDiscovering: boolean = false;
private cartService: DistributedCartService = DistributedCartService.getInstance();
aboutToAppear(): void {
// 初始化服务和加载数据
this.cartService.initDeviceManager();
this.cartService.initKVStore(getContext(this));
this.refreshCart();
this.refreshDeviceList();
}
aboutToDisappear(): void {
// 页面销毁时停止发现,节省资源
this.cartService.stopDeviceDiscovery();
}
refreshCart() {
this.cartItems = new Map(this.cartService.cartItems);
}
refreshDeviceList() {
this.discoveredDevices = this.cartService.getDiscoveredDevices();
}
build() {
Column() {
Text('分布式购物车').fontSize(24).fontWeight(FontWeight.Bold).margin({ top: 20, bottom: 10 })
// 设备发现区域
Column() {
Row() {
Text('设备列表').fontSize(18).fontWeight(FontWeight.Medium)
Blank()
Button(this.isDiscovering ? '停止发现' : '发现设备')
.onClick(() => {
if (this.isDiscovering) {
this.cartService.stopDeviceDiscovery();
this.isDiscovering = false;
this.refreshDeviceList();
} else {
this.cartService.startDeviceDiscovery();
this.isDiscovering = true;
// 模拟定时刷新设备列表
setInterval(() => {
this.refreshDeviceList();
}, 2000);
}
})
}.width('100%').padding(10)
List({ space: 8 }) {
ForEach(this.discoveredDevices, (device: distributedDevice.DeviceInfo) => {
ListItem() {
Row() {
Text(device.deviceName).fontSize(16)
Blank()
Button('信任')
.onClick(() => {
// 此处省略认证逻辑,实际应用需要调用认证API
promptAction.showToast({ message: `已请求信任 ${device.deviceName}` });
})
}.width('100%').padding(10)
}
})
}.width('100%').layoutWeight(1)
}.backgroundColor('#f1f3f5').borderRadius(12).margin({ horizontal: 16, vertical: 8 }).padding(12)
// 购物车操作区域
Row() {
Button('添加商品 (手机)')
.onClick(() => {
const newProduct = new Product(Date.now().toString(), '华为手机', 6999);
this.cartService.addProduct(newProduct);
// 添加后手动刷新一次,因为数据同步是异步的
setTimeout(() => this.refreshCart(), 500);
})
Button('添加商品 (平板)')
.onClick(() => {
const newProduct = new Product(Date.now().toString(), '华为平板', 4999);
this.cartService.addProduct(newProduct);
setTimeout(() => this.refreshCart(), 500);
})
}.justifyContent(FlexAlign.SpaceEvenly).width('100%').margin({ vertical: 10 })
// 购物车列表
Text(`购物车 (${this.cartItems.size})`).fontSize(18).fontWeight(FontWeight.Medium).margin({ top: 10 })
List({ space: 8 }) {
ForEach(Array.from(this.cartItems.entries()), (entry: [string, Product]) => {
ListItem() {
Row() {
Column() {
Text(entry[1].name).fontSize(16).fontWeight(FontWeight.Medium)
Text(`¥${entry[1].price}`).fontSize(14).fontColor(Color.Red)
}.alignItems(HorizontalAlign.Start)
Blank()
Button('删除')
.onClick(() => {
this.cartService.removeProduct(entry[1].id);
setTimeout(() => this.refreshCart(), 500);
})
}.width('100%').padding(12)
}
}, (entry: [string, Product]) => entry[0]) // 使用唯一的ID作为key
}.width('100%').layoutWeight(2).padding({ horizontal: 16 })
}.width('100%').height('100%')
}
}
第四部分:功能测试与验证
- 编译与安装: 使用 Full SDK 编译工程,并将生成的 HAP 包安装到两台 OpenHarmony 设备上。
- 授权: 在两台设备上首次启动应用时,系统会弹出权限请求框,请务必点击"允许"。
- 设备发现:
- 在设备 A 上,点击"发现设备"按钮。
- 等待几秒,设备 B 的名称应该会出现在设备 A 的列表中。
- 数据同步测试:
- 添加: 在设备 A 上点击"添加商品 (手机)"。观察设备 A 的购物车列表出现新商品,并弹出"已添加"提示。3-5秒内,设备 B 的购物车列表也会自动刷新,显示相同的商品。
- 删除: 在设备 B 上点击该商品旁的"删除"按钮。观察设备 B 的商品消失。3-5秒内,设备 A 的商品也会同步消失。
- 双向验证: 重复在设备 B 上添加、在设备 A 上删除的操作,确保数据同步是双向、可靠的。
第五部分:待完善和优化部分
- 冲突解决:
autoSync模式下,如果设备离线操作,重新上线时系统会尝试合并。对于复杂场景,可关闭autoSync,手动调用sync接口,并监听syncComplete回调处理冲突。 - 性能优化: 当购物车商品数量巨大时,频繁的
put和 UI 刷新可能影响性能。可考虑:- 批量更新: 将多个操作合并后一次性写入 KV Store。
- LazyForEach: 对于超长列表,使用
LazyForEach替代ForEach实现按需加载,提升滚动性能。
- 数据安全: KV Store 支持在
KVStoreOptions中设置encrypt: true,对数据进行加密存储,保护用户隐私。 - 用户体验增强:
- 添加同步状态的视觉提示(如一个小图标表示正在同步)。
- 实现离线模式,当设备无网络时,数据暂存本地,待网络恢复后自动同步。
- 完善设备认证的 UI 流程,提供 PIN 码验证等更安全的组网方式。