鸿蒙学习实战之路-蓝牙设备配对与连接完全指南

最近好多朋友问我:“西兰花啊,我找到了蓝牙设备,但咋配对连接啊?” 害,这问题可问对人了!上回咱们学会了"找菜"(扫描设备),今天就来学"买菜"(配对设备)和"炒菜"(连接设备)~


🥦 先唠唠配对连接是啥

配对就像给菜称重量、算钱,确认这菜是你的了;连接就像把菜拎回家,可以开始用了。只有完成配对和连接,才能真正和蓝牙设备通信~

第一步:配齐"锅碗瓢盆"(申请权限+导入模块)

1.1 先搞定权限

就像上回说的,用蓝牙得先洗手(申请权限)。在 module.json5 里加上:

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

1.2 导入必要的模块

这次需要的模块更多了,就像炒复杂的菜要准备更多调料:

import {
  connection,
  a2dp,
  hfp,
  hid,
  baseProfile,
  constant,
  common,
} from "@kit.ConnectivityKit";
import { BusinessError } from "@kit.BasicServicesKit";

🥦 西兰花小贴士
这些模块分别对应不同的蓝牙功能:

  • connection:蓝牙连接的核心模块
  • a2dp:音频传输(比如连接音箱)
  • hfp:免提通话(比如连接车载蓝牙)
  • hid:人机交互设备(比如连接蓝牙键盘、鼠标)

第二步:“看秤”(订阅配对状态变化)

配对过程中状态会变来变去,得盯着点,就像买菜时盯着秤一样:

// 定义配对状态变化回调函数
function on配对状态变化(data: connection.BondStateParam) {
  console.info("配对结果: " + JSON.stringify(data));
}

try {
  // 发起订阅 - 相当于盯着秤看
  connection.on("bondStateChange", on配对状态变化);
} catch (err) {
  console.error(
    "订阅失败: " +
      (err as BusinessError).code +
      ", " +
      (err as BusinessError).message
  );
}

🥦 西兰花警告
配对状态有好几种,其中 BOND_STATE_BONDED 才是配对成功的状态哦!

第三步:“付钱”(发起配对)

找到心仪的设备,就得付钱(发起配对)啦。鸿蒙提供了两种配对方式,看你喜欢哪种~

3.1 简单粗暴法(API 20 及以前)

不知道设备地址类型?没关系,用这个简单的方法:

// 通过扫描设备流程获取的设备地址
let 目标设备地址 = "11:22:33:44:55:66";

try {
  // 发起配对 - 相当于付钱拿菜
  connection.pairDevice(目标设备地址).then(
    () => {
      console.info("开始配对");
    },
    (error: BusinessError) => {
      console.error(
        "配对失败: errCode:" + error.code + ", errMessage:" + error.message
      );
    }
  );
} catch (err) {
  console.error(
    "发起配对出错: errCode:" + err.code + ", errMessage:" + err.message
  );
}

3.2 精确控制法(API 21 及以后)

知道设备地址类型?可以用这个更精确的方法:

// 定义设备地址和类型
let 设备信息: common.BluetoothAddress = {
  address: "11:22:33:44:55:66", // 目标设备的MAC地址(实际或虚拟的都行)
  addressType: common.BluetoothAddressType.REAL, // 地址类型:REAL是实际地址,VIRTUAL是虚拟地址
};

try {
  // 发起配对
  connection.pairDevice(设备信息).then(
    () => {
      console.info("开始配对");
    },
    (error: BusinessError) => {
      console.error(
        "配对失败: errCode:" + error.code + ", errMessage:" + error.message
      );
    }
  );
} catch (err) {
  console.error(
    "发起配对出错: errCode:" + err.code + ", errMessage:" + err.message
  );
}

🥦 西兰花小贴士
蓝牙设备的实际 MAC 地址是隐私信息,系统会分配虚拟 MAC 地址。如果不确定,用第一种方法更安全~

第四步:“拎菜回家”(连接设备的 Profile)

配对成功后,得连接设备的 Profile 才能用它的功能。就像买了菜得拎回家,才能做饭~

