Harmony開發之分佈式數據管理——跨設備數據同步

引入:手機編輯文檔,平板接着看

在日常工作中,我們經常需要在多個設備間切換使用同一個應用。比如在手機上編輯文檔,回到家後想在平板上繼續編輯;或者在手機上添加購物車商品,在平板上查看購物車內容。傳統方案需要手動同步數據,或者依賴雲端存儲,操作繁瑣且存在數據延遲。HarmonyOS的分佈式數據管理技術,讓數據能夠在可信設備組網內自由流動,實現"一次操作,多端同步"的無縫體驗。

一、分佈式數據管理核心概念

1.1 什麼是分佈式數據管理?

分佈式數據管理是HarmonyOS實現"超級終端"體驗的核心技術基礎,它打破了傳統單設備數據孤島的限制,讓數據能夠在可信設備組網內自由流動。與傳統的雲同步方案不同,HarmonyOS的分佈式數據管理基於分佈式軟總線技術,實現了設備間的直接數據同步,具有低延遲、高可靠和離線可用的特點。

1.2 核心價值與優勢

傳統數據同步的痛點:

  • 需要手動建立通信連接和消息處理邏輯
  • 數據衝突解決複雜,調試難度隨設備數量增加而倍增
  • 網絡依賴性強,離線場景體驗差

HarmonyOS分佈式數據管理的優勢:

  • 自動同步:系統自動完成設備發現、連接建立和數據同步
  • 透明訪問:開發者像操作本地數據一樣訪問跨設備數據
  • 實時性高:毫秒級響應,設備間數據修改可實時感知
  • 離線支持:網絡中斷時數據暫存本地,恢復後自動補同步

1.3 技術架構層次

HarmonyOS分佈式數據管理採用分層架構設計:

  • 應用層:提供開發者友好的API接口
  • 服務層:數據管理、同步調度、衝突解決等核心服務
  • 通信層:分佈式軟總線,負責設備間數據傳輸
  • 存儲層:本地數據持久化存儲

二、分佈式數據對象詳解

2.1 核心特性與運作機制

生命週期狀態:分佈式數據對象的生命週期包含四種狀態:

  • 未初始化:對象未創建或已銷燬
  • 本地數據對象:已創建但未加入同步組網
  • 分佈式數據對象:設備在線且相同sessionId的設備數≥2,可跨設備同步
  • 已銷燬:內存釋放,磁盤數據清除

數據同步機制:分佈式數據對象建立在分佈式內存數據庫之上,採用JS對象型封裝機制。當開發者對對象屬性進行"讀取"或"賦值"時,會自動映射到數據庫的get/put操作。

2.2 完整開發實戰

以下是一個完整的分佈式數據對象實現示例:

// TodoItem.ts - 定義待辦事項數據結構
export class TodoItem {
  id: string;
  title: string;
  completed: boolean;
  
  constructor(id: string, title: string, completed: boolean = false) {
    this.id = id;
    this.title = title;
    this.completed = completed;
  }
}
// TodoService.ts - 分佈式數據對象管理
import { distributedData } from '@ohos.data.distributedData';
import { BusinessError } from '@ohos.BasicServicesKit';

export class TodoService {
  private kvManager: distributedData.KVManager | null = null;
  private kvStore: distributedData.KVStore | null = null;
  
  // 初始化分佈式數據對象
  async init(): Promise<void> {
    try {
      // 創建KVManager
      const config = {
        bundleName: 'com.example.todoapp',
        userInfo: {
          userId: 'defaultUser',
          userType: distributedData.UserType.SAME_USER_ID
        }
      };
      
      this.kvManager = distributedData.createKVManager(config);
      
      // 創建KVStore並開啓自動同步
      const options = {
        createIfMissing: true,
        encrypt: false,
        backup: false,
        autoSync: true,  // 開啓自動同步
        kvStoreType: distributedData.KVStoreType.DEVICE_COLLABORATION
      };
      
      this.kvStore = await this.kvManager.getKVStore('todo_store', options);
      
      // 訂閲數據變更通知
      this.kvStore.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_ALL, (data) => {
        this.handleDataChange(data);
      });
      
    } catch (error) {
      console.error('初始化分佈式數據對象失敗:', error);
    }
  }
  
  // 添加待辦事項
  async addTodo(todo: TodoItem): Promise<void> {
    if (!this.kvStore) {
      await this.init();
    }
    
    try {
      await this.kvStore.put(todo.id, JSON.stringify(todo));
      console.log('待辦事項已添加並同步');
    } catch (error) {
      console.error('添加待辦事項失敗:', error);
    }
  }
  
  // 獲取所有待辦事項
  async getAllTodos(): Promise<TodoItem[]> {
    if (!this.kvStore) {
      await this.init();
    }
    
    try {
      const entries = await this.kvStore.getEntries('');
      const todos: TodoItem[] = [];
      
      for (const entry of entries) {
        try {
          const todo = JSON.parse(entry.value as string);
          todos.push(new TodoItem(todo.id, todo.title, todo.completed));
        } catch (parseError) {
          console.error('解析待辦事項失敗:', parseError);
        }
      }
      
      return todos;
    } catch (error) {
      console.error('獲取待辦事項失敗:', error);
      return [];
    }
  }
  
  // 處理數據變更
  private handleDataChange(data: distributedData.ChangeNotification): void {
    console.log('數據發生變化:', data);
    // 這裏可以觸發UI更新或其他業務邏輯
  }
}
// TodoListPage.ets - 待辦事項列表頁面
import { TodoService } from '../service/TodoService';
import { TodoItem } from '../model/TodoItem';

