文档管理中心

如何实现BLE蓝牙快速回连

问题现象

如果应用已经与对端蓝牙设备建立过BLE蓝牙连接,那么如何不通过蓝牙扫描,就能够与之前已连接过的蓝牙设备快速建立连接?

背景知识

  • 在HarmonyOS中,若设备A与B没有建立配对,则它们的蓝牙虚拟MAC地址会随着开关机等操作发生改变。若设备A与设备B后建立了蓝牙配对,则它们的蓝牙虚拟MAC地址会被固化,即使设备开关机,蓝牙虚拟MAC地址也不会发生改变。
  • API16及后续版本,HarmonyOS提供了无需配对就可将蓝牙虚拟MAC地址固化的API接口:addPersistentDeviceId。由于应用通过蓝牙扫描获取到未配对或未固化的设备MAC地址是虚拟随机的,若想该虚拟随机地址不发生改变,可以调用addPersistentDeviceId接口持久化存储虚拟随机地址。注意
    1. 使用蓝牙虚拟MAC地址固化API需要使用到ohos.permission.PERSISTENT_BLUETOOTH_PEERS_MAC权限,且需要ACL权限申请通过后方可使用。
    2. 使用该接口时,开发者应明确该虚拟随机地址对应的对端蓝牙设备真实地址是不变的,若对端设备地址发生变化,持久化保存的地址信息也会失效,无法继续使用。

解决方案

  • 方案一:与对端设备第一次连接后,发起蓝牙配对,配对完成后,蓝牙虚拟MAC地址会被固化。后续无需重复建立蓝牙扫描,调用getPairedDevices接口就可以从已配对列表中获取对端蓝牙的虚拟MAC地址,向对端设备发起连接。
  • 方案二:与对端设备第一次连接后,使用addPersistentDeviceId接口固化对端设备虚拟MAC地址。后续同样无需重复建立蓝牙扫描,调用getPersistentDeviceIds接口就可以从已固化设备列表中获取对端蓝牙的虚拟MAC地址,向对端设备发起连接。

方案一、方案二完整代码如下:

