一、HarmonyOS 5 分布式能力概述

HarmonyOS 作为新一代分布式操作系统,其核心特性之一就是支持设备间的无缝协同。在 HarmonyOS 5 中,分布式数据管理能力得到了显著增强,特别是关系型数据库的跨设备同步功能,为开发者提供了更强大的数据共享能力。

1.1 分布式数据同步的基本概念

关系型数据库跨设备数据同步是指应用程序本地存储的关系型数据能够在组网内的不同设备间自动同步。这种同步是双向的,既可以将数据从本地设备推送到远程设备,也可以将数据从远程设备拉取到本地。

在 HarmonyOS 5 中,这一功能主要依赖以下几个核心组件:

  1. 分布式表:开发者可以指定需要同步的表为分布式表
  2. 数据管理服务:负责数据的读取、传输和写入
  3. 同步机制:包括推送和拉取两种同步方式
  4. 变化通知:本地和分布式数据变化的实时通知

二、关系型数据库跨设备同步的实现原理

2.1 数据同步流程

当应用程序写入关系型数据库后,整个同步过程主要分为以下几个步骤:

  1. 应用向数据管理服务发起同步请求
  2. 数据管理服务从应用沙箱内读取待同步数据
  3. 根据对端设备的 deviceId 将数据发送到目标设备
  4. 目标设备的数据管理服务接收数据并写入同应用的数据库

2.2 数据变化通知机制

HarmonyOS 5 提供了完善的数据变化通知机制,主要分为两类:

  1. 本地数据变化通知:本地设备的应用内订阅数据变化通知
  2. 分布式数据变化通知:订阅组网内其他设备数据变化的通知

这种机制确保了数据变更能够实时反映在所有设备上,为用户提供一致的体验。

三、开发准备与环境配置

3.1 开发环境要求

  • DevEco Studio 5.0.4 或更高版本
  • HarmonyOS SDK API 11+
  • 支持 HarmonyOS 5 的真机设备(至少两台)

3.2 权限配置

module.json5 文件中添加必要的权限:

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

四、完整示例:实现跨设备待办事项同步

下面我们通过一个完整的待办事项应用示例,演示如何在 HarmonyOS 5 中实现关系型数据库的跨设备同步。

4.1 数据库设计与初始化

首先创建数据库和表结构:

// database/TodoDB.ts
import relationalStore from '@ohos.data.relationalStore';
import { BusinessError } from '@ohos.base';

const DB_NAME = 'todo.db';
const TABLE_NAME = 'todo';
const STORE_CONFIG: relationalStore.StoreConfig = {
  name: DB_NAME,
  securityLevel: relationalStore.SecurityLevel.S1
};

export class TodoDB {
  private rdbStore: relationalStore.RdbStore | null = null;
  
  // 初始化数据库
  async initDB(): Promise<void> {
    try {
      const sql = `CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL,
        completed INTEGER DEFAULT 0,
        timestamp INTEGER,
        device_id TEXT
      )`;
      
      this.rdbStore = await relationalStore.getRdbStore(globalThis.context, STORE_CONFIG);
      await this.rdbStore.executeSql(sql);
      
      // 设置分布式表
      await this.rdbStore.setDistributedTables([TABLE_NAME]);
      
      console.info('Database initialized and tables set as distributed');
    } catch (err) {
      console.error(`Failed to initialize database: ${JSON.stringify(err)}`);
      throw err;
    }
  }
  
  // 获取数据库实例
  getStore(): relationalStore.RdbStore {
    if (!this.rdbStore) {
      throw new Error('Database not initialized');
    }
    return this.rdbStore;
  }
}

export const todoDB = new TodoDB();

4.2 数据模型定义

定义待办事项数据模型:

// model/Todo.ts
export interface Todo {
  id?: number;
  title: string;
  completed: boolean;
  timestamp: number;
  deviceId?: string;
}

export function fromRdb(resultSet: relationalStore.ResultSet): Todo {
  return {
    id: resultSet.getLong(resultSet.getColumnIndex('id')),
    title: resultSet.getString(resultSet.getColumnIndex('title')),
    completed: resultSet.getLong(resultSet.getColumnIndex('completed')) === 1,
    timestamp: resultSet.getLong(resultSet.getColumnIndex('timestamp')),
    deviceId: resultSet.getString(resultSet.getColumnIndex('device_id'))
  };
}