@Entry
@Component
struct TodoListPage {
  @State todoList: TodoItem[] = [];
  private todoService: TodoService = new TodoService();
  
  aboutToAppear() {
    this.loadTodos();
  }
  
  async loadTodos() {
    this.todoList = await this.todoService.getAllTodos();
  }
  
  async addNewTodo() {
    const newTodo = new TodoItem(
      Date.now().toString(),
      '新的待辦事項',
      false
    );
    await this.todoService.addTodo(newTodo);
    await this.loadTodos(); // 重新加載列表
  }
  
  build() {
    Column() {
      Button('添加待辦事項')
        .onClick(() => this.addNewTodo())
        .margin({ top: 20 })
      
      List() {
        ForEach(this.todoList, (item: TodoItem) => {
          ListItem() {
            Row() {
              Text(item.title)
                .fontSize(18)
                .fontColor(item.completed ? '#666' : '#000')
              Checkbox()
                .checked(item.completed)
                .onChange((value: boolean) => {
                  // 更新完成狀態
                  item.completed = value;
                  this.todoService.addTodo(item);
                })
            }
            .padding(10)
          }
        })
      }
      .layoutWeight(1)
    }
  }
}

三、分佈式數據庫實戰

3.1 關係型數據庫跨設備同步

對於需要持久化存儲的場景,HarmonyOS提供了分佈式關係型數據庫解決方案:

// 文件:services/DatabaseService.ts
import { relationalStore } from '@ohos.data.relationalStore';
import { BusinessError } from '@ohos.BasicServicesKit';

export class DatabaseService {
  private db: relationalStore.RdbStore | null = null;
  
  // 初始化分佈式數據庫
  async init(): Promise<void> {
    try {
      const config = {
        name: 'todo_app.db',
        securityLevel: relationalStore.SecurityLevel.S1,
        distributed: true  // 關鍵配置:啓用分佈式
      };
      
      this.db = await relationalStore.getRdbStore(getContext(this), config);
      
      // 創建表
      await this.createTables();
      
      console.log('分佈式數據庫初始化成功');
    } catch (error) {
      console.error('數據庫初始化失敗:', error);
    }
  }
  
  // 創建數據表
  private async createTables(): Promise<void> {
    if (!this.db) {
      return;
    }
    
    try {
      // 創建待辦事項表
      await this.db.executeSql(`
        CREATE TABLE IF NOT EXISTS todos (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          title TEXT NOT NULL,
          completed INTEGER DEFAULT 0,
          created_at INTEGER,
          updated_at INTEGER
        )
      `);
      
      // 設置分佈式表
      await this.db.setDistributedTables(['todos']);
      
      console.log('數據表創建成功');
    } catch (error) {
      console.error('創建表失敗:', error);
    }
  }
  
  // 添加待辦事項
  async addTodo(title: string): Promise<number> {
    if (!this.db) {
      await this.init();
    }
    
    try {
      const now = Date.now();
      const value = {
        title: title,
        completed: 0,
        created_at: now,
        updated_at: now
      };
      
      const result = await this.db.insert('todos', value);
      console.log('待辦事項添加成功,ID:', result);
      return result;
    } catch (error) {
      console.error('添加待辦事項失敗:', error);
      throw error;
    }
  }
  
