【HarmonyOS 6】UIAbility跨設備連接詳解(分佈式軟總線運用)

一、前言

我對於分佈式軟總線相當的親切。2022年搞開源鴻蒙的時候,就經常和分佈式軟總線打交道。在HarmonyOS中,UIAbility跨設備連接,其實就是對底層開源鴻蒙,分佈式軟總線的能力封裝。

二、首先理解跨設備鏈接的步驟

在這裏插入圖片描述

首先由Distributed Service Kit提供該能力的封裝。

在進行跨設備鏈接之前,我們要對設備進行互信操作。這個在鴻蒙裏叫做分佈式設備管理,包含了設備的發現,配對,可信查詢,解除配對等。詳情可參見我之前寫的文章:【HarmonyOS 5】鴻蒙分佈式協同應用開發詳解

進行完上面的可信操作,我們才能對於設備間的鏈接通信做處理。

三、專有名詞理解:

DMS(Distributedsched Management Service): 分佈式組件管理框架,相當於跨設備協同的“中間人”,負責管理組件和建立連接

UIAbility: 應用的界面交互核心,管生命週期、用户交互和界面渲染,跨設備協同本質就是兩台設備的UIAbility在“對話”

四、環境準備步驟

工欲善其事,先把環境搭好: 1、硬件:兩台能登錄華為賬號的設備(A和B),需要支持API 18 2、開發工具:DevEco Studio 4.1及以上,public-SDK更新到API 18+ 3、設備連接:用USB線把兩台設備連到PC,打開藍牙讓設備互相識別組網 4、驗證組網:PC端執行shell命令,顯示“remote device num = 1”就是組網成功

hdc shell
hidumper -s 4700 -a "buscenter -l remote_device_info"

也可通過前置步驟的分佈式設備管理,來驗證設備的可信。

五、源碼步驟拆解:

在這裏插入圖片描述

1、 導入核心模塊

首先要導入分佈式服務相關的Kit,不管是發起端還是接收端都需要:

import { abilityConnectionManager, distributedDeviceManager } from '@kit.DistributedServiceKit';
import { common, AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

2、 發現目標設備(設備A側)

設備A要先找到設備B的networkId,作為連接的關鍵參數:

let dmClass: distributedDeviceManager.DeviceManager;

// 初始化設備管理實例
function initDmClass(): void {
  try {
    dmClass = distributedDeviceManager.createDeviceManager('com.example.remotephotodemo');
  } catch (err) {
    hilog.error(0x0000, 'testTag', '創建設備管理實例失敗: ' + JSON.stringify(err));
  }
}

// 獲取設備B的networkId
function getRemoteDeviceId(): string | undefined {
  initDmClass();
  if (!dmClass) return undefined;
  
  hilog.info(0x0000, 'testTag', '開始查找遠程設備');
  const deviceList = dmClass.getAvailableDeviceListSync();
  
  if (!deviceList || deviceList.length === 0) {
    hilog.info(0x0000, 'testTag', '未找到可用設備');
    return undefined;
  }
  
  // 這裏取第一個設備,實際開發可做設備選擇列表
  return deviceList[0].networkId;
}

3、創建會話並連接(兩端操作不同)

設備A(發起端):創建會話+發起連接
@StorageLink('sessionId') sessionId: number = -1;

// 配置設備B的協同信息
const peerInfo: abilityConnectionManager.PeerInfo = {
  deviceId: getRemoteDeviceId()!, // 設備B的networkId
  bundleName: 'com.example.remotephotodemo', // 必須和設備B應用一致
  moduleName: 'entry',
  abilityName: 'EntryAbility',
  serviceName: 'collabTest' // 自定義服務名,兩端要一致
};

// 連接配置
const connectOptions: abilityConnectionManager.ConnectOptions = {
  needSendData: true,
  startOptions: abilityConnectionManager.StartOptionParams.START_IN_FOREGROUND,
  parameters: { "newKey1": "value1" }
};

// 發起連接
async function connectRemoteAbility() {
  const context = this.getUIContext().getHostContext();
  try {
    // 創建會話,獲取sessionId
    this.sessionId = abilityConnectionManager.createAbilityConnectionSession(
      "collabTest", 
      context, 
      peerInfo, 
      connectOptions
    );
    hilog.info(0x0000, 'testTag', `創建會話成功,sessionId: ${this.sessionId}`);
    
    // 發起連接(會拉起設備B的應用)
    const connectResult = await abilityConnectionManager.connect(this.sessionId);
    if (!connectResult.isConnected) {
      hilog.info(0x0000, 'testTag', '連接失敗');
      return;
    }
    hilog.info(0x0000, 'testTag', '連接成功');
  } catch (error) {
    hilog.error(0x0000, 'testTag', `連接異常: ${JSON.stringify(error)}`);
  }
}
設備B(接收端):被拉起後接受連接

設備A發起連接後,設備B的應用會被協同拉起,觸發onCollaborate生命週期函數:

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'testTag', '應用啓動');
  }

  // 協同拉起時觸發
  onCollaborate(wantParam: Record<string, Object>): AbilityConstant.CollaborateResult {
    hilog.info(0x0000, 'testTag', '收到協同請求');
    const collabParam = wantParam["ohos.extra.param.key.supportCollaborateIndex"] as Record<string, Object>;
    this.handleCollaborate(collabParam);
    return 0;
  }

  // 處理協同連接
  private async handleCollaborate(collabParam: Record<string, Object>) {
    const sessionId = this.createSessionFromParam(collabParam);
    if (sessionId === -1) {
      hilog.error(0x0000, 'testTag', '會話創建失敗');
      return;
    }

    // 獲取協同token,必須傳入acceptConnect
    const collabToken = collabParam["ohos.dms.collabToken"] as string;
    try {
      await abilityConnectionManager.acceptConnect(sessionId, collabToken);
      hilog.info(0x0000, 'testTag', '接受連接成功');
      AppStorage.setOrCreate('sessionId', sessionId);
    } catch (error) {
      hilog.error(0x0000, 'testTag', `接受連接失敗: ${JSON.stringify(error)}`);
    }
  }

  // 從協同參數創建會話
  private createSessionFromParam(collabParam: Record<string, Object>): number {
    let sessionId = -1;
    const peerInfo = collabParam["PeerInfo"] as abilityConnectionManager.PeerInfo;
    const connectOptions = collabParam["ConnectOption"] as abilityConnectionManager.ConnectOptions;

    if (!peerInfo || !connectOptions) return sessionId;

    // 配置數據傳輸能力
    connectOptions.needSendData = true;
    connectOptions.needSendStream = true;

    try {
      sessionId = abilityConnectionManager.createAbilityConnectionSession(
        "collabTest", 
        this.context, 
        peerInfo, 
        connectOptions
      );
    } catch (error) {
      hilog.error(0x0000, 'testTag', `創建會話失敗: ${JSON.stringify(error)}`);
    }
    return sessionId;
  }
}

