【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. 传输隐私数据时,记得加弹框提醒用户(系统不审查传输内容)
Logo

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

更多推荐