HarmonyOS 5分布式数据同步实战:跨设备待办事项应用

🔧 一、前期准备:配置与权限

在开始编码前,需要进行一些基础配置。

  1. 模块配置 (module.json5): 在 module.json5文件中申请分布式数据同步权限。

    {
      "module": {
        "requestPermissions": [
          {
            "name": "ohos.permission.DISTRIBUTED_DATASYNC"
          }
        ]
      }
    }
    

    此权限允许应用在可信设备组网内同步数据。

  2. 导入模块: 在你的ArkTS文件中,导入必要的模块。

    import distributedKVStore from '@ohos.data.distributedKVStore';
    import deviceManager from '@ohos.distributedDeviceManager';
    import common from '@ohos.app.ability.common';
    import { BusinessError } from '@ohos.base';
    // UI相关组件
    import { TodoItem } from './TodoItem'; // 自定义数据模型,见下文
    

📊 二、定义数据模型

定义一个简单的待办事项数据模型,通常放在一个单独的文件中(如 TodoItem.ets)。

// TodoItem.ets
export class TodoItem {
  id: string; // 唯一标识,用于分布式同步
  content: string = ''; // 待办内容
  completed: boolean = false; // 完成状态
  createdAt: number = Date.now(); // 创建时间
  updatedAt: number = Date.now(); // 最后更新时间
  deviceId: string = ''; // 创建此条目的设备ID,用于显示来源

  constructor(content: string) {
    this.id = this.generateUUID(); // 生成唯一ID
    this.content = content;
  }

  private generateUUID(): string {
    // 一个简单的UUID生成方法,实际项目中可使用更复杂的算法
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      const r = Math.random() * 16 | 0;
      const v = c == 'x' ? r : (r & 0x3 | 0x8);
      return v.toString(16);
    });
  }
}

为减少冲突,每条数据需有唯一标识。deviceIdupdatedAt有助于在冲突时决定保留哪条数据(例如采用“最后写入获胜”或合并策略)。

🧩 三、初始化分布式数据库

在应用的入口组件或一个单独的管理类中初始化分布式数据库。

// DistributedKVStoreManager.ets (示例)
class DistributedKVStoreManager {
  private kvManager: distributedKVStore.KVManager | null = null;
  private kvStore: distributedKVStore.SingleKVStore | null = null;
  private static instance: DistributedKVStoreManager;

  public static getInstance(): DistributedKVStoreManager {
    // 单例模式,确保全局只有一个数据库管理器实例
    if (!DistributedKVStoreManager.instance) {
      DistributedKVStoreManager.instance = new DistributedKVStoreManager();
    }
    return DistributedKVStoreManager.instance;
  }

  public async initKVStore(context: common.Context): Promise<void> {
    try {
      // 1. 创建KVManager配置
      const kvManagerConfig: distributedKVStore.Config = {
        bundleName: 'com.example.todoapp', // 你的应用包名
        userInfo: {
          userId: '0', // 同一用户ID下的设备可以同步数据
          userType: distributedKVStore.UserType.SAME_USER_ID
        }
      };

      // 2. 创建KVManager实例
      this.kvManager = distributedKVStore.createKVManager(kvManagerConfig);

      // 3. 配置KVStore选项
      const options: distributedKVStore.StoreConfig = {
        storeId: 'todo_app_store', // 存储标识
        kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
        securityLevel: distributedKVStore.SecurityLevel.S2, // 安全等级
        autoSync: true, // 开启自动同步
        encrypt: false // 根据需求是否加密
      };

      // 4. 获取或创建KVStore
      this.kvStore = await this.kvManager.getKVStore<distributedKVStore.SingleKVStore>(options);
      console.info('Distributed KVStore initialized successfully.');
    } catch (error) {
      console.error(`Failed to initialize KVStore: ${(error as BusinessError).message}`);
    }
  }

  public getKVStore(): distributedKVStore.SingleKVStore | null {
    return this.kvStore;
  }
}

autoSync: true使得数据变更会自动同步到同一用户下的所有设备。

🧩 四、核心数据操作与同步