4、 註冊事件監聽(兩端都要加)

連接成功後,通過監聽事件獲取連接狀態和消息:

function registerEventListeners(sessionId: number) {
  // 監聽連接成功事件
  abilityConnectionManager.on("connect", sessionId, (callbackInfo) => {
    hilog.info(0x0000, 'testTag', `會話${callbackInfo.sessionId}連接成功`);
  });

  // 監聽斷開連接事件
  abilityConnectionManager.on("disconnect", sessionId, (callbackInfo) => {
    hilog.info(0x0000, 'testTag', `會話${callbackInfo.sessionId}已斷開`);
  });

  // 監聽接收消息事件
  abilityConnectionManager.on("receiveMessage", sessionId, (callbackInfo) => {
    hilog.info(0x0000, 'testTag', `收到消息: ${callbackInfo.message}, 會話ID: ${callbackInfo.sessionId}`);
    // 這裏可以處理業務邏輯,比如更新UI顯示消息
  });
}

5、 發送消息(兩端都可發)

連接成功後,用sendMessage發送文本信息:

async function sendTestMessage(sessionId: number) {
  try {
    await abilityConnectionManager.sendMessage(sessionId, "這是來自設備A的測試消息");
    hilog.info(0x0000, 'testTag', '消息發送成功');
  } catch (error) {
    hilog.error(0x0000, 'testTag', `消息發送失敗: ${JSON.stringify(error)}`);
  }
}

6、 結束協同(關鍵!避免資源泄露)

業務完成後一定要斷開連接或銷燬會話:

function endCollaboration(sessionId: number) {
  if (sessionId === -1) {
    hilog.info(0x0000, 'testTag', '無效的會話ID');
    return;
  }

  // 短期還需協同:只斷開連接,保留sessionId
  abilityConnectionManager.disconnect(sessionId);
  hilog.info(0x0000, 'testTag', '已斷開連接');

  // 長期不用:銷燬會話(自動斷開連接)
  abilityConnectionManager.destroyAbilityConnectionSession(sessionId);
  hilog.info(0x0000, 'testTag', '已銷燬會話');
}

五、注意事項:

  1. 僅支持API 18及以上版本,且設備必須登錄相同華為賬號
  2. 只有相同bundleName的UIAbility才能協同(比如都是“com.example.remotephotodemo”)
  3. 協同結束後一定要及時關閉,鎖屏或退後台5秒未申請長時任務,協同會被系統強制結束
  4. 傳輸隱私數據時,記得加彈框提醒用户(系統不審查傳輸內容)