1. React Native蓝牙通信基础概念
在移动应用开发中,蓝牙通信是一个常见需求。React Native作为跨平台框架,提供了与原生蓝牙模块交互的能力。理解其工作原理是发送蓝牙命令的前提。
蓝牙通信主要分为经典蓝牙(Bluetooth Classic)和低功耗蓝牙(Bluetooth Low Energy, BLE)两种模式。React Native应用通常需要与BLE设备交互,因为BLE更省电且适合物联网设备。核心概念包括:
- GATT(Generic Attribute Profile):BLE设备通信的基础协议
- Service:设备提供的功能集合,每个服务有唯一UUID
- Characteristic:服务中的具体数据点,可读写或通知
- Descriptor:特性的附加描述信息
在React Native中,我们主要通过以下步骤实现蓝牙通信:
- 扫描并发现周边设备
- 连接目标设备
- 发现设备服务与特征
- 读写特征值或订阅通知
提示:Android和iOS的蓝牙权限处理方式不同,需要分别配置。Android需要位置权限才能扫描BLE设备,这是容易被忽略的点。
2. 环境准备与权限配置
2.1 安装必要的库
React Native本身不包含蓝牙模块,需要借助第三方库。目前最成熟的选择是:
npm install react-native-ble-plx # 或 yarn add react-native-ble-plx安装后需要链接原生模块:
npx react-native link react-native-ble-plx对于较新的React Native版本(0.60+),可能需要手动配置:
cd ios && pod install2.2 平台特定配置
Android配置:
- 在
AndroidManifest.xml中添加权限:
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>- 在
build.gradle中确保minSdkVersion ≥ 21:
defaultConfig { minSdkVersion 21 targetSdkVersion 30 }iOS配置:
- 在
Info.plist中添加:
<key>NSBluetoothAlwaysUsageDescription</key> <string>我们需要蓝牙权限来连接您的设备</string> <key>NSBluetoothPeripheralUsageDescription</key> <string>我们需要蓝牙权限来连接您的设备</string>- 在
Podfile中确认包含:
pod 'react-native-ble-plx', :path => '../node_modules/react-native-ble-plx'2.3 处理Windows长路径问题
如果遇到"filename longer than 260 characters"错误,这是Windows路径长度限制导致的。解决方案:
- 启用长路径支持:
- 运行
gpedit.msc - 导航到:计算机配置 > 管理模板 > 系统 > 文件系统
- 启用"启用Win32长路径"
- 或使用
npm install --production跳过开发依赖安装
3. 蓝牙设备扫描与连接
3.1 初始化蓝牙管理器
首先创建蓝牙管理器实例:
import { BleManager } from 'react-native-ble-plx'; const manager = new BleManager(); // 监听蓝牙状态变化 manager.onStateChange((state) => { console.log('当前蓝牙状态:', state); }, true);3.2 扫描周边设备
const scanAndConnect = async () => { try { // 先检查蓝牙是否开启 const isPoweredOn = await manager.state() === 'PoweredOn'; if (!isPoweredOn) { throw new Error('请先开启蓝牙'); } // 开始扫描 manager.startDeviceScan(null, null, (error, device) => { if (error) { console.error('扫描错误:', error); return; } // 这里可以添加设备过滤逻辑 if (device.name?.includes('目标设备')) { console.log('发现设备:', device.name); manager.stopDeviceScan(); connectToDevice(device); } }); } catch (error) { console.error('扫描过程出错:', error); } };3.3 连接目标设备
const connectToDevice = async (device) => { try { const connectedDevice = await device.connect(); console.log('连接成功:', connectedDevice.id); // 发现设备服务 const discoveredDevice = await connectedDevice.discoverAllServicesAndCharacteristics(); return discoveredDevice; } catch (error) { console.error('连接失败:', error); } };4. 发送蓝牙命令的核心实现
4.1 发现服务与特征
连接设备后,需要找到目标服务和特征:
const findCharacteristics = async (device) => { try { // 获取所有服务 const services = await device.services(); // 遍历服务查找目标特征 for (const service of services) { const characteristics = await device.characteristicsForService(service.uuid); // 假设我们要找的特征UUID已知 const targetChar = characteristics.find(c => c.uuid === '0000ffe1-0000-1000-8000-00805f9b34fb' ); if (targetChar) { return targetChar; } } } catch (error) { console.error('查找特征失败:', error); } };4.2 发送命令到设备
找到特征后,可以发送命令:
const sendCommand = async (device, characteristic, command) => { try { // 将命令转换为适合传输的格式 const value = stringToBytes(command); // 写入特征值 await device.writeCharacteristicWithResponseForService( characteristic.serviceUUID, characteristic.uuid, value ); console.log('命令发送成功'); } catch (error) { console.error('发送命令失败:', error); } }; // 辅助函数:字符串转字节数组 function stringToBytes(str) { const bytes = []; for (let i = 0; i < str.length; i++) { bytes.push(str.charCodeAt(i)); } return Base64.encode(bytes); }4.3 接收设备响应
许多蓝牙设备会在收到命令后返回响应,可以通过订阅特征通知实现:
const monitorResponse = async (device, characteristic) => { try { await device.monitorCharacteristicForService( characteristic.serviceUUID, characteristic.uuid, (error, char) => { if (error) { console.error('监听错误:', error); return; } if (char.value) { const response = bytesToString(char.value); console.log('收到响应:', response); } } ); } catch (error) { console.error('监听失败:', error); } }; // 辅助函数:字节数组转字符串 function bytesToString(base64) { const bytes = Base64.decode(base64); return String.fromCharCode(...bytes); }5. 实际应用中的关键问题与解决方案
5.1 Android与iOS的差异处理
连接超时问题:
- iOS默认无超时设置,可能导致挂起
- 解决方案:实现自定义超时逻辑
const connectWithTimeout = (device, timeout = 10000) => { return new Promise((resolve, reject) => { const timer = setTimeout(() => { device.cancelConnection(); reject(new Error('连接超时')); }, timeout); device.connect() .then((d) => { clearTimeout(timer); resolve(d); }) .catch(reject); }); };后台运行限制:
- iOS对后台蓝牙操作有严格限制
- 解决方案:配置必要的后台模式并优化操作流程
5.2 大数据量传输处理
当需要传输大量数据时:
- 分片发送:
const sendLargeData = async (device, characteristic, data, chunkSize = 20) => { for (let i = 0; i < data.length; i += chunkSize) { const chunk = data.slice(i, i + chunkSize); await sendCommand(device, characteristic, chunk); } };- 使用流控制特性(如果有):
- 检查设备是否支持"Write Without Response"特性
- 使用
writeCharacteristicWithoutResponseForService方法
5.3 连接稳定性优化
蓝牙连接可能因各种原因中断,需要实现重连机制:
let isConnected = false; let reconnectAttempts = 0; const MAX_RECONNECT_ATTEMPTS = 3; manager.onDeviceDisconnected(deviceId, (error, device) => { isConnected = false; if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { reconnectAttempts++; console.log(`尝试重新连接(${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`); connectToDevice(device); } }); const connectToDevice = async (device) => { try { const connectedDevice = await device.connect(); isConnected = true; reconnectAttempts = 0; // ...其他逻辑 } catch (error) { console.error('连接失败:', error); } };6. 调试技巧与常见问题排查
6.1 常见错误处理
权限问题:
- Android上确保已获取位置权限
- iOS上确保在Info.plist中添加了正确的描述
设备不可见:
- 检查设备是否处于可发现模式
- 确认设备没有被其他应用占用
写入失败:
- 检查特征是否可写
- 尝试使用带响应和不带响应的写入方法
6.2 调试工具推荐
- nRF Connect:强大的蓝牙调试工具,可查看服务、特征和原始数据
- LightBlue:简单的BLE设备探索工具
- Android Studio的BLE调试功能:内置的蓝牙日志分析
6.3 性能优化建议
- 扫描优化:
// 只扫描特定服务的设备 manager.startDeviceScan(['0000ffe0-0000-1000-8000-00805f9b34fb'], null, callback);- 连接管理:
- 及时取消未完成的连接
- 合理使用
cancelDeviceConnection和cancelTransaction
- 资源释放:
// 组件卸载时清理 useEffect(() => { return () => { manager.stopDeviceScan(); manager.destroy(); }; }, []);7. 完整示例代码
以下是一个完整的React Native组件示例,实现了蓝牙命令发送功能:
import React, { useState, useEffect } from 'react'; import { View, Text, Button, TextInput } from 'react-native'; import { BleManager } from 'react-native-ble-plx'; const BluetoothCommandSender = () => { const [manager] = useState(new BleManager()); const [devices, setDevices] = useState([]); const [connectedDevice, setConnectedDevice] = useState(null); const [command, setCommand] = useState(''); const [response, setResponse] = useState(''); const [status, setStatus] = useState('未连接'); useEffect(() => { // 监听蓝牙状态 const subscription = manager.onStateChange((state) => { console.log('蓝牙状态:', state); }, true); return () => { subscription.remove(); manager.destroy(); }; }, [manager]); const scanDevices = () => { setStatus('扫描中...'); setDevices([]); manager.startDeviceScan(null, null, (error, device) => { if (error) { console.error(error); setStatus('扫描出错'); return; } setDevices(prev => [...prev, device]); }); }; const connectDevice = async (device) => { try { setStatus('连接中...'); manager.stopDeviceScan(); const connected = await device.connect(); const discovered = await connected.discoverAllServicesAndCharacteristics(); setConnectedDevice(discovered); setStatus('已连接'); // 监听响应 await discovered.monitorCharacteristicForService( '0000ffe0-0000-1000-8000-00805f9b34fb', '0000ffe1-0000-1000-8000-00805f9b34fb', (error, characteristic) => { if (characteristic?.value) { setResponse(bytesToString(characteristic.value)); } } ); } catch (error) { console.error(error); setStatus('连接失败'); } }; const sendCommand = async () => { if (!connectedDevice || !command) return; try { setStatus('发送中...'); await connectedDevice.writeCharacteristicWithResponseForService( '0000ffe0-0000-1000-8000-00805f9b34fb', '0000ffe1-0000-1000-8000-00805f9b34fb', stringToBytes(command) ); setStatus('命令已发送'); } catch (error) { console.error(error); setStatus('发送失败'); } }; return ( <View style={{ padding: 20 }}> <Text>状态: {status}</Text> <Button title="扫描设备" onPress={scanDevices} /> {devices.map(device => ( <Button key={device.id} title={`连接 ${device.name || '未知设备'}`} onPress={() => connectDevice(device)} /> ))} <TextInput value={command} onChangeText={setCommand} placeholder="输入蓝牙命令" /> <Button title="发送命令" onPress={sendCommand} disabled={!connectedDevice} /> <Text>响应: {response}</Text> </View> ); }; // 辅助函数省略... export default BluetoothCommandSender;在实际项目中,你可能需要根据具体蓝牙设备的协议调整服务UUID、特征UUID以及数据格式处理方法。这个示例提供了完整的框架,可以在此基础上进行扩展和定制。