收起
自动换行
深色代码主题
复制
  1. import { access, ble, connection, constant } from '@kit.ConnectivityKit';
  2. @Entry
  3. @Component
  4. struct Reconnect {
  5. @State gattClient: ble.GattClientDevice | undefined = undefined;
  6. // 方案一
  7. ReconnectOne() {
  8. // 先查询已配对设备列表,判断需要连接的设备是否在已配对设备列表中存在。
  9. let devices = connection.getPairedDevices();
  10. for (let index = 0; index < devices.length; index++) {
  11. let name = connection.getRemoteDeviceName(devices[index]);
  12. // 需要连接的设备在已配对设备列表中存在,无需发起扫描,直接创建实例进行连接。
  13. if (name === 'name') {
  14. // 创建ble蓝牙client实例
  15. this.gattClient = ble.createGattClientDevice(devices[index]);
  16. // 连接前先创建连接状态回调监听
  17. this.onBLEConnectionStateChangeOne();
  18. // 连接ble蓝牙
  19. this.gattClient.connect();
  20. return;
  21. }
  22. }
  23. // 需要连接的设备在已配对设备列表中不存在,开启常规ble蓝牙连接流程。
  24. // 订阅BLE设备发现
  25. this.onBLEDeviceFindOne();
  26. // 过滤参数可根据实际场景进行设置
  27. let scanFilter: ble.ScanFilter = {
  28. name: 'name'
  29. };
  30. // 扫描参数可根据实际场景配置
  31. let scanOptions: ble.ScanOptions = {
  32. interval: 500,
  33. dutyMode: ble.ScanDuty.SCAN_MODE_LOW_POWER,
  34. matchMode: ble.MatchMode.MATCH_MODE_AGGRESSIVE,
  35. };
  36. ble.startBLEScan([scanFilter], scanOptions);
  37. }
  38. onBLEDeviceFindOne() {
  39. ble.on('BLEDeviceFind', (data: Array<ble.ScanResult>) => {
  40. // 发现设备,创建实例进行连接。
  41. this.gattClient = ble.createGattClientDevice(data[0].deviceId);
  42. // 连接前先创建连接状态回调监听
  43. this.onBLEConnectionStateChangeOne();
  44. // 连接ble蓝牙
  45. this.gattClient.connect();
  46. // 连接成后,将已连接设备加入配对列表。
  47. // 注意:加入配对列表时机并不固定,可根据实际场景来进行变更。
  48. connection.pairDevice(data[0].deviceId, () => {
  49. console.info('pairDevice err');
  50. });
  51. });
  52. }
  53. onBLEConnectionStateChangeOne() {
  54. this.gattClient?.on('BLEConnectionStateChange', (state: ble.BLEConnectionChangeState) => {
  55. if (state.state === constant.ProfileConnectionState.STATE_DISCONNECTED) {
  56. // 如果蓝牙意外断开了连接,可以在此处重新发起连接,以达到意外断连快速回连能力。
  57. // 不过需要保证此时this.gattClient实例没有被销毁。
  58. this.gattClient?.connect();
  59. }
  60. });
  61. }
  62. // 方案二
  63. ReconnectTwo() {
  64. // 先查询已固化设备列表,判断需要连接的设备是否在已固化设备列表中存在。
  65. let devices = access.getPersistentDeviceIds();
  66. for (let index = 0; index < devices.length; index++) {
  67. let name = connection.getRemoteDeviceName(devices[index]);
  68. let isValid = access.isValidRandomDeviceId(devices[index]);
  69. // 需要连接的设备在已固化设备列表中存在,同时该地址有效,无需发起扫描,直接创建实例进行连接。
  70. if (isValid && name === 'name') {
  71. // 创建ble蓝牙client实例
  72. this.gattClient = ble.createGattClientDevice(devices[index]);
  73. // 连接前先创建连接状态回调监听
  74. this.onBLEConnectionStateChangeTwo();
  75. // 连接ble蓝牙
  76. this.gattClient.connect();
  77. return;
  78. }
  79. }
  80. // 需要连接的设备在已固化设备列表中不存在,开启常规ble蓝牙连接流程。
  81. // 订阅BLE设备发现
  82. this.onBLEDeviceFindTwo();
  83. // 过滤参数可根据实际场景进行设置
  84. let scanFilter: ble.ScanFilter = {
  85. name: 'name'
  86. };
  87. // 扫描参数可根据实际场景配置
  88. let scanOptions: ble.ScanOptions = {
  89. interval: 500,
  90. dutyMode: ble.ScanDuty.SCAN_MODE_LOW_POWER,
  91. matchMode: ble.MatchMode.MATCH_MODE_AGGRESSIVE,
  92. };
  93. ble.startBLEScan([scanFilter], scanOptions);
  94. }
  95. onBLEDeviceFindTwo() {
  96. ble.on('BLEDeviceFind', (data: Array<ble.ScanResult>) => {
  97. // 发现设备,创建实例进行连接。
  98. this.gattClient = ble.createGattClientDevice(data[0].deviceId);
  99. // 连接前先创建连接状态回调监听
  100. this.onBLEConnectionStateChangeTwo();
  101. // 连接ble蓝牙
  102. this.gattClient.connect();
  103. // 连接成后,将已连接设备加入固化列表。
  104. // 注意:加入固化列表时机并不固定,可根据实际场景来进行变更。
  105. access.addPersistentDeviceId(data[0].deviceId);
  106. });
  107. }
  108. onBLEConnectionStateChangeTwo() {
  109. this.gattClient?.on('BLEConnectionStateChange', (state: ble.BLEConnectionChangeState) => {
  110. if (state.state === constant.ProfileConnectionState.STATE_DISCONNECTED) {
  111. // 如果蓝牙意外断开了连接,可以在此处重新发起连接,以达到意外断连快速回连能力。
  112. // 不过需要保证此时this.gattClient实例没有被销毁。
  113. this.gattClient?.connect();
  114. }
  115. });
  116. }
  117. build() {
  118. Column() {
  119. Button('ble蓝牙连接/支持快速回连(方案1)').onClick(() => {
  120. // 发起连接
  121. this.ReconnectOne();
  122. });
  123. Button('ble蓝牙连接/支持快速回连(方案2)').onClick(() => {
  124. // 发起连接
  125. this.ReconnectTwo();
  126. });
  127. }.height('100%')
  128. .width('100%')
  129. .justifyContent(FlexAlign.SpaceAround);
  130. }
  131. }
说明

由于方案二需要使用受限开放的ohos.permission.PERSISTENT_BLUETOOTH_PEERS_MAC权限,一般建议使用方案一。若业务中不能包含配对场景,且可以成功申请ohos.permission.PERSISTENT_BLUETOOTH_PEERS_MAC权限,就可以采取方案二。

常见FAQ

Q:由于担心蓝牙虚拟MAC地址变化,需要在重新连接前一直扫描,会造成资源消耗,应该如何解决?

A:可以使用addPersistentDeviceId持久化蓝牙地址,就不需要在重连前一直扫描。

Q:使用addPersistentDeviceId持久化后,是否即使蓝牙设备关机重启,只要蓝牙物理MAC地址不变,被持久化的虚拟MAC地址也不会发生改变?

A:是的,只要对端蓝牙设备真实地址保持不变,那么被addPersistentDeviceId接口持久化的虚拟MAC地址就不会发生改变。

Q:使用addPersistentDeviceId固化蓝牙虚拟MAC地址后,调用deletePersistentDeviceId删除该地址,再次查询固化列表时发现被删除的地址仍然存在,如何解决?

A:该问题出现在调试阶段,由于应用前后两次签名发生变化,系统未能识别差异,导致固化地址无法正常删除。在正式版本中不会出现此问题。若在调试阶段遇到此问题,可通过"设置>系统>重置>还原网络设置"清除相关数据解决。

在 FAQ 中进行搜索
请输入您想要搜索的关键词