You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS端React Native蓝牙扫描异常问题咨询

React Native BLE 扫描问题解决方案

针对你遇到的三个核心问题,逐一分析解决:

1. 无法扫描到手机、耳机等设备

  • 原因:

    • 耳机等设备可能是经典蓝牙而非BLE(低功耗蓝牙),react-native-ble-plx仅支持BLE设备;
    • 手机未开启BLE广播(需在设置中开启蓝牙并允许设备被发现);
    • 权限未配置完整:Android需位置、蓝牙扫描/连接权限;iOS需蓝牙使用权限;
    • 扫描参数限制:startDeviceScan第一个参数传null会过滤部分设备。
  • 解决步骤:

    • 确认目标设备支持BLE并开启可发现模式;
    • 配置平台权限:
      • Android:在AndroidManifest.xml中添加:
        <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
        <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
        <uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
        <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
        
      • iOS:在Info.plist中添加:
        <key>NSBluetoothAlwaysUsageDescription</key>
        <string>需要蓝牙权限连接设备</string>
        <key>NSBluetoothPeripheralUsageDescription</key>
        <string>需要蓝牙权限扫描设备</string>
        
    • 修改扫描调用,将第一个参数改为[]以匹配所有BLE设备。

2. 扫描列表不显示设备名称,仅连接后展示MAC地址

  • 原因:

    • 部分设备扫描时不会主动广播名称,需连接后读取GAP服务的名称特征;
    • iOS上设备id是系统UUID而非MAC,Android上id对应MAC地址;
    • 扫描回调中的device.name可能为空,未做兜底处理。
  • 解决步骤:

    • 连接后主动读取设备名称;
    • 渲染时为空名称添加兜底文本。

3. Mac设备以不同ID重复出现两次

  • 原因:

    • Mac可能开启多个BLE接口,或设备广播不同UUID;
    • 现有去重逻辑仅通过id判断,无法识别同一设备的不同广播标识。
  • 解决步骤:

    • 优化去重逻辑,结合address(Android)或name判断设备唯一性;
    • 扫描时过滤无名称无地址的无效设备。

修改后的完整代码

import React, {useEffect, useState} from 'react';
import {Alert, Button, FlatList, Text, View} from 'react-native';
import {BleManager, Device, ScanMode} from 'react-native-ble-plx';

const ble = new BleManager();
function App() {
  const [devices, setDevices] = useState<any[]>([]);
  const [connected, setConnectedIds] = useState<string[]>([]);

  useEffect(() => {
    ble.onStateChange(state => {
      console.log('BLE State:', state);
    }, true);
  }, []);

  const isDuplicateDevice = (devices: Device[], nextDevice: Device) => {
    if (nextDevice.address) {
      return devices.some(d => d.address === nextDevice.address);
    }
    if (nextDevice.name) {
      return devices.some(d => d.name === nextDevice.name);
    }
    return devices.findIndex(device => nextDevice.id === device.id) > -1;
  };

  const startScane = async () => {
    setDevices([]);
    await ble.startDeviceScan(
      [],
      {scanMode: ScanMode.LowLatency},
      async (_err, device) => {
        if (device && (device.name || device.address)) {
          // @ts-ignore
          delete device._manager;
          setDevices(items => {
            if (!isDuplicateDevice(items, device)) {
              return [...items, device];
            }
            return items;
          });
        }
      },
    );
  };

  const stopScane = async () => {
    await ble.stopDeviceScan();
  };

  const clearAll = async () => {
    setConnectedIds([]);
    setDevices([]);
    await stopScane();
  };

  const handleConnect = async (device: any) => {
    try {
      const connectedDevice = await ble.connectToDevice(device.id);
      await connectedDevice.discoverAllServicesAndCharacteristics();
      
      const gapService = await connectedDevice.serviceForUUID('1800');
      if (gapService) {
        const nameCharacteristic = await gapService.characteristicForUUID('2A00');
        if (nameCharacteristic) {
          const nameValue = await nameCharacteristic.read();
          const deviceName = nameValue.value?.toString();
          if (deviceName) {
            setDevices(prev => prev.map(d => 
              d.id === device.id ? {...d, name: deviceName} : d
            ));
          }
        }
      }

      setConnectedIds(prev => [...prev, device.id]);
      Alert.alert('Device connected');
    } catch (error: any) {
      Alert.alert('Device connection error', error.message);
    }
  };

  const deviceDisconnect = async (device: any) => {
    try {
      await ble.cancelDeviceConnection(device.id);
      setConnectedIds(prev => prev.filter(item => item !== device.id));
      Alert.alert('Device disconnected');
    } catch (error: any) {
      Alert.alert('Device disconnection error', error.message);
    }
  };

  const renderDeviceItem = ({item}: any) => {
    const isConnected = connected.includes(item.id);
    const action = isConnected ? deviceDisconnect : handleConnect;

    return (
      <View
        style={{
          backgroundColor: isConnected ? 'lightgreen' : 'lightgrey',
          marginBottom: 10,
          padding: 10,
          borderRadius: 10,
        }}>
        <Text>Device Name: {item.name || 'Unknown Device'}</Text>
        <Text>Device ID/Address: {item.id || item.address}</Text>
        <Text>Can connect: {item.isConnectable ? 'Yes' : 'No'}</Text>
        {item.isConnectable && (
          <Button
            onPress={() => action(item)}
            title={isConnected ? 'Disconnect' : 'Connect'}
          />
        )}
      </View>
    );
  };

  return (
    <View style={{paddingTop: 50, paddingHorizontal: 10}}>
      <Button title="Scan" onPress={startScane} />
      <Button title={`Stop Scan (${devices.length})`} onPress={stopScane} />
      <Button title={`Clear all`} onPress={clearAll} />
      <FlatList
        style={{maxHeight: 700, marginTop: 10}}
        data={devices}
        keyExtractor={item => item.id}
        renderItem={renderDeviceItem}
      />
    </View>
  );
}

export default App;

内容的提问来源于stack exchange,提问作者Sandeep Kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 13:10:54