【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', '已銷燬會話');
}
五、注意事項:
- 僅支持API 18及以上版本,且設備必須登錄相同華為賬號
- 只有相同bundleName的UIAbility才能協同(比如都是“com.example.remotephotodemo”)
- 協同結束後一定要及時關閉,鎖屏或退後台5秒未申請長時任務,協同會被系統強制結束
- 傳輸隱私數據時,記得加彈框提醒用户(系統不審查傳輸內容)