4.3 数据操作实现

实现增删改查和同步操作:

// service/TodoService.ts
import { relationalStore } from '@ohos.data.relationalStore';
import { Todo, fromRdb } from '../model/Todo';
import { todoDB } from '../database/TodoDB';
import { BusinessError } from '@ohos.base';
import { deviceManager } from '@ohos.distributedDeviceManager';

export class TodoService {
  // 添加待办事项
  async addTodo(todo: Todo): Promise<number> {
    const store = todoDB.getStore();
    const valueBucket: relationalStore.ValuesBucket = {
      'title': todo.title,
      'completed': todo.completed ? 1 : 0,
      'timestamp': new Date().getTime(),
      'device_id': this.getLocalDeviceId()
    };
    
    try {
      const id = await store.insert(TABLE_NAME, valueBucket);
      console.info(`Added todo with id: ${id}`);
      return id;
    } catch (err) {
      console.error(`Failed to add todo: ${JSON.stringify(err)}`);
      throw err;
    }
  }
  
  // 获取所有待办事项
  async getAllTodos(): Promise<Todo[]> {
    const store = todoDB.getStore();
    const predicates = new relationalStore.RdbPredicates(TABLE_NAME);
    predicates.orderByAsc('timestamp');
    
    try {
      const resultSet = await store.query(predicates, ['id', 'title', 'completed', 'timestamp', 'device_id']);
      const todos: Todo[] = [];
      while (resultSet.goToNextRow()) {
        todos.push(fromRdb(resultSet));
      }
      resultSet.close();
      return todos;
    } catch (err) {
      console.error(`Failed to query todos: ${JSON.stringify(err)}`);
      throw err;
    }
  }
  
  // 同步数据到所有设备
  async syncTodos(): Promise<void> {
    const store = todoDB.getStore();
    const devices = await this.getTrustedDevices();
    
    try {
      for (const device of devices) {
        const syncPredicates = new relationalStore.RdbPredicates(TABLE_NAME);
        await store.sync(relationalStore.SyncMode.SYNC_MODE_PUSH, syncPredicates, (err: BusinessError, result: Array<[string, number]>) => {
          if (err) {
            console.error(`Sync failed for device ${device}: ${JSON.stringify(err)}`);
            return;
          }
          console.info(`Sync result for device ${device}: ${JSON.stringify(result)}`);
        });
      }
    } catch (err) {
      console.error(`Failed to sync todos: ${JSON.stringify(err)}`);
      throw err;
    }
  }
  
  // 获取可信设备列表
  private async getTrustedDevices(): Promise<string[]> {
    try {
      const deviceList = deviceManager.getTrustedDeviceListSync();
      return deviceList.map(device => device.deviceId);
    } catch (err) {
      console.error(`Failed to get trusted devices: ${JSON.stringify(err)}`);
      return [];
    }
  }
  
  // 获取本地设备ID
  private getLocalDeviceId(): string {
    try {
      return deviceManager.getLocalDeviceInfoSync().deviceId;
    } catch (err) {
      console.error(`Failed to get local device id: ${JSON.stringify(err)}`);
      return 'unknown';
    }
  }
}

export const todoService = new TodoService();

4.4 UI 界面实现

实现待办事项列表界面:

// pages/TodoList.ets
import { todoService } from '../service/TodoService';
import { Todo } from '../model/Todo';

@Entry
@Component
struct TodoList {
  @State todos: Todo[] = [];
  @State newTodoTitle: string = '';

  aboutToAppear() {
    this.loadTodos();
    this.setupDataChangeListener();
  }

  // 加载待办事项
  async loadTodos() {
    try {
      this.todos = await todoService.getAllTodos();
    } catch (err) {
      console.error(`Failed to load todos: ${JSON.stringify(err)}`);
    }
  }

  // 设置数据变化监听
  setupDataChangeListener() {
    const store = todoDB.getStore();
    store.on('dataChange', relationalStore.SubscribeType.SUBSCRIBE_TYPE_REMOTE, (changedItems: Array<string>) => {
      console.info(`Data changed on remote device: ${changedItems}`);
      this.loadTodos();
    });
  }