  // 查詢所有待辦事項
  async getAllTodos(): Promise<any[]> {
    if (!this.db) {
      await this.init();
    }
    
    try {
      const predicates = new relationalStore.RdbPredicates('todos');
      const resultSet = await this.db.query(predicates, ['id', 'title', 'completed', 'created_at', 'updated_at']);
      
      const todos: any[] = [];
      while (resultSet.goToNextRow()) {
        todos.push({
          id: resultSet.getLong(resultSet.getColumnIndex('id')),
          title: resultSet.getString(resultSet.getColumnIndex('title')),
          completed: resultSet.getLong(resultSet.getColumnIndex('completed')),
          created_at: resultSet.getLong(resultSet.getColumnIndex('created_at')),
          updated_at: resultSet.getLong(resultSet.getColumnIndex('updated_at'))
        });
      }
      
      return todos;
    } catch (error) {
      console.error('查詢待辦事項失敗:', error);
      return [];
    }
  }
  
  // 手動觸發數據同步
  async syncData(): Promise<void> {
    if (!this.db) {
      await this.init();
    }
    
    try {
      const predicates = new relationalStore.RdbPredicates('todos');
      await this.db.sync(relationalStore.SyncMode.SYNC_MODE_PUSH_PULL, predicates);
      console.log('數據同步成功');
    } catch (error) {
      console.error('數據同步失敗:', error);
    }
  }
}

3.2 數據同步衝突解決策略

在分佈式環境中,數據同步衝突是常見問題。HarmonyOS提供了多種衝突解決機制:

// 配置衝突解決策略
const storeConfig: relationalStore.StoreConfig = {
  conflictResolution: relationalStore.ConflictResolutionPolicy.LAST_WIN  // 最後寫入勝利
};

// 創建數據庫時應用衝突策略
this.db = await relationalStore.getRdbStore(getContext(this), config, storeConfig);

內置衝突解決策略:

  • LAST_WIN:最後寫入勝利,保留最新數據
  • ROLLBACK:回滾事務
  • ABORT:中止操作
  • REPLACE:替換舊數據

自定義衝突解決函數:

// 自定義衝突解決策略
store.setConflictResolver((localData, remoteData) => {
  // 規則1:價格衝突時取最低價
  if (localData.price !== remoteData.price) {
    return localData.price < remoteData.price ? localData : remoteData;
  }
  
  // 規則2:數量衝突時求和
  return { 
    ...localData, 
    count: localData.count + remoteData.count 
  };
});

四、權限配置與安全機制

4.1 權限申請

使用分佈式數據管理需要申請相關權限:

// module.json5
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.DISTRIBUTED_DATASYNC"
      },
      {
        "name": "ohos.permission.ACCESS_NETWORK_STATE"
      }
    ]
  }
}

4.2 數據安全等級

HarmonyOS將數據分為5個安全等級(S0-S4),從生成開始,在存儲、使用、傳輸的整個生命週期都需要根據對應的安全策略提供不同強度的安全防護。

// 配置數據庫安全等級
const config = {
  name: 'secure_data.db',
  securityLevel: relationalStore.SecurityLevel.S2,  // S2安全等級
  encrypt: true  // 啓用加密
};

五、性能優化與最佳實踐

5.1 數據同步優化

批量操作:減少跨設備通信次數

// 批量插入數據
async batchInsertTodos(todos: any[]) {
  await this.db.batchInsert('todos', todos);
}

// 批量更新數據
async batchUpdateTodos(todos: any[]) {
  const updates = todos.map(todo => ({
    table: 'todos',
    values: todo,
    predicates: new relationalStore.RdbPredicates('todos')
      .equalTo('id', todo.id)
  }));
  
  await this.db.batchUpdate(updates);
}

增量同步:只同步變化部分,而不是整個數據集

// 只同步24小時內的數據
await this.db.sync(
  relationalStore.SyncMode.SYNC_MODE_PUSH_PULL,
  new relationalStore.RdbPredicates('todos')
    .greaterThan('updated_at', Date.now() - 86400000)
);

5.2 索引優化

為高頻查詢字段添加索引提升性能:

// 為completed字段添加索引
await this.db.executeSql('CREATE INDEX idx_completed ON todos(completed)');

// 為created_at字段添加索引
await this.db.executeSql('CREATE INDEX idx_created_at ON todos(created_at)');

5.3 網絡狀態感知

import { network } from '@ohos.network';

// 監聽網絡狀態變化
network.on('networkStateChange', (state) => {
  if (state.isConnected) {
    // 網絡恢復,觸發數據同步
    this.syncData();
  } else {
    // 網絡斷開,啓用離線模式
    this.enableOfflineMode();
  }
});

