iOS端React Native蓝牙扫描异常问题咨询
React Native BLE 扫描问题解决方案
针对你遇到的三个核心问题,逐一分析解决:
1. 无法扫描到手机、耳机等设备
原因:
- 耳机等设备可能是经典蓝牙而非BLE(低功耗蓝牙),
react-native-ble-plx仅支持BLE设备; - 手机未开启BLE广播(需在设置中开启蓝牙并允许设备被发现);
- 权限未配置完整:Android需位置、蓝牙扫描/连接权限;iOS需蓝牙使用权限;
- 扫描参数限制:
startDeviceScan第一个参数传null会过滤部分设备。
- 耳机等设备可能是经典蓝牙而非BLE(低功耗蓝牙),
解决步骤:
- 确认目标设备支持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>
- Android:在
- 修改扫描调用,将第一个参数改为
[]以匹配所有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
相关产品推荐
相关产品推荐