4.1 先知道设备支持啥 Profile

不同设备支持不同的 Profile(功能),得先看看它有啥:

// 已配对的设备地址
let 已配对设备地址 = "XX:XX:XX:XX:XX:XX";

try {
  // 查询设备支持的Profile
  let 支持的Profile列表 = await connection.getRemoteProfileUuids(
    已配对设备地址
  );
  console.info("设备支持的Profile: " + JSON.stringify(支持的Profile列表));
} catch (err) {
  console.error(
    "查询失败: errCode:" + err.code + ", errMessage:" + err.message
  );
}

4.2 创建 Profile 实例

就像不同的菜要用不同的锅炒,不同的 Profile 要用不同的实例:

// 创建A2DP(音频)、HFP(免提)、HID(人机交互)实例
let a2dp音频实例 = a2dp.createA2dpSrcProfile();
let hfp免提实例 = hfp.createHfpAgProfile();
let hid交互实例 = hid.createHidHostProfile();

4.3 订阅连接状态变化

连接过程中状态也会变,得盯着点:

// 定义A2DP连接状态变化回调
function on音频连接变化(data: baseProfile.StateChangeParam) {
  console.info(`音频连接状态: ${JSON.stringify(data)}`);
}

// 定义HFP连接状态变化回调
function on免提连接变化(data: baseProfile.StateChangeParam) {
  console.info(`免提连接状态: ${JSON.stringify(data)}`);
}

// 定义HID连接状态变化回调
function on交互连接变化(data: baseProfile.StateChangeParam) {
  console.info(`交互设备连接状态: ${JSON.stringify(data)}`);
}

try {
  // 订阅连接状态变化
  a2dp音频实例.on("connectionStateChange", on音频连接变化);
  hfp免提实例.on("connectionStateChange", on免提连接变化);
  hid交互实例.on("connectionStateChange", on交互连接变化);
} catch (err) {
  console.error(
    "订阅连接状态失败: " +
      (err as BusinessError).code +
      ", " +
      (err as BusinessError).message
  );
}

4.4 发起连接

一切准备就绪,可以拎菜回家了:

try {
  // 发起连接支持的Profile
  connection.connectAllowedProfiles(已配对设备地址).then(
    () => {
      console.info("开始连接设备");
    },
    (error: BusinessError) => {
      console.error(
        "连接失败: errCode:" + error.code + ", errMessage:" + error.message
      );
    }
  );
} catch (err) {
  console.error(
    "发起连接出错: errCode:" + err.code + ", errMessage:" + err.message
  );
}

🥦 西兰花警告
配对完成后 30 秒内连接效果最好,别搁太久了!

🥦 给你整个"预制菜"(完整工具类)

为了方便大家使用,我把上面的功能封装成了一个工具类,就像超市里的预制菜,拿回去直接炒就行~

import {
  connection,
  a2dp,
  hfp,
  hid,
  baseProfile,
  constant,
} from "@kit.ConnectivityKit";
import { BusinessError } from "@kit.BasicServicesKit";

export class 设备配对连接管理器 {
  目标设备地址: string = "";
  配对状态: connection.BondState = connection.BondState.BOND_STATE_INVALID;

  // 创建Profile实例
  音频实例 = a2dp.createA2dpSrcProfile();
  免提实例 = hfp.createHfpAgProfile();
  交互实例 = hid.createHidHostProfile();

  // 定义配对状态变化回调函数
  on配对状态变化 = (data: connection.BondStateParam) => {
    console.info("配对结果: " + JSON.stringify(data));
    if (data && data.deviceId == this.目标设备地址) {
      this.配对状态 = data.state; // 保存目标设备的配对状态
    }
  };

  // 发起配对
  public 开始配对(设备地址: string) {
    this.目标设备地址 = 设备地址;

    try {
      // 订阅配对状态变化
      connection.on("bondStateChange", this.on配对状态变化);
    } catch (err) {
      console.error(
        "订阅配对状态失败: " +
          (err as BusinessError).code +
          ", " +
          (err as BusinessError).message
      );
    }

    try {
      // 发起配对
      connection.pairDevice(设备地址).then(
        () => {
          console.info("开始配对");
        },
        (error: BusinessError) => {
          console.error(
            "配对失败: errCode:" + error.code + ", errMessage:" + error.message
          );
        }
      );
    } catch (err) {
      console.error(
        "发起配对出错: errCode:" + err.code + ", errMessage:" + err.message
      );
    }
  }