  // 添加新待办事项
  async addTodo() {
    if (!this.newTodoTitle.trim()) return;
    
    const todo: Todo = {
      title: this.newTodoTitle,
      completed: false,
      timestamp: new Date().getTime()
    };
    
    try {
      await todoService.addTodo(todo);
      this.newTodoTitle = '';
      await this.loadTodos();
      await todoService.syncTodos(); // 同步到其他设备
    } catch (err) {
      console.error(`Failed to add todo: ${JSON.stringify(err)}`);
    }
  }

  build() {
    Column() {
      // 输入框和添加按钮
      Row() {
        TextInput({ text: this.newTodoTitle, placeholder: 'Add a new task' })
          .onChange((value: string) => {
            this.newTodoTitle = value;
          })
          .layoutWeight(1)
        
        Button('Add')
          .onClick(() => this.addTodo())
      }
      .padding(10)
      .width('100%')

      // 待办事项列表
      List({ space: 10 }) {
        ForEach(this.todos, (todo: Todo) => {
          ListItem() {
            Row() {
              Text(todo.title)
                .fontSize(18)
                .textDecoration(todo.completed ? TextDecoration.LineThrough : TextDecoration.None)
              
              if (todo.deviceId) {
                Text(`(From ${todo.deviceId.substring(0, 4)})`)
                  .fontSize(12)
                  .fontColor(Color.Gray)
              }
            }
            .justifyContent(FlexAlign.SpaceBetween)
            .width('100%')
          }
        }, (todo: Todo) => todo.id?.toString() ?? '')
      }
      .layoutWeight(1)
      .width('100%')

      // 同步按钮
      Button('Sync Now')
        .onClick(async () => {
          await todoService.syncTodos();
          await this.loadTodos();
        })
        .margin(10)
    }
    .width('100%')
    .height('100%')
  }
}

4.5 主页面入口

// EntryAbility.ets
import { todoDB } from './database/TodoDB';
import { UIAbility } from '@ohos.arkui.UIAbility';
import { window } from '@ohos.window';

export default class EntryAbility extends UIAbility {
  async onWindowStageCreate(windowStage: window.WindowStage) {
    try {
      // 初始化数据库
      await todoDB.initDB();
      
      windowStage.loadContent('pages/TodoList', (err) => {
        if (err) {
          console.error(`Failed to load content: ${JSON.stringify(err)}`);
          return;
        }
        console.info('Content loaded successfully');
      });
    } catch (err) {
      console.error(`Failed to initialize database: ${JSON.stringify(err)}`);
    }
  }
}

五、关键技术与注意事项

5.1 分布式表的设置

在初始化数据库时,必须明确指定哪些表需要跨设备同步:

await this.rdbStore.setDistributedTables([TABLE_NAME]);

5.2 同步模式选择

HarmonyOS 5 提供了两种同步模式:

  1. SYNC_MODE_PUSH:将本地数据推送到远程设备
  2. SYNC_MODE_PULL:从远程设备拉取数据

开发者可以根据场景选择合适的同步方式。

5.3 数据冲突处理

当多个设备同时修改同一条数据时,系统会根据时间戳自动解决冲突,最后修改的数据会覆盖之前的版本。开发者也可以通过自定义冲突解决策略来处理特殊情况。

5.4 性能优化建议

  1. 避免频繁同步大数据量
  2. 合理设计表结构,减少同步的数据量
  3. 使用适当的索引提高查询效率
  4. 考虑使用增量同步而非全量同步

六、总结

本文详细介绍了 HarmonyOS 5 中关系型数据库跨设备同步的实现方法,通过一个完整的待办事项应用示例,展示了从数据库设计到UI实现的完整流程。HarmonyOS 5 的分布式数据同步能力为开发者构建多设备协同应用提供了强大支持,能够显著提升用户体验和应用价值。

在实际开发中,开发者还需要考虑网络状态、设备认证、数据安全等因素,以确保应用在各种场景下都能稳定可靠地工作。随着 HarmonyOS 生态的不断发展,分布式能力将会变得更加强大和易用,为开发者创造更多可能性。

Logo

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

更多推荐