HarmonyOS开发之多端协同案例——分布式购物车
定义商品数据模型,使用@Observed// 文件:src/main/ets/model/Product.ts@Observedid: string;// 添加设备的ID,用于冲突解决// 商品总价计算分布式架构是基础:通过KV Store实现多设备数据实时同步,采用发布-订阅模式确保数据一致性。权限配置是关键:必须正确声明和权限,并在运行时动态申请。设备适配是体验保障:根据设备类型采用不同的UI
HarmonyOS开发之多端协同案例——分布式购物车
第一部分:引入
在日常购物场景中,我们经常遇到这样的困扰:手机上浏览商品添加到购物车,走到电脑前想要结算时,却发现购物车空空如也;或者与家人一起购物时,想要合并结算却需要反复分享商品链接。这种设备孤岛和协作壁垒严重影响了购物体验的连贯性。
HarmonyOS的分布式购物车技术正是为解决这一痛点而生。它通过分布式数据管理能力,将多个设备的购物车状态实时同步,让手机、平板、手表等设备成为购物流程中的不同触点。无论你切换设备还是与他人协作,购物车数据始终无缝流转,真正实现"一处添加,处处可见"的超级终端体验。
第二部分:讲解
一、分布式购物车的架构设计
1.1 核心架构原理
分布式购物车基于HarmonyOS的分布式数据管理框架,采用"发布-订阅"模式实现数据同步。其架构分为三层:
数据层:使用分布式键值数据库(KV Store)存储购物车商品信息。每个商品被封装为可观察对象,当数据变更时自动触发同步机制。
同步层:负责设备发现、连接管理和数据传输。通过分布式软总线自动建立设备间安全通道,采用增量同步策略减少网络开销。
UI层:各设备根据自身屏幕特性和交互方式,展示统一的购物车数据。平板采用双列网格布局,手机使用单列列表,手表则显示精简信息。
1.2 数据同步流程
// 文件:src/main/ets/service/DistributedCartService.ts
import distributedData from '@ohos.data.distributedData';
import distributedDevice from '@ohos.distributedDevice';
@Component
export class DistributedCartService {
private kvManager: distributedData.KVManager | null = null;
private kvStore: distributedData.SingleKVStore | null = null;
private deviceList: distributedDevice.DeviceInfo[] = [];
// 初始化KV Store
async initKVStore(context: Context): Promise<void> {
try {
// 创建KV管理器配置
const kvManagerConfig: distributedData.KVManagerConfig = {
context: context,
bundleName: 'com.example.distributedcart'
};
this.kvManager = distributedData.createKVManager(kvManagerConfig);
// 配置KV Store选项
const options: distributedData.KVStoreOptions = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: true, // 开启自动同步
kvStoreType: distributedData.KVStoreType.SINGLE_VERSION,
securityLevel: distributedData.SecurityLevel.S1
};
this.kvStore = await this.kvManager.getKVStore('shopping_cart_store', options) as distributedData.SingleKVStore;
// 订阅数据变更事件
this.kvStore.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_ALL, (data) => {
this.handleDataChange(data); // 处理数据变更
});
} catch (error) {
console.error('KV Store初始化失败:', error);
}
}
}
二、完整代码实现
2.1 权限配置
首先在配置文件中声明必要的分布式权限:
// 文件:src/main/module.json5
{
"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"
}
}
]
}
}
在字符串资源中定义权限说明:
// 文件:src/main/resources/base/element/string.json
{
"string": [
{
"name": "permission_reason_distributed_datasync",
"value": "用于在不同设备间同步您的购物车数据,提供无缝体验"
},
{
"name": "permission_reason_access_service_dm",
"value": "用于发现和连接附近的信任设备,以实现分布式功能"
}
]
}
2.2 数据模型定义
定义商品数据模型,使用@Observed装饰器实现深度观察:
// 文件:src/main/ets/model/Product.ts
import { Observed } from '@arkts.observer';
@Observed
export class Product {
id: string;
name: string;
price: number;
count: number;
imageUrl: string;
deviceId: string; // 添加设备的ID,用于冲突解决
constructor(id: string, name: string, price: number, imageUrl: string = '') {
this.id = id;
this.name = name;
this.price = price;
this.count = 1;
this.imageUrl = imageUrl;
this.deviceId = '';
}
// 商品总价计算
get totalPrice(): number {
return this.price * this.count;
}
}
2.3 购物车服务实现
实现核心的分布式购物车服务:
// 文件:src/main/ets/service/DistributedCartService.ts
import { BusinessError } from '@ohos.base';
import distributedData from '@ohos.data.distributedData';
import distributedDevice from '@ohos.distributedDevice';
import { Product } from '../model/Product';
export class DistributedCartService {
private static instance: DistributedCartService = new DistributedCartService();
private kvStore: distributedData.SingleKVStore | null = null;
public cartItems: Map<string, Product> = new Map();
private changeCallbacks: Array<(items: Map<string, Product>) => void> = [];
public static getInstance(): DistributedCartService {
return this.instance;
}
// 添加商品到购物车
async addProduct(product: Product): Promise<void> {
if (!this.kvStore) {
throw new Error('KV Store未初始化');
}
try {
// 设置设备ID,用于冲突解决
product.deviceId = await this.getLocalDeviceId();
// 将商品数据序列化并存储
const productKey = `product_${product.id}`;
const productData = JSON.stringify(product);
await this.kvStore.put(productKey, productData);
console.info('商品添加成功:', product.name);
} catch (error) {
console.error('添加商品失败:', error);
throw error;
}
}
// 从购物车移除商品
async removeProduct(productId: string): Promise<void> {
if (!this.kvStore) {
throw new Error('KV Store未初始化');
}
try {
const productKey = `product_${productId}`;
await this.kvStore.delete(productKey);
console.info('商品移除成功:', productId);
} catch (error) {
console.error('移除商品失败:', error);
throw error;
}
}
// 更新商品数量
async updateProductCount(productId: string, newCount: number): Promise<void> {
if (!this.kvStore) {
throw new Error('KV Store未初始化');
}
try {
const productKey = `product_${productId}`;
const existingProduct = this.cartItems.get(productId);
if (existingProduct) {
existingProduct.count = newCount;
const productData = JSON.stringify(existingProduct);
await this.kvStore.put(productKey, productData);
}
} catch (error) {
console.error('更新商品数量失败:', error);
throw error;
}
}
// 加载购物车数据
private async loadCartData(): Promise<void> {
if (!this.kvStore) return;
try {
const entries = await this.kvStore.getEntries('product_');
this.cartItems.clear();
entries.forEach(entry => {
try {
const product = JSON.parse(entry.value.toString()) as Product;
this.cartItems.set(product.id, product);
} catch (parseError) {
console.error('解析商品数据失败:', parseError);
}
});
// 通知所有订阅者数据已更新
this.notifyChange();
} catch (error) {
console.error('加载购物车数据失败:', error);
}
}
// 处理数据变更
private handleDataChange(data: distributedData.ChangeData): Promise<void> {
console.info('接收到数据变更:', JSON.stringify(data));
return this.loadCartData();
}
// 获取本地设备ID
private async getLocalDeviceId(): Promise<string> {
// 实际实现中应调用分布式设备管理API
return 'local_device_id';
}
// 注册数据变更回调
registerChangeCallback(callback: (items: Map<string, Product>) => void): void {
this.changeCallbacks.push(callback);
}
// 通知数据变更
private notifyChange(): void {
this.changeCallbacks.forEach(callback => {
callback(new Map(this.cartItems));
});
}
}
2.4 购物车UI实现
实现跨设备适配的购物车界面:
// 文件:src/main/ets/pages/ShoppingCartPage.ts
import { DistributedCartService } from '../service/DistributedCartService';
import { Product } from '../model/Product';
@Entry
@Component
struct ShoppingCartPage {
@State cartItems: Map<string, Product> = new Map();
@State totalPrice: number = 0;
@State isConnected: boolean = false;
private cartService: DistributedCartService = DistributedCartService.getInstance();
aboutToAppear(): void {
// 注册数据变更回调
this.cartService.registerChangeCallback((items: Map<string, Product>) => {
this.cartItems = new Map(items);
this.calculateTotalPrice();
});
// 初始化购物车服务
this.initCartService();
}
// 初始化购物车服务
async initCartService(): Promise<void> {
try {
// 在实际实现中应传递正确的Context
await this.cartService.initKVStore(getContext(this));
this.isConnected = true;
} catch (error) {
console.error('购物车服务初始化失败:', error);
}
}
// 计算总价
calculateTotalPrice(): void {
this.totalPrice = Array.from(this.cartItems.values()).reduce(
(total, product) => total + product.totalPrice, 0
);
}
// 增加商品数量
increaseQuantity(productId: string): void {
const product = this.cartItems.get(productId);
if (product) {
this.cartService.updateProductCount(productId, product.count + 1);
}
}
// 减少商品数量
decreaseQuantity(productId: string): void {
const product = this.cartItems.get(productId);
if (product && product.count > 1) {
this.cartService.updateProductCount(productId, product.count - 1);
}
}
// 结算操作
checkout(): void {
if (this.cartItems.size === 0) {
alert('购物车为空,请先添加商品');
return;
}
// 在实际实现中应跳转到结算页面
console.info('开始结算,总金额:', this.totalPrice);
}
build() {
Column({ space: 20 }) {
// 标题栏
Text('分布式购物车')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Center)
.margin({ top: 20, bottom: 10 })
// 连接状态指示器
Row() {
Text(this.isConnected ? '设备已连接' : '设备未连接')
.fontSize(14)
.fontColor(this.isConnected ? '#00FF00' : '#FF0000')
Image(this.isConnected ? 'connected.png' : 'disconnected.png')
.width(20)
.height(20)
.margin({ left: 10 })
}
.width('100%')
.justifyContent(FlexAlign.Start)
.padding({ left: 20, right: 20 })
// 商品列表
List({ space: 15 }) {
ForEach(Array.from(this.cartItems.entries()), ([id, product]) => {
ListItem() {
this.buildProductItem(product);
}
})
}
.layoutWeight(1)
.width('100%')
.padding(10)
// 底部结算栏
this.buildCheckoutBar()
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
// 构建商品项组件
@Builder
buildProductItem(product: Product) {
Row({ space: 15 }) {
// 商品图片
Image(product.imageUrl || 'default_product.png')
.width(80)
.height(80)
.objectFit(ImageFit.Cover)
.borderRadius(8)
// 商品信息
Column({ space: 5 }) {
Text(product.name)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.width('100%')
.textAlign(TextAlign.Start)
Text(`¥${product.price.toFixed(2)}`)
.fontSize(14)
.fontColor('#FF6B00')
.width('100%')
.textAlign(TextAlign.Start)
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
// 数量控制器
Row({ space: 10 }) {
Button('-')
.width(30)
.height(30)
.fontSize(14)
.onClick(() => this.decreaseQuantity(product.id))
Text(product.count.toString())
.fontSize(16)
.width(40)
.textAlign(TextAlign.Center)
Button('+')
.width(30)
.height(30)
.fontSize(14)
.onClick(() => this.increaseQuantity(product.id))
}
// 移除按钮
Button('删除')
.width(60)
.height(30)
.fontSize(12)
.backgroundColor('#FF4757')
.fontColor('#FFFFFF')
.onClick(() => this.cartService.removeProduct(product.id))
}
.width('100%')
.padding(15)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({ radius: 2, color: '#000000', offsetX: 0, offsetY: 1 })
}
// 构建结算栏组件
@Builder
buildCheckoutBar() {
Column({ space: 10 }) {
// 总价信息
Row() {
Text('合计:')
.fontSize(16)
Text(`¥${this.totalPrice.toFixed(2)}`)
.fontSize(20)
.fontColor('#FF6B00')
.fontWeight(FontWeight.Bold)
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding({ left: 20, right: 20 })
// 结算按钮
Button('去结算')
.width('90%')
.height(45)
.fontSize(18)
.backgroundColor('#07C160')
.fontColor('#FFFFFF')
.onClick(() => this.checkout())
}
.width('100%')
.padding(15)
.backgroundColor('#FFFFFF')
}
}
三、关键API参数说明
| API接口 | 参数说明 | 返回值 | 使用场景 |
|---|---|---|---|
distributedData.createKVManager() |
config: KV管理器配置 |
KVManager实例 |
创建分布式数据管理器 |
KVManager.getKVStore() |
storeId: 存储ID options: 存储选项 |
KVStore实例 |
获取分布式数据库实例 |
KVStore.put() |
key: 键名 value: 键值 |
Promise<void> |
存储或更新数据 |
KVStore.delete() |
key: 要删除的键名 |
Promise<void> |
删除指定数据 |
KVStore.on() |
type: 订阅类型 callback: 回调函数 |
void |
订阅数据变更事件 |
KVStore.getEntries() |
prefix: 键前缀 |
Promise<Entry[]> |
获取匹配前缀的所有数据项 |
四、多设备UI适配策略
4.1 设备类型检测与适配
// 文件:src/main/ets/utils/DeviceAdapter.ts
import display from '@ohos.display';
export class DeviceAdapter {
// 检测设备类型
static getDeviceType(): DeviceType {
const displayInfo = display.getDefaultDisplaySync();
const width = displayInfo.width;
const height = displayInfo.height;
const minSize = Math.min(width, height);
if (minSize < 600) {
return DeviceType.WEARABLE; // 手表
} else if (minSize >= 600 && minSize < 1200) {
return DeviceType.PHONE; // 手机
} else {
return DeviceType.TABLET; // 平板
}
}
// 获取布局配置
static getLayoutConfig(): LayoutConfig {
const deviceType = this.getDeviceType();
switch (deviceType) {
case DeviceType.WEARABLE:
return {
columns: 1,
itemHeight: 80,
fontSize: 14,
imageSize: 60
};
case DeviceType.PHONE:
return {
columns: 1,
itemHeight: 100,
fontSize: 16,
imageSize: 80
};
case DeviceType.TABLET:
return {
columns: 2,
itemHeight: 120,
fontSize: 18,
imageSize: 100
};
default:
return {
columns: 1,
itemHeight: 100,
fontSize: 16,
imageSize: 80
};
}
}
}
export enum DeviceType {
WEARABLE = 'wearable',
PHONE = 'phone',
TABLET = 'tablet'
}
export interface LayoutConfig {
columns: number;
itemHeight: number;
fontSize: number;
imageSize: number;
}
五、注意事项与最佳实践
5.1 数据同步冲突解决
最后写入优先策略:当多个设备同时修改同一商品时,采用时间戳机制,最后修改的操作覆盖之前的操作。
设备优先级:手机作为主要操作设备,其修改优先级高于手表等辅助设备。
// 冲突解决示例
async resolveConflict(productId: string, localProduct: Product, remoteProduct: Product): Promise<Product> {
const localTimestamp = localProduct.timestamp || 0;
const remoteTimestamp = remoteProduct.timestamp || 0;
// 时间戳更新的优先
if (remoteTimestamp > localTimestamp) {
return remoteProduct;
} else {
return localProduct;
}
}
5.2 性能优化建议
- 数据分页加载:购物车商品过多时,采用分页加载策略。
- 图片懒加载:商品图片在进入可视区域时再加载。
- 防抖处理:商品数量修改操作添加防抖,避免频繁同步。
- 本地缓存:在分布式存储基础上添加本地缓存,提升读取速度。
5.3 常见问题及解决方案
问题1:设备连接不稳定
- 解决方案:实现重连机制,在网络恢复时自动重新同步。
问题2:同步延迟
- 解决方案:添加本地状态提示,明确告知用户数据同步状态。
问题3:权限申请失败
- 解决方案:提供友好的权限引导界面,指导用户手动开启权限。
第三部分:总结
核心要点回顾
- 分布式架构是基础:通过KV Store实现多设备数据实时同步,采用发布-订阅模式确保数据一致性。
- 权限配置是关键:必须正确声明
DISTRIBUTED_DATASYNC和ACCESS_SERVICE_DM权限,并在运行时动态申请。 - 设备适配是体验保障:根据设备类型采用不同的UI布局和交互方式,确保在各设备上都有良好体验。
- 冲突解决是稳定性核心:实现合理的数据冲突解决策略,确保在多设备同时操作时的数据正确性。
行动建议
- 开发阶段:使用DevEco Studio的分布式模拟器进行多设备联调,重点测试网络切换、数据冲突等边界场景。
- 测试阶段:覆盖单设备异常、网络波动、权限拒绝等异常情况,确保应用健壮性。
- 上线前:在多款真机设备上进行完整流程测试,验证不同设备型号的兼容性。
下篇预告
下一篇我们将深入探讨渲染性能优化——让应用如丝般顺滑。你将学习到HarmonyOS应用渲染原理、常见性能瓶颈识别方法、以及列表渲染优化、内存管理、动画优化等实用技巧。通过性能优化,你的应用将实现更流畅的用户体验,为后续的综合实战项目打下坚实基础。
更多推荐



所有评论(0)