六、實戰案例:跨設備購物車

6.1 場景描述

用户可以在手機上添加商品到購物車,然後在平板上查看購物車內容,實現多設備購物車同步。

6.2 核心代碼實現

// model/CartItem.ts
export class CartItem {
  id: string;
  productId: string;
  name: string;
  price: number;
  quantity: number;
  imageUrl: string;
  
  constructor(data?: any) {
    if (data) {
      this.id = data.id || '';
      this.productId = data.productId || '';
      this.name = data.name || '';
      this.price = data.price || 0;
      this.quantity = data.quantity || 1;
      this.imageUrl = data.imageUrl || '';
    }
  }
  
  toObject(): any {
    return {
      id: this.id,
      productId: this.productId,
      name: this.name,
      price: this.price,
      quantity: this.quantity,
      imageUrl: this.imageUrl
    };
  }
}
// service/CartService.ts
import { distributedData } from '@ohos.data.distributedData';
import { CartItem } from '../model/CartItem';

export class CartService {
  private kvManager: distributedData.KVManager | null = null;
  private kvStore: distributedData.KVStore | null = null;
  private readonly CART_KEY = 'shopping_cart';
  
  async init(): Promise<void> {
    try {
      const config = {
        bundleName: 'com.example.shopping',
        userInfo: {
          userId: 'defaultUser',
          userType: distributedData.UserType.SAME_USER_ID
        }
      };
      
      this.kvManager = distributedData.createKVManager(config);
      
      const options = {
        createIfMissing: true,
        encrypt: false,
        backup: false,
        autoSync: true,
        kvStoreType: distributedData.KVStoreType.DEVICE_COLLABORATION
      };
      
      this.kvStore = await this.kvManager.getKVStore('cart_store', options);
      
      // 訂閲購物車數據變化
      this.kvStore.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_ALL, (data) => {
        this.handleCartChange(data);
      });
      
    } catch (error) {
      console.error('購物車服務初始化失敗:', error);
    }
  }
  
  // 添加商品到購物車
  async addToCart(product: any): Promise<void> {
    if (!this.kvStore) {
      await this.init();
    }
    
    try {
      // 獲取當前購物車
      const cartItems = await this.getCartItems();
      
      // 檢查商品是否已存在
      const existingItem = cartItems.find(item => item.productId === product.id);
      
      if (existingItem) {
        // 增加數量
        existingItem.quantity += 1;
      } else {
        // 添加新商品
        const newItem = new CartItem({
          id: Date.now().toString(),
          productId: product.id,
          name: product.name,
          price: product.price,
          quantity: 1,
          imageUrl: product.imageUrl
        });
        cartItems.push(newItem);
      }
      
      // 保存購物車
      await this.kvStore.put(this.CART_KEY, JSON.stringify(cartItems.map(item => item.toObject())));
      console.log('商品已添加到購物車');
      
    } catch (error) {
      console.error('添加商品到購物車失敗:', error);
    }
  }
  
  // 獲取購物車商品列表
  async getCartItems(): Promise<CartItem[]> {
    if (!this.kvStore) {
      await this.init();
    }
    
    try {
      const cartData = await this.kvStore.get(this.CART_KEY);
      if (cartData) {
        const items = JSON.parse(cartData as string);
        return items.map((item: any) => new CartItem(item));
      }
      return [];
    } catch (error) {
      console.error('獲取購物車商品失敗:', error);
      return [];
    }
  }
  
  // 更新商品數量
  async updateQuantity(productId: string, quantity: number): Promise<void> {
    if (!this.kvStore) {
      await this.init();
    }
    
    try {
      const cartItems = await this.getCartItems();
      const item = cartItems.find(item => item.productId === productId);
      
      if (item) {
        item.quantity = quantity;
        if (item.quantity <= 0) {
          // 數量為0,移除商品
          const index = cartItems.findIndex(i => i.productId === productId);
          if (index !== -1) {
            cartItems.splice(index, 1);
          }
        }
        
        await this.kvStore.put(this.CART_KEY, JSON.stringify(cartItems.map(item => item.toObject())));
        console.log('商品數量已更新');
      }
    } catch (error) {
      console.error('更新商品數量失敗:', error);
    }
  }
  
  // 清空購物車
  async clearCart(): Promise<void> {
    if (!this.kvStore) {
      await this.init();
    }
    
    try {
      await this.kvStore.delete(this.CART_KEY);
      console.log('購物車已清空');
    } catch (error) {
      console.error('清空購物車失敗:', error);
    }
  }
  
  // 處理購物車數據變化
  private handleCartChange(data: distributedData.ChangeNotification): void {
    console.log('購物車數據發生變化:', data);
    // 這裏可以觸發UI更新或其他業務邏輯
  }
}

