如何解决存在较大量的蓝牙设备时-低功耗蓝牙连接效率低问题
============================

#### 问题现象

存在较大量的蓝牙设备时（车库场景），低功耗蓝牙连接效率低。

#### 背景知识

[低功耗蓝牙](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/bluetooth-ble)是从蓝牙4.0起支持的协议，与[传统蓝牙](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/bluetooth-br)相比，功耗极低、传输速度更快，但传输数据量较小。常用在对续航要求较高且只需小数据量传输的各种智能电子产品中，比如智能穿戴设备、智能家电、传感器等，应用场景广泛。

*   当存在大量蓝牙设备时，广播信道中会充斥大量扫描报文，并且会占据空口资源，导致连接效率低；扫描端使用ScanFilter过滤参数，扫描端设备链路层只处理位于过滤参数白名单中的设备的广播数据，减少扫描报文的发送，减少空口压力，同时减少扫描设备内部控制器和主机间的交互通信，降低芯片功耗，提高连接效率。
*   可根据不同的场景选择合适的扫描模式[ScanDuty](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-bluetooth-ble#scanduty)，如需提高扫描发现效率，可以选择SCAN\_MODE\_LOW\_LATENCY扫描模式。

#### 解决方案

*   **方案一**：
    
    在BLE扫描距离范围内扫描时，可以通过扫描过滤参数[ScanFilter](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-bluetooth-ble#scanfilter)来过滤出目标设备，如设备的随机MAC地址、设备名称、serviceUUID等，可以快速找到目标设备。如果已经连接过，可以将随机MAC地址或deviceName保存起来，下次可以直接用保存的信息去过滤扫描。
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/5e/v3/bPdQTt35SwiPqOe-hjGNjQ/note_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260709T134215Z&HW-CC-Expire=86400&HW-CC-Sign=88205A075C3DC88E58420CFA9AB9FA47163E484D0CE98F6168B6D539342433EA)
    
    该随机MAC在如下情况会改变：已配对设备取消配对后重新扫描或蓝牙服务下电时，该随机地址会变更，若和该设备地址配对成功后，该地址不会变更，参考[ble.getConnectedBLEDevices](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-bluetooth-ble#blegetconnectedbledevices)。
    
    使用name过滤发起BLE扫描的代码。
    
    _// 开始扫描_
    async startScan(): Promise<void> {
      let grantStatus: boolean = await this.checkPermissionGrant('ohos.permission.ACCESS\_BLUETOOTH') ===
      abilityAccessCtrl.GrantStatus.PERMISSION\_GRANTED;
      if (!grantStatus) {
        this.promptAction.showToast({
          message: '请先申请蓝牙权限',
          duration: 2000
        });
        return;
      }
    
      if (this.isScanning) {
        return;
      }
    
      try {
      _  // 清空之前的结果_
        this.deviceList = \[\];
    
        let scanFilter: ble.ScanFilter = {
          name: 'XXXXXXXX'
        };
      _  // Setting Scan Parameters_
        let scanOptions: ble.ScanOptions = {
          interval: 100,
          dutyMode: ble.ScanDuty.SCAN\_MODE\_LOW\_LATENCY,
          matchMode: ble.MatchMode.MATCH\_MODE\_AGGRESSIVE
        };
    
        ble.on('BLEDeviceFind', (data) => {
          console.info('testTag', \`scan result = ${data\[0\].deviceId}\`);
          console.info('BLEScannerDemo: 扫描结果:', JSON.stringify(data));
          this.scanCallback(data);
        });
    
       _ // 开始扫描_
        await ble.startBLEScan(\[scanFilter\], scanOptions);
        console.info('BLEScannerDemo: 开始扫描');
    
        this.isScanning = true;
        this.promptAction.showToast({
          message: '开始扫描BLE设备',
          duration: 2000
        });
    
        console.info('BLE扫描已启动');
    
      _  // 30秒后自动停止扫描（节省电量）_
        setTimeout(() => {
          if (this.isScanning) {
            this.stopScan();
          }
        }, 30000);
    
      } catch (error) {
        const err: BusinessError = error as BusinessError;
        console.error('开始扫描失败:', err.code, err.message);
        this.promptAction.showToast({
          message: \`扫描失败: ${err.message}\`,
          duration: 3000
        });
      }
    }
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/a1/v3/v5b53tdQQg67ZaYWBrTLTQ/note_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260709T134215Z&HW-CC-Expire=86400&HW-CC-Sign=417BE3B9A45A2FA5459E8BA5AA1870288386224ACA31C6712641A390F06C4A5A)
    
    使用蓝牙能力时，需要在module.json5中添加ohos.permission.ACCESS\_BLUETOOTH权限。
    
    完整示例参考如下：
    
    import { ble } from '@kit.ConnectivityKit';
    import { BusinessError } from '@kit.BasicServicesKit';
    import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';
    import { PromptAction } from '@kit.ArkUI';
    import { JSON } from '@kit.ArkTS';
    import { bundleManager } from '@kit.AbilityKit';
    
    _// BLE设备信息接口_
    interface BLEDeviceInfo {
      deviceId: string;
      deviceName: string;
      rssi: number;
      manufactureData?: ArrayBuffer;
      serviceUuids?: Array<string>;
    }
    
    @Entry
    @Component
    struct BLEScannerDemo {
     _ // 扫描状态_
      @State isScanning: boolean = false;
      _// 扫描结果列表_
      @State deviceList: Array<BLEDeviceInfo> = \[\];
      _// 权限状态_
      context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext;
      uiContext: UIContext = this.getUIContext();
      promptAction: PromptAction = this.uiContext.getPromptAction();
    
      _// BLE扫描回调_
      scanCallback(data: Array<ble.ScanResult>) {
     _   // 扫描到设备时触发_
        data.forEach(device => {
          console.info(\`发现设备: ${device.deviceId}, RSSI: ${device.rssi}\`);
    
         _ // 检查是否已存在该设备_
          const existingIndex = this.deviceList.findIndex(item => item.deviceId === device.deviceId);
    
          if (existingIndex !== -1) {
           _ // 更新已有设备信息_
            this.deviceList\[existingIndex\] = {
              deviceId: device.deviceId,
              deviceName: device.deviceName || '未知设备',
              rssi: device.rssi,
    
            };
          } else {
         _   // 添加新设备_
            const newDevice: BLEDeviceInfo = {
              deviceId: device.deviceId,
              deviceName: device.deviceName || '未知设备',
              rssi: device.rssi,
            };
            this.deviceList.push(newDevice);
          }
    
        _  // 触发UI更新_
          this.deviceList = \[...this.deviceList\];
    
        });
    
      }
    
      _// 页面显示时初始化_
      aboutToAppear(): void {
        const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
        atManager.requestPermissionsFromUser(this.context, \['ohos.permission.ACCESS\_BLUETOOTH'\], (err: BusinessError) => {
          if (err) {
            console.error('权限申请失败');
          } else {
            console.info('权限申请成功');
          }
        });
      }
    
     _ //校验权限_
      async checkPermissionGrant(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
        let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
        let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION\_DENIED;
    
       _ // 获取应用程序的accessTokenID。_
        let tokenId: number = 0;
        try {
          let bundleInfo: bundleManager.BundleInfo =
            await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET\_BUNDLE\_INFO\_WITH\_APPLICATION);
          let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
          tokenId = appInfo.accessTokenId;
        } catch (error) {
          const err: BusinessError = error as BusinessError;
          console.error(\`Failed to get bundle info for self, code: ${err.code}, message: ${err.message}\`);
        }
    
      _  // 校验应用是否被授予权限。_
        try {
          grantStatus = await atManager.checkAccessToken(tokenId, permission);
        } catch (error) {
          const err: BusinessError = error as BusinessError;
          console.error(\`Failed to check access token, code: ${err.code}, message: ${err.message}\`);
        }
    
        return grantStatus;
      }
    
     _ // 开始扫描_
      async startScan(): Promise<void> {
        let grantStatus: boolean = await this.checkPermissionGrant('ohos.permission.ACCESS\_BLUETOOTH') ===
        abilityAccessCtrl.GrantStatus.PERMISSION\_GRANTED;
        if (!grantStatus) {
          this.promptAction.showToast({
            message: '请先申请蓝牙权限',
            duration: 2000
          });
          return;
        }
    
        if (this.isScanning) {
          return;
        }
    
        try {
       _   // 清空之前的结果_
          this.deviceList = \[\];
    
          let scanFilter: ble.ScanFilter = {
            name: 'XXXXXXXX'
          };
         _ // Setting Scan Parameters_
          let scanOptions: ble.ScanOptions = {
            interval: 100,
            dutyMode: ble.ScanDuty.SCAN\_MODE\_LOW\_LATENCY,
            matchMode: ble.MatchMode.MATCH\_MODE\_AGGRESSIVE
          };
    
          ble.on('BLEDeviceFind', (data) => {
            console.info('testTag', \`scan result = ${data\[0\].deviceId}\`);
            console.info('BLEScannerDemo: 扫描结果:', JSON.stringify(data));
            this.scanCallback(data);
          });
    
       _   // 开始扫描_
          await ble.startBLEScan(\[scanFilter\], scanOptions);
          console.info('BLEScannerDemo: 开始扫描');
    
          this.isScanning = true;
          this.promptAction.showToast({
            message: '开始扫描BLE设备',
            duration: 2000
          });
    
          console.info('BLE扫描已启动');
    
      _    // 30秒后自动停止扫描（节省电量）_
          setTimeout(() => {
            if (this.isScanning) {
              this.stopScan();
            }
          }, 30000);
    
        } catch (error) {
          const err: BusinessError = error as BusinessError;
          console.error('开始扫描失败:', err.code, err.message);
          this.promptAction.showToast({
            message: \`扫描失败: ${err.message}\`,
            duration: 3000
          });
        }
      }
    
     _ // 停止扫描_
      async stopScan(): Promise<void> {
        if (!this.isScanning) {
          return;
        }
    
        try {
    
          await ble.stopBLEScan();
          this.isScanning = false;
          console.info('BLEScannerDemo: 停止扫描');
          this.promptAction.showToast({
            message: \`扫描结束，发现 ${this.deviceList.length} 个设备\`,
            duration: 3000
          });
          console.info('BLE扫描已停止');
          ble.off('BLEDeviceFind');
        } catch (error) {
          const err: BusinessError = error as BusinessError;
          console.error('停止扫描失败:', err.code, err.message);
          this.promptAction.showToast({
            message: \`停止扫描失败: ${err.message}\`,
            duration: 3000
          });
        }
      }
    
     _ // 清空结果_
      clearResults(): void {
        this.deviceList = \[\];
        this.promptAction.showToast({
          message: '已清空扫描结果',
          duration: 2000
        });
      }
    
     _ // 格式化设备信息用于显示_
      formatDeviceInfo(device: BLEDeviceInfo): string {
        let info = \`名称: ${device.deviceName}\\n\`;
        info += \`ID: ${device.deviceId}\\n\`;
        info += \`信号强度: ${device.rssi} dBm\`;
    
        if (device.serviceUuids && device.serviceUuids.length > 0) {
          info += \`\\n服务UUID: ${device.serviceUuids.length} 个\`;
        }
    
        return info;
      }
    
      build() {
        Column({ space: 20 }) {
        _  // 标题_
          Text('BLE设备扫描器')
            .fontSize(24)
            .fontWeight(FontWeight.Bold)
            .margin({ top: 20 })
    
       _   // 扫描状态显示_
          Row({ space: 10 }) {
            Text('扫描状态:')
              .fontSize(16)
            Text(this.isScanning ? '扫描中...' : '已停止')
              .fontSize(16)
              .fontColor(this.isScanning ? Color.Green : Color.Gray)
          }
          .margin({ top: 10 })
    
         _ // 控制按钮_
          Row({ space: 20 }) {
            Button(this.isScanning ? '停止扫描' : '开始扫描')
              .backgroundColor(this.isScanning ? Color.Orange : Color.Blue)
              .fontColor(Color.White)
              .padding(10)
              .borderRadius(8)
              .onClick(() => {
                if (this.isScanning) {
                  this.stopScan();
                } else {
                  this.startScan();
                }
              })
    
            Button('清空结果')
              .backgroundColor(Color.Gray)
              .fontColor(Color.White)
              .padding(10)
              .borderRadius(8)
              .onClick(() => this.clearResults())
              .enabled(this.deviceList.length > 0 && !this.isScanning)
          }
          .margin({ top: 20 })
    
         _ // 设备数量统计_
          Text(\`发现设备: ${this.deviceList.length} 个\`)
            .fontSize(16)
            .fontColor(Color.Blue)
            .margin({ top: 10 })
    
         _ // 设备列表_
          if (this.deviceList.length > 0) {
            List({ space: 10 }) {
              ForEach(this.deviceList, (device: BLEDeviceInfo) => {
                ListItem() {
                  Column({ space: 8 }) {
                 _   // 设备名称和信号强度_
                    Row() {
                      Text(device.deviceName)
                        .fontSize(18)
                        .fontWeight(FontWeight.Bold)
                        .layoutWeight(1)
    
                   _   // 信号强度指示_
                      Text(\`${device.rssi} dBm\`)
                        .fontSize(14)
                        .fontColor(this.getRSSIColor(device.rssi))
                    }
                    .width('100%')
    
                  _  // 设备ID_
                    Text(\`ID: ${device.deviceId}\`)
                      .fontSize(12)
                      .fontColor(Color.Gray)
                      .width('100%')
                      .textOverflow({ overflow: TextOverflow.Ellipsis })
    
                  _  // 附加信息_
                    if (device.manufactureData || device.serviceUuids) {
                      Text(this.getAdditionalInfo(device))
                        .fontSize(12)
                        .fontColor(Color.Green)
                        .width('100%')
                    }
                  }
                  .padding(12)
                  .backgroundColor(Color.White)
                  .borderRadius(8)
                  .shadow({
                    radius: 2,
                    color: Color.Gray,
                    offsetX: 1,
                    offsetY: 1
                  })
                  .margin({ top: 5, bottom: 5 })
                }
              }, (device: BLEDeviceInfo) => device.deviceId)
            }
            .width('100%')
            .height('60%')
            .margin({ top: 10 })
            .divider({
              strokeWidth: 1,
              color: Color.Gray,
              startMargin: 10,
              endMargin: 10
            })
          } else {
         _   // 空状态提示_
            Column({ space: 20 }) {
    
              Text(this.isScanning ? '正在搜索附近的BLE设备...' : '点击"开始扫描"搜索BLE设备')
                .fontSize(16)
                .fontColor(Color.Gray)
                .textAlign(TextAlign.Center)
            }
            .height('60%')
            .justifyContent(FlexAlign.Center)
            .margin({ top: 40 })
          }
        }
        .width('100%')
        .height('100%')
        .padding(16)
        .backgroundColor(Color.White)
      }
    
     _ // 根据RSSI值获取颜色_
      private getRSSIColor(rssi: number): ResourceColor {
        if (rssi >= -50) {
          return Color.Green;
        }
        if (rssi >= -70) {
          return Color.Blue;
        }
        if (rssi >= -85) {
          return Color.Orange;
        }
        return Color.Red;
      }
    
      _// 获取附加信息_
      private getAdditionalInfo(device: BLEDeviceInfo): string {
        const info: string\[\] = \[\];
    
        if (device.manufactureData) {
          info.push('有制造商数据');
        }
    
        if (device.serviceUuids && device.serviceUuids.length > 0) {
          info.push(\`${device.serviceUuids.length}个服务\`);
        }
    
        return info.join(' | ');
      }
    
     _ // 页面销毁时停止扫描_
      aboutToDisappear(): void {
        if (this.isScanning) {
          this.stopScan();
        }
      }
    }
    
*   **方案二**：
    
    [ble.startBLEScan](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-bluetooth-ble#blestartblescan)发起BLE扫描流程时，可以选择SCAN\_MODE\_LOW\_LATENCY扫描模式，减少蓝牙扫描的时间，提高扫描发现效率。
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/53/v3/tpGVTuDFStK00MWXdT-YLQ/zh-cn_image_0000002658851753.png?HW-CC-KV=V1&HW-CC-Date=20260709T134215Z&HW-CC-Expire=86400&HW-CC-Sign=C1BA1F2B1BAACCE008A98B89EB9CB1C7155C8CB7898DA3BFAA11F41233B67969 "点击放大")
    

#### 总结

1.  低功耗蓝牙建连过程，对端BLE设备发广播的强度，本端设备和对端BLE设备的距离都可能造成蓝牙连接失败，需要保持在连接范围内。
2.  首次蓝牙连接后，可保存MAC地址或设备名等信息作为ScanFilter参数，提高连接效率。

#### 常见FAQ

Q：ScanFilter指的是结果中被过滤掉（不包含）的部分，还是过滤出（仅包含）的意思？

A：是过滤出（仅包含）的意思。

Q：蓝牙扫描怎么取消过滤条件？

A：将startBLEScan过滤条件传入为null。