在同一个管理类中,实现数据的增删改查和同步方法。

// 接 DistributedKVStoreManager.ets
class DistributedKVStoreManager {
  // ... 之前的代码 ...

  // 添加待办事项
  public async addTodoItem(todoItem: TodoItem): Promise<void> {
    if (!this.kvStore) {
      console.error('KVStore is not initialized.');
      return;
    }
    try {
      // 将TodoItem对象转换为JSON字符串存储
      const todoJson = JSON.stringify(todoItem);
      // 使用id作为Key,todoJson作为Value存入KVStore
      await this.kvStore.put(todoItem.id, todoJson);
      console.info(`Todo item added: ${todoItem.content}`);
    } catch (error) {
      console.error(`Failed to add todo item: ${(error as BusinessError).message}`);
    }
  }

  // 获取所有待办事项
  public async getAllTodoItems(): Promise<TodoItem[]> {
    if (!this.kvStore) {
      console.error('KVStore is not initialized.');
      return [];
    }
    try {
      const entries = await this.kvStore.getEntries('');
      const todoItems: TodoItem[] = [];
      for (let i = 0; i < entries.length; i++) {
        const itemJson = entries[i].value.value as string;
        try {
          const todoItem: TodoItem = JSON.parse(itemJson);
          todoItems.push(todoItem);
        } catch (parseError) {
          console.error(`Failed to parse todo item: ${parseError}`);
        }
      }
      return todoItems;
    } catch (error) {
      console.error(`Failed to get todo items: ${(error as BusinessError).message}`);
      return [];
    }
  }

  // 更新待办事项(例如标记完成/未完成)
  public async updateTodoItem(todoItem: TodoItem): Promise<void> {
    if (!this.kvStore) {
      console.error('KVStore is not initialized.');
      return;
    }
    try {
      todoItem.updatedAt = Date.now(); // 更新修改时间
      const todoJson = JSON.stringify(todoItem);
      await this.kvStore.put(todoItem.id, todoJson);
      console.info(`Todo item updated: ${todoItem.content}`);
    } catch (error) {
      console.error(`Failed to update todo item: ${(error as BusinessError).message}`);
    }
  }

  // 删除待办事项
  public async deleteTodoItem(todoId: string): Promise<void> {
    if (!this.kvStore) {
      console.error('KVStore is not initialized.');
      return;
    }
    try {
      await this.kvStore.delete(todoId);
      console.info(`Todo item deleted: ${todoId}`);
    } catch (error) {
      console.error(`Failed to delete todo item: ${(error as BusinessError).message}`);
    }
  }
}

📡 五、监听数据变化

为了在数据发生变化时(包括本地和远程设备)及时更新UI,需要注册数据变化监听器。

// 接 DistributedKVStoreManager.ets
class DistributedKVStoreManager {
  // ... 之前的代码 ...

  private dataChangeListener: distributedKVStore.DataChangeListener | null = null;

  public async setupDataChangeListener(callback: (changedItems: TodoItem[]) => void): Promise<void> {
    if (!this.kvStore) {
      return;
    }
    try {
      this.dataChangeListener = (data: distributedKVStore.ChangeData) => {
        console.info(`Data changed: key=${data.key}, value=${data.value?.value}`);
        // 当监听到变化,重新获取所有数据并回调更新UI
        this.getAllTodoItems().then((items) => {
          callback(items);
        });
      };
      // 订阅所有类型的数据变更(添加、更新、删除)
      this.kvStore.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL, this.dataChangeListener);
    } catch (error) {
      console.error(`Failed to set up data change listener: ${(error as BusinessError).message}`);
    }
  }

  // 在合适的地方(例如组件销毁时)移除监听器,防止内存泄漏
  public removeDataChangeListener(): void {
    if (this.kvStore && this.dataChangeListener) {
      this.kvStore.off('dataChange', this.dataChangeListener);
      this.dataChangeListener = null;
    }
  }
}

🖥️ 六、构建UI界面

最后,在UI组件(通常是 @Entry组件)中连接数据库管理和UI渲染。