6.3 購物車頁面實現

// pages/CartPage.ets
import { CartService } from '../service/CartService';
import { CartItem } from '../model/CartItem';

@Entry
@Component
struct CartPage {
  @State cartItems: CartItem[] = [];
  private cartService: CartService = new CartService();
  
  aboutToAppear() {
    this.loadCart();
  }
  
  async loadCart() {
    this.cartItems = await this.cartService.getCartItems();
  }
  
  async updateQuantity(productId: string, quantity: number) {
    await this.cartService.updateQuantity(productId, quantity);
    await this.loadCart();
  }
  
  async clearCart() {
    await this.cartService.clearCart();
    await this.loadCart();
  }
  
  build() {
    Column() {
      // 購物車標題
      Row() {
        Text('購物車')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
        Button('清空')
          .onClick(() => this.clearCart())
          .margin({ left: 20 })
      }
      .padding(16)
      .width('100%')
      
      // 購物車列表
      if (this.cartItems.length === 0) {
        Column() {
          Image($r('app.media.empty_cart'))
            .width(120)
            .height(120)
          Text('購物車為空')
            .fontSize(18)
            .margin({ top: 20 })
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
      } else {
        List() {
          ForEach(this.cartItems, (item: CartItem) => {
            ListItem() {
              Row() {
                // 商品圖片
                Image(item.imageUrl)
                  .width(80)
                  .height(80)
                  .objectFit(ImageFit.Cover)
                  .borderRadius(8)
                  .margin({ right: 12 })
                
                // 商品信息
                Column() {
                  Text(item.name)
                    .fontSize(16)
                    .fontWeight(FontWeight.Medium)
                  Text(`¥${item.price.toFixed(2)}`)
                    .fontSize(14)
                    .fontColor('#ff5000')
                }
                .layoutWeight(1)
                
                // 數量控制
                Row() {
                  Button('-')
                    .onClick(() => this.updateQuantity(item.productId, item.quantity - 1))
                  Text(item.quantity.toString())
                    .width(30)
                    .textAlign(TextAlign.Center)
                  Button('+')
                    .onClick(() => this.updateQuantity(item.productId, item.quantity + 1))
                }
              }
              .padding(12)
            }
          })
        }
        .layoutWeight(1)
        
        // 底部結算欄
        Row() {
          Text(`合計:¥${this.cartItems.reduce((total, item) => total + item.price * item.quantity, 0).toFixed(2)}`)
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
          Button('結算')
            .backgroundColor('#ff5000')
            .fontColor(Color.White)
            .margin({ left: 20 })
        }
        .padding(16)
        .backgroundColor('#f5f5f5')
        .width('100%')
      }
    }
  }
}

七、總結與行動建議

7.1 核心要點回顧

  1. 分佈式數據對象:適合臨時數據同步,數據存儲在內存中,應用關閉後數據不保留
  2. 分佈式數據庫:適合持久化數據存儲,支持關係型數據模型,數據持久化到磁盤
  3. 自動同步機制:系統自動完成設備發現、連接建立和數據同步,開發者無需關心底層細節
  4. 衝突解決策略:提供多種內置衝突解決機制,支持自定義衝突解決函數
  5. 安全機制:支持數據加密、權限控制和設備認證,確保數據安全

7.2 行動建議

  1. 場景選擇:根據業務需求選擇合適的存儲方案 臨時數據(如購物車、草稿)→ 分佈式數據對象 持久化數據(如用户信息、訂單)→ 分佈式數據庫
  2. 性能優化: 使用批量操作減少網絡請求 為高頻查詢字段添加索引 合理設置數據同步頻率
  3. 錯誤處理: 添加網絡狀態監聽,處理離線場景 實現數據同步失敗的重試機制 記錄操作日誌便於問題排查
  4. 測試驗證: 在不同網絡環境下測試數據同步 模擬多設備同時操作,驗證衝突解決機制 測試離線場景下的數據恢復能力

通過合理運用HarmonyOS的分佈式數據管理能力,可以輕鬆構建跨設備協同應用,為用户提供無縫的多設備使用體驗。