  // 定义连接状态变化回调
  on音频连接变化 = (data: baseProfile.StateChangeParam) => {
    console.info(`音频连接状态: ${JSON.stringify(data)}`);
  };

  on免提连接变化 = (data: baseProfile.StateChangeParam) => {
    console.info(`免提连接状态: ${JSON.stringify(data)}`);
  };

  on交互连接变化 = (data: baseProfile.StateChangeParam) => {
    console.info(`交互设备连接状态: ${JSON.stringify(data)}`);
  };

  // 发起连接
  public async 开始连接(设备地址: string) {
    try {
      // 查询设备支持的Profile
      let 支持的Profile列表 = await connection.getRemoteProfileUuids(设备地址);
      console.info("设备支持的Profile: " + JSON.stringify(支持的Profile列表));

      let 可用Profile数量 = 0;

      // 检查并订阅A2DP(音频)
      if (
        支持的Profile列表.some(
          (uuid) =>
            uuid == constant.ProfileUuids.PROFILE_UUID_A2DP_SINK.toLowerCase()
        )
      ) {
        console.info("设备支持音频传输");
        可用Profile数量++;
        this.音频实例.on("connectionStateChange", this.on音频连接变化);
      }

      // 检查并订阅HFP(免提)
      if (
        支持的Profile列表.some(
          (uuid) =>
            uuid == constant.ProfileUuids.PROFILE_UUID_HFP_HF.toLowerCase()
        )
      ) {
        console.info("设备支持免提通话");
        可用Profile数量++;
        this.免提实例.on("connectionStateChange", this.on免提连接变化);
      }

      // 检查并订阅HID(人机交互)
      if (
        支持的Profile列表.some(
          (uuid) => uuid == constant.ProfileUuids.PROFILE_UUID_HID.toLowerCase()
        ) ||
        支持的Profile列表.some(
          (uuid) =>
            uuid == constant.ProfileUuids.PROFILE_UUID_HOGP.toLowerCase()
        )
      ) {
        console.info("设备支持人机交互");
        可用Profile数量++;
        this.交互实例.on("connectionStateChange", this.on交互连接变化);
      }

      // 如果有可用的Profile,就发起连接
      if (可用Profile数量 > 0) {
        connection.connectAllowedProfiles(设备地址).then(
          () => {
            console.info("开始连接设备");
          },
          (error: BusinessError) => {
            console.error(
              "连接失败: errCode:" +
                error.code +
                ", errMessage:" +
                error.message
            );
          }
        );
      }
    } catch (err) {
      console.error(
        "连接出错: errCode:" + err.code + ", errMessage:" + err.message
      );
    }
  }
}

// 导出实例,方便全局使用
let 设备配对连接管理器实例 = new 设备配对连接管理器();
export default 设备配对连接管理器实例 as 设备配对连接管理器;

咋用这个工具类呢?

就像炒预制菜一样简单:

import 设备配对连接管理器 from "./PairDeviceManager";

// 开始配对设备
设备配对连接管理器.开始配对("11:22:33:44:55:66");

// 配对成功后,开始连接设备
设备配对连接管理器.开始连接("11:22:33:44:55:66");

是不是超简单?^^

🥦 最后再啰嗦两句

  1. 配对要确认:配对时系统会弹出对话框,得用户同意才行,就像买菜得你点头确认价格一样
  2. Profile 要选对:不同设备支持不同的 Profile,别连错了
  3. 连接要及时:配对后尽快连接,30 秒内效果最好
  4. 状态要监听:配对和连接状态都会变,得盯着点才知道成功没

📚 推荐资料


我是盐焗西兰花,
不教理论,只给你能跑的代码和避坑指南。
下期见!🥦

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