App点击蓝牙设备列表不显示设备
================

#### 问题现象

进入App内蓝牙设备列表，不显示设备，是什么原因导致的？

#### 背景知识

*   [蓝牙connection模块](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-bluetooth-connection)：connection模块提供了蓝牙设备的配对、连接及状态查询等能力。
*   [connection.on('bluetoothDeviceFind')](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-bluetooth-connection#connectiononbluetoothdevicefind)：订阅蓝牙设备扫描结果上报事件。使用Callback异步回调。可扫描到的设备类型包括传统蓝牙设备和低功耗蓝牙设备。该上报方式只支持获取设备地址信息。
*   [connection.getRemoteDeviceName](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-bluetooth-connection#connectiongetremotedevicename)：获取对端蓝牙设备的名称。
*   [ForEach](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-rendering-control-foreach)：ForEach接口基于数组类型数据来进行循环渲染。
*   [List](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-service-widget-container-list)：列表包含一系列相同宽度的列表项。适合连续、多行呈现同类数据，例如图片和文本。
*   使用蓝牙功能前，需要申请权限ohos.permission.ACCESS\_BLUETOOTH。如何申请蓝牙权限，具体操作请参考[声明权限](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/declare-permissions)。

#### 问题定位

1.  检查从系统设置里是否能蓝牙搜索到附近设备：系统设置中可搜到附近设备，排除硬件问题。
2.  检查是否配置权限接入了ohos.permission.ACCESS\_BLUETOOTH：观察hilog日志，触发蓝牙扫描时未触发permission相关报错，并且返回了相关地址信息，说明蓝牙功能是可用的。
3.  检查触发蓝牙扫描时的hilog信息，蓝牙扫描时的hilog结果（截取部分）：
    
    (operator():81)device: D0:1E:\*\*:\*\*:\*\*:8D, len: 31
    
    可见进入App蓝牙设备列表后有获取到附近设备的地址信息，但并未有相关设备名。
    

#### 分析结论

根据问题定位现象可知，App能正常获取到地址信息。未能在App蓝牙设备页面显示应用信息，可能原因有：

1.  缺少了通过设备地址获取设备名称的步骤。
2.  UI设计上缺少了列表显示设备信息的组件。

#### 修改建议

1.  在[module.json5配置文件](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/module-configuration-file)的requestPermissions标签中声明权限ohos.permission.ACCESS\_BLUETOOTH。
2.  在使用connection.on('bluetoothDeviceFind')获取蓝牙设备相关信息后，通过connection.getRemoteDeviceName获取蓝牙设备的名称。示例代码如下：
    
    connection.on('bluetoothDeviceFind', (data: string\[\]) => {
      console.info(\`data: ${JSON.stringify(data)} ${connection.getRemoteDeviceName(data\[0\])}\`);
      if (!this.findList.find((item: Device) => item.deviceId === data\[0\])) {
        this.findList.push({
          name: connection.getRemoteDeviceName(data\[0\]),
          deviceId: data\[0\]
        });
      };
    });
    
3.  使用ForEach结合List实现列表显示设备信息的组件。示例代码如下：
    
    List() {
      ForEach(this.findList, (item: Device) => {
        ListItem() {
          Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
            Column() {
              Text(\`设备名称：${item?.name}\`);
              Text(\`设备id：${item?.deviceId}\`).fontColor('#ffcbcbcb').fontSize(12);
            }
            .alignItems(HorizontalAlign.Start);
    
    
            Row({ space: 5 }) {
              Button('链接').width(60).height(20).fontSize(12).onClick(() => {
                try {
               _   // 实际的地址可由扫描流程获取_
                  connection.pairDevice(item.deviceId, () => {
                    this.getUIContext().getPromptAction().showToast({
                      message: '配对成功'
                    });
                  });
                } catch (err) {
                  console.error(\`errCode: ${(err as BusinessError).code}, errMessage: ${(err as BusinessError).message}\`);
                };
              });
            };
          }
          .height(50)
          .backgroundColor('#fff')
          .width('100%')
          .borderRadius(10)
          .padding(6);
        }.padding(4);
      });
    }
    .height(200)
    .width('100%')
    .flexGrow(1)
    .flexShrink(1)
    .flexBasis(1);
    
4.  完整示例代码如下：
    
    import connection from '@ohos.bluetooth.connection';
    import { BusinessError } from '@ohos.base';
    import { PromptAction } from '@kit.ArkUI';
    import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
    import common from '@ohos.app.ability.common';
    
    
    interface Device {
      name?: string
      deviceId?: string
    };
    
    
    @Entry
    @Component
    struct Index {
      _// 正确写法（Stage模型）_
      private context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext;
      promptAction: PromptAction = this.getUIContext().getPromptAction();
    
    
      @State findList: Device\[\] = \[\];
    
    
      aboutToAppear(): void {
        const permissions: Array<Permissions> = \['ohos.permission.ACCESS\_BLUETOOTH'\];
        let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
        atManager.requestPermissionsFromUser(this.context, permissions).then(() => {
       _   // 授权成功_
        }).catch((err: BusinessError) => {
          console.error(\`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}\`);
        });
      };
    
    
      build() {
        Flex({ direction: FlexDirection.Column }) {
          Row() {
            Button('扫描蓝牙').onClick(() => {
             _ // 开启扫描_
              try {
                connection.startBluetoothDiscovery();
                console.info('startBleScan success');
                this.getUIContext().getPromptAction().showToast({
                  message: '已打开'
                });
              } catch (err) {
                this.getUIContext().getPromptAction().showToast({
                  message: '蓝牙开关未打开'
                });
              };
    
    
            _  // 接收扫描结果_
              connection.on('bluetoothDeviceFind', (data: string\[\]) => {
                console.info(\`data: ${JSON.stringify(data)} ${connection.getRemoteDeviceName(data\[0\])}\`);
                if (!this.findList.find((item: Device) => item.deviceId === data\[0\])) {
                  this.findList.push({
                    name: connection.getRemoteDeviceName(data\[0\]),
                    deviceId: data\[0\]
                  });
                };
              });
            });
    
    
            Button('停止扫描').onClick(() => {
              try {
            _    // 关闭扫描_
                connection.stopBluetoothDiscovery();
                console.info('stopBleScan success');
                this.getUIContext().getPromptAction().showToast({
                  message: '停止扫描'
                });
              } catch (err) {
                this.getUIContext().getPromptAction().showToast({
                  message: '蓝牙已关闭'
                });
              };
            });
    
    
            Button('清空').onClick(() => {
              this.findList = \[\];
            });
          };
          List() {
            ForEach(this.findList, (item: Device) => {
              ListItem() {
                Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
                  Column() {
                    Text(\`设备名称：${item?.name}\`);
                    Text(\`设备id：${item?.deviceId}\`).fontColor('#ffcbcbcb').fontSize(12);
                  }
                  .alignItems(HorizontalAlign.Start);
    
    
                  Row({ space: 5 }) {
                    Button('链接').width(60).height(20).fontSize(12).onClick(() => {
                      try {
                      _  // 实际的地址可由扫描流程获取_
                        connection.pairDevice(item.deviceId, () => {
                          this.getUIContext().getPromptAction().showToast({
                            message: '配对成功'
                          });
                        });
                      } catch (err) {
                        console.error(\`errCode: ${(err as BusinessError).code}, errMessage: ${(err as BusinessError).message}\`);
                      };
                    });
                  };
                }
                .height(50)
                .backgroundColor('#fff')
                .width('100%')
                .borderRadius(10)
                .padding(6);
              }.padding(4);
            });
          }
          .height(200)
          .width('100%')
          .flexGrow(1)
          .flexShrink(1)
          .flexBasis(1);
        }.padding(10).width('100%').height('100%').backgroundColor('#ffcdcdcd');
      };
    };