HarmonyOS实战项目:构建一个跨设备的Todo清单应用(上)
1. 项目概述与架构设计
1.1 项目背景与核心价值
在HarmonyOS生态中,跨设备Todo应用是展示分布式能力的最佳实践场景。用户可以在手机端创建任务,在平板上查看编辑,在手表上接收提醒,实现真正的无缝体验。这种多设备协同模式解决了传统单设备应用的数据孤岛问题,让用户任务管理更加高效便捷。
与传统Todo应用的对比优势:
- 数据实时同步:任一设备的操作秒级同步到所有设备
- 设备能力互补:手机便捷输入、平板大屏浏览、手表即时提醒
- 体验连续性:设备间任务状态无缝流转,不中断用户工作流
1.2 技术架构设计
本项目采用分层分布式架构,确保系统的高可用性和可扩展性:
应用层:UI界面(手机、平板、手表多端适配)
↓
业务层:任务管理、分布式同步、冲突解决
↓
服务层:数据持久化、设备管理、消息推送
↓
通信层:分布式软总线、数据同步引擎
核心模块职责划分:
- 任务管理模块:负责Todo任务的CRUD操作和本地存储
- 设备管理模块:发现可用设备、管理设备状态和能力
- 同步引擎模块:处理跨设备数据同步和冲突解决
- UI适配模块:针对不同设备尺寸提供最佳交互体验
2. 开发环境配置与项目初始化
2.1 环境准备与依赖配置
首先在package.json中配置项目依赖和HarmonyOS SDK版本要求:
{
"dependencies": {
"@ohos/distributedHardware": "^1.0.0",
"@ohos/data.distributedDataObject": "^1.0.0",
"@ohos.data.distributedKVStore": "^1.0.0",
"@ohos.ability.featureAbility": "^1.0.0"
},
"harmonyos": {
"compileSdkVersion": 12,
"compatibleSdkVersion": 9
}
}
2.2 权限声明配置
在module.json5中声明应用所需的分布式权限:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC",
"reason": "用于跨设备同步Todo数据",
"usedScene": {
"abilities": ["MainAbility"],
"when": "always"
}
},
{
"name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO",
"reason": "发现和连接其他设备",
"usedScene": {
"abilities": ["MainAbility"],
"when": "inuse"
}
}
]
}
}
3. 数据模型设计与分布式存储
3.1 核心数据模型定义
设计良好的数据模型是分布式应用的基础。以下是Todo应用的核心数据结构:
// Todo任务数据模型
interface TodoItem {
id: string; // 全局唯一标识
title: string; // 任务标题
description?: string; // 任务描述(可选)
completed: boolean; // 完成状态
priority: 'low' | 'medium' | 'high'; // 优先级
dueDate?: number; // 截止时间戳
createdAt: number; // 创建时间
updatedAt: number; // 最后更新时间
deviceId: string; // 创建设备ID
tags: string[]; // 标签分类
}
// 同步元数据
interface SyncMetadata {
version: number; // 数据版本号(用于冲突检测)
lastSyncTime: number; // 最后同步时间
deviceId: string; // 最后修改设备
}
// 设备信息模型
interface DeviceInfo {
deviceId: string; // 设备唯一标识
deviceName: string; // 设备显示名称
deviceType: 'phone' | 'tablet' | 'watch' | 'tv'; // 设备类型
capabilities: DeviceCapabilities; // 设备能力
lastSeen: number; // 最后在线时间
isTrusted: boolean; // 是否可信设备
}
3.2 分布式数据存储实现
采用KV Store作为主要存储方案,平衡性能与复杂度:
import distributedKVStore from '@ohos.data.distributedKVStore';
import { BusinessError } from '@ohos.base';
class TodoDataManager {
private kvManager: distributedKVStore.KVManager | null = null;
private kvStore: distributedKVStore.KVStore | null = null;
// 初始化分布式KV Store
async initializeDataStore(): Promise<void> {
try {
const config: distributedKVStore.KVManagerConfig = {
bundleName: 'com.example.todoapp',
userInfo: { userId: 'defaultUser' }
};
this.kvManager = distributedKVStore.createKVManager(config);
const options: distributedKVStore.Options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: true,
kvStoreType: distributedKVStore.KVStoreType.DEVICE_COLLABORATION,
securityLevel: distributedKVStore.SecurityLevel.S2
};
this.kvStore = await this.kvManager.getKVStore('todo_store', options);
// 设置数据变更监听
this.setupDataChangeListener();
} catch (error) {
console.error('初始化数据存储失败:', error);
}
}
// 保存Todo任务
async saveTodoItem(todo: TodoItem): Promise<boolean> {
if (!this.kvStore) return false;
try {
// 更新元数据
todo.updatedAt = Date.now();
const syncKey = `todo_${todo.id}`;
await this.kvStore.put(syncKey, JSON.stringify(todo));
return true;
} catch (error) {
console.error('保存Todo失败:', error);
return false;
}
}
// 获取所有Todo任务
async getAllTodos(): Promise<TodoItem[]> {
if (!this.kvStore) return [];
try {
const entries = await this.kvStore.getEntries('todo_');
const todos: TodoItem[] = [];
for (const entry of entries) {
try {
const todo = JSON.parse(entry.value as string) as TodoItem;
todos.push(todo);
} catch (parseError) {
console.warn('解析Todo数据失败:', parseError);
}
}
return todos.sort((a, b) => b.updatedAt - a.updatedAt);
} catch (error) {
console.error('获取Todo列表失败:', error);
return [];
}
}
}
4. 设备管理与发现机制
4.1 设备发现与连接
实现设备的自动发现和可信连接管理:
import deviceManager from '@ohos.distributedHardware.deviceManager';
class DeviceDiscoveryManager {
private availableDevices: Map<string, DeviceInfo> = new Map();
private deviceCallbacks: Array<(devices: DeviceInfo[]) => void> = [];
// 初始化设备发现
async initializeDeviceDiscovery(): Promise<void> {
try {
const dm = await deviceManager.createDeviceManager('com.example.todoapp');
// 监听设备状态变化
dm.on('deviceStateChange', (data) => {
this.handleDeviceStateChange(data);
});
// 开始设备发现
const subscribeInfo = {
subscribeId: Math.random().toString(36).substring(2),
mode: 0xAA, // 主动发现模式
medium: 2, // Wi-Fi
freq: 2, // 正常频率
isSameAccount: false,
isWakeRemote: true,
capability: 0
};
dm.startDeviceDiscovery(subscribeInfo);
} catch (error) {
console.error('设备发现初始化失败:', error);
}
}
// 处理设备状态变化
private handleDeviceStateChange(data: any): void {
const { deviceId, deviceName, deviceType, state } = data;
if (state === 'online') {
// 设备上线
const deviceInfo: DeviceInfo = {
deviceId,
deviceName,
deviceType: this.mapDeviceType(deviceType),
capabilities: await this.getDeviceCapabilities(deviceId),
lastSeen: Date.now(),
isTrusted: true
};
this.availableDevices.set(deviceId, deviceInfo);
this.notifyDeviceCallbacks();
} else if (state === 'offline') {
// 设备离线
this.availableDevices.delete(deviceId);
this.notifyDeviceCallbacks();
}
}
// 获取可信设备列表
getTrustedDevices(): DeviceInfo[] {
return Array.from(this.availableDevices.values())
.filter(device => device.isTrusted)
.sort((a, b) => b.lastSeen - a.lastSeen);
}
}
4.2 设备能力协商
不同设备具有不同的能力,需要智能分配任务:
interface DeviceCapabilities {
canEdit: boolean; // 是否支持编辑
canNotify: boolean; // 是否支持通知
screenSize: 'small' | 'medium' | 'large'; // 屏幕尺寸
inputMethods: ('touch' | 'voice' | 'keyboard')[]; // 输入方式
}
class DeviceCapabilityManager {
// 根据设备类型推断能力
inferCapabilities(deviceType: string): DeviceCapabilities {
const capabilities: { [key: string]: DeviceCapabilities } = {
'phone': {
canEdit: true,
canNotify: true,
screenSize: 'small',
inputMethods: ['touch', 'voice']
},
'tablet': {
canEdit: true,
canNotify: true,
screenSize: 'medium',
inputMethods: ['touch', 'keyboard']
},
'watch': {
canEdit: false,
canNotify: true,
screenSize: 'small',
inputMethods: ['touch']
},
'tv': {
canEdit: false,
canNotify: false,
screenSize: 'large',
inputMethods: []
}
};
return capabilities[deviceType] || capabilities.phone;
}
// 为任务分配合适的设备
findSuitableDeviceForTask(task: TodoItem, devices: DeviceInfo[]): string | null {
if (task.completed) {
// 已完成任务适合在任何设备查看
return devices[0]?.deviceId || null;
}
// 未完成任务优先选择支持编辑的设备
const editableDevices = devices.filter(device =>
device.capabilities.canEdit
);
if (editableDevices.length > 0) {
// 优先选择屏幕较大的设备进行编辑
return editableDevices.sort((a, b) =>
this.getScreenSizeScore(b.capabilities.screenSize) -
this.getScreenSizeScore(a.capabilities.screenSize)
)[0].deviceId;
}
return null;
}
private getScreenSizeScore(size: string): number {
const scores = { 'small': 1, 'medium': 2, 'large': 3 };
return scores[size] || 1;
}
}
5. 分布式任务同步引擎
5.1 同步策略设计
实现高效可靠的数据同步机制:
class SyncEngine {
private pendingChanges: Map<string, TodoItem> = new Map();
private isSyncing: boolean = false;
private syncQueue: Array<() => Promise<void>> = [];
// 队列化同步操作
async queueSyncOperation(operation: () => Promise<void>): Promise<void> {
this.syncQueue.push(operation);
if (!this.isSyncing) {
await this.processSyncQueue();
}
}
// 处理同步队列
private async processSyncQueue(): Promise<void> {
this.isSyncing = true;
while (this.syncQueue.length > 0) {
const operation = this.syncQueue.shift();
if (operation) {
try {
await operation();
} catch (error) {
console.error('同步操作失败:', error);
// 重试逻辑
await this.retryOperation(operation);
}
}
}
this.isSyncing = false;
}
// 增量同步机制
async performIncrementalSync(localTodos: TodoItem[], remoteTodos: TodoItem[]): Promise<TodoItem[]> {
const mergedTodos: TodoItem[] = [];
const localMap = new Map(localTodos.map(todo => [todo.id, todo]));
const remoteMap = new Map(remoteTodos.map(todo => [todo.id, todo]));
// 合并所有唯一的ID
const allIds = new Set([...localMap.keys(), ...remoteMap.keys()]);
for (const id of allIds) {
const local = localMap.get(id);
const remote = remoteMap.get(id);
if (local && remote) {
// 冲突解决
mergedTodos.push(this.resolveConflict(local, remote));
} else if (local) {
mergedTodos.push(local);
} else if (remote) {
mergedTodos.push(remote);
}
}
return mergedTodos;
}
}
5.2 冲突解决策略
处理多设备同时修改的数据冲突:
class ConflictResolver {
// 基于时间戳的冲突解决
resolveConflict(local: TodoItem, remote: TodoItem): TodoItem {
// 优先选择较新的版本
if (remote.updatedAt > local.updatedAt) {
return remote;
} else if (local.updatedAt > remote.updatedAt) {
return local;
}
// 时间戳相同,基于设备优先级
return this.resolveByDevicePriority(local, remote);
}
// 基于设备类型的优先级解决
private resolveByDevicePriority(local: TodoItem, remote: TodoItem): TodoItem {
const devicePriority = { 'phone': 3, 'tablet': 2, 'watch': 1, 'tv': 0 };
const localPriority = devicePriority[local.deviceType] || 0;
const remotePriority = devicePriority[remote.deviceType] || 0;
if (remotePriority > localPriority) {
return remote;
} else {
return local;
}
}
// 智能合并策略(用于复杂冲突)
smartMerge(local: TodoItem, remote: TodoItem): TodoItem {
// 检查哪些字段有冲突
const conflictedFields = this.getConflictedFields(local, remote);
if (conflictedFields.length === 0) {
return local.updatedAt >= remote.updatedAt ? local : remote;
}
// 对每个冲突字段应用特定合并规则
const merged = { ...local };
for (const field of conflictedFields) {
merged[field] = this.mergeField(field, local[field], remote[field]);
}
merged.updatedAt = Date.now();
merged.deviceId = this.getCurrentDeviceId();
return merged;
}
}
6. 基础UI组件与交互设计
6.1 响应式布局组件
创建适配不同屏幕尺寸的UI组件:
@Component
struct TodoListItem {
@Prop todo: TodoItem;
@Prop onToggle: (id: string) => void;
@Prop onEdit: (todo: TodoItem) => void;
@StorageProp('currentDeviceType') deviceType: string;
build() {
Row() {
// 完成状态复选框
Checkbox({ selected: this.todo.completed })
.onChange((checked: boolean) => {
this.onToggle(this.todo.id);
})
.margin(12)
// 任务内容区域
Column() {
Text(this.todo.title)
.fontSize(this.getTitleFontSize())
.decoration({ type: this.todo.completed ?
TextDecorationType.LineThrough : TextDecorationType.None })
if (this.todo.description) {
Text(this.todo.description)
.fontSize(14)
.fontColor(Color.Gray)
.maxLines(2)
.margin({ top: 4 })
}
// 标签和日期信息
if (this.deviceType !== 'watch') {
this.buildMetaInfo();
}
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
// 操作按钮(根据设备能力显示)
if (this.deviceType !== 'watch') {
Button('编辑')
.onClick(() => this.onEdit(this.todo))
.padding(8)
}
}
.padding(16)
.backgroundColor(Color.White)
.borderRadius(8)
.shadow(2)
}
// 根据设备类型调整字体大小
private getTitleFontSize(): number {
const sizes = { 'phone': 16, 'tablet': 18, 'watch': 14, 'tv': 20 };
return sizes[this.deviceType] || 16;
}
@Builder
buildMetaInfo() {
Row() {
if (this.todo.priority !== 'medium') {
Text(this.getPriorityText())
.fontSize(12)
.fontColor(this.getPriorityColor())
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
.backgroundColor(this.getPriorityBackground())
.borderRadius(4)
}
if (this.todo.dueDate) {
Text(this.formatDate(this.todo.dueDate))
.fontSize(12)
.fontColor(Color.Gray)
.margin({ left: 8 })
}
}
.margin({ top: 8 })
}
}
6.2 设备适配的页面布局
主页面根据设备能力提供不同的布局:
@Entry
@Component
struct TodoMainPage {
@State todos: TodoItem[] = [];
@State currentDevice: DeviceInfo;
@Provide('currentDeviceType') deviceType: string = 'phone';
// 设备特定的布局配置
private getLayoutConfig() {
const configs = {
'phone': { columns: 1, itemHeight: 80 },
'tablet': { columns: 2, itemHeight: 100 },
'tv': { columns: 3, itemHeight: 120 },
'watch': { columns: 1, itemHeight: 60 }
};
return configs[this.deviceType] || configs.phone;
}
build() {
Column() {
// 标题栏
this.buildHeader()
// 内容区域
if (this.deviceType === 'phone' || this.deviceType === 'watch') {
this.buildMobileLayout();
} else {
this.buildTabletLayout();
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
@Builder
buildHeader() {
Row() {
Text('Todo清单')
.fontSize(24)
.fontWeight(FontWeight.Bold)
Blank()
// 设备连接状态指示器
DeviceStatusIndicator({ device: this.currentDevice })
// 同步状态指示器
SyncStatusIndicator()
}
.padding(16)
.backgroundColor(Color.White)
}
@Builder
buildMobileLayout() {
Column() {
// 统计信息
TodoStatistics({ todos: this.todos })
// 列表区域
List({ space: 12 }) {
ForEach(this.todos, (todo: TodoItem) => {
ListItem() {
TodoListItem({
todo: todo,
onToggle: this.toggleTodo.bind(this),
onEdit: this.editTodo.bind(this)
})
}
})
}
.layoutWeight(1)
}
}
@Builder
buildTabletLayout() {
Row() {
// 侧边栏(统计和筛选)
Column() {
TodoStatistics({ todos: this.todos })
TodoFilter()
}
.width('30%')
.margin({ right: 12 })
// 主内容区
Column() {
List({ space: 12 }) {
ForEach(this.todos, (todo: TodoItem) => {
ListItem() {
TodoListItem({
todo: todo,
onToggle: this.toggleTodo.bind(this),
onEdit: this.editTodo.bind(this)
})
}
})
}
}
.layoutWeight(1)
}
}
}
7. 总结与下篇预告
本篇我们完成了跨设备Todo应用的基础架构搭建,重点实现了:
7.1 本篇完成的核心功能
- 项目架构设计:确立了分层分布式架构和模块职责划分
- 数据模型设计:定义了完整的Todo数据结构和同步元数据
- 分布式存储:基于KV Store实现了数据的跨设备持久化
- 设备管理:实现了设备发现、能力协商和连接管理
- 同步引擎:设计了增量同步和冲突解决机制
- UI组件:创建了响应式布局和设备适配的界面组件
7.2 关键技术亮点
- 智能设备发现:自动发现周边设备并建立可信连接
- 冲突解决策略:基于时间戳和设备优先级的智能合并
- 响应式设计:一套代码适配多种设备尺寸和能力
- 队列化同步:确保数据同步的可靠性和顺序性
7.3 下篇内容预告
在下一篇中,我们将深入实现以下高级功能:
- 高级同步特性:实时同步、离线队列、批量操作优化
- 任务智能分配:基于设备能力和用户习惯的任务路由
- 高级UI交互:手势操作、语音输入、动效优化
- 性能优化:内存管理、渲染优化、电量控制
- 测试与调试:分布式场景下的调试技巧和性能分析
需要参加鸿蒙认证的请点击 鸿蒙认证链接

浙公网安备 33010602011771号