// TodoListPage.ets
import { DistributedKVStoreManager } from '../model/DistributedKVStoreManager';
import { TodoItem } from '../model/TodoItem';

@Entry
@Component
struct TodoListPage {
  @State todoList: TodoItem[] = [];
  @State newTodoContent: string = '';

  private kvStoreManager: DistributedKVStoreManager = DistributedKVStoreManager.getInstance();

  aboutToAppear() {
    // 初始化KVStore
    this.kvStoreManager.initKVStore(getContext(this)).then(async () => {
      // 初始加载数据
      this.todoList = await this.kvStoreManager.getAllTodoItems();
      // 设置数据变化监听器
      await this.kvStoreManager.setupDataChangeListener((items) => {
        this.todoList = items;
      });
    });
  }

  aboutToDisappear() {
    // 移除监听器
    this.kvStoreManager.removeDataChangeListener();
  }

  build() {
    Column() {
      // 标题
      Text('跨设备待办事项')
        .fontSize(30)
        .margin(20)

      // 输入框和添加按钮
      Row() {
        TextInput({ placeholder: '输入新事项...', text: this.newTodoContent })
          .width('70%')
          .onChange((value: string) => {
            this.newTodoContent = value;
          })
        Button('添加')
          .width('30%')
          .onClick(() => {
            if (this.newTodoContent.trim() !== '') {
              const newItem = new TodoItem(this.newTodoContent.trim());
              this.kvStoreManager.addTodoItem(newItem);
              this.newTodoContent = ''; // 清空输入框
            }
          })
      }
      .width('100%')
      .padding(10)

      // 待办事项列表
      List({ space: 10 }) {
        ForEach(this.todoList, (item: TodoItem) => {
          ListItem() {
            Row() {
              // 完成状态复选框
              Checkbox()
                .select(item.completed)
                .onChange((checked: boolean) => {
                  item.completed = checked;
                  item.updatedAt = Date.now();
                  this.kvStoreManager.updateTodoItem(item);
                })

              // 待办内容
              Text(item.content)
                .fontSize(18)
                .textDecoration(item.completed ? TextDecoration.LineThrough : TextDecoration.None)
                .fontColor(item.completed ? '#999' : '#000')

              // 删除按钮
              Button('删除')
                .onClick(() => {
                  this.kvStoreManager.deleteTodoItem(item.id);
                })
            }
            .width('100%')
            .justifyContent(FlexAlign.SpaceBetween)
          }
        }, (item: TodoItem) => item.id)
      }
      .layoutWeight(1) // 占据剩余空间
      .width('100%')
    }
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
}

💡 七、处理数据冲突

在分布式系统中,数据冲突(如多设备同时修改同一数据)不可避免。HarmonyOS分布式数据库默认采用“最后写入获胜”(LWW)策略,即时间戳最新的修改会覆盖旧数据。我们的 TodoItem模型中的 updatedAt字段正是用于比较时间先后。

对于更复杂的冲突解决策略(如合并特定字段),你可能需要在 DataChangeListener中获取变更数据后,手动比较本地和远程数据的 updatedAt字段,然后决定如何合并。

⚠️ 八、注意事项与最佳实践

  1. 性能: 单个KV记录的值不宜过大(建议小于500KB)。高频更新可考虑批处理操作。
  2. 错误处理: 务必对所有的数据库操作进行 try-catch,妥善处理可能出现的异常(如网络断开、同步失败)。
  3. 设备连接: 确保设备已登录同一华为账号,并在同一局域网内,且已在“设置”中形成可信组网。
  4. 离线支持: 应用应能在设备离线时正常进行本地增删改查,并在网络恢复后自动同步。
  5. 安全: 分布式数据在传输过程中会使用TLS进行加密。

通过以上步骤,你就可以构建一个功能完善的跨设备待办事项应用。HarmonyOS的分布式数据管理API大大简化了多设备同步的复杂性,让你能更专注于业务逻辑和用户体验。

需要参加鸿蒙认证的请点击 鸿蒙认证链接

posted @ 2025-09-24 17:15  猫林老师  阅读(37)  评论(0)    收藏  举报