简介
本文基于 React Native 项目实践,介绍如何实现蓝牙低功耗(BLE)设备接入。重点讨论 Android 不同版本间的权限差异处理,以及蓝牙状态的实时监控等关键技术点。
在 React Native 项目中实现蓝牙低功耗(BLE)功能需要综合考虑多个方面:权限配置、设备扫描、连接管理、状态监控等。本文提供一套完整的开发方案,适用于大多数 BLE 设备接入场景。
架构设计
本文采用四层架构设计,实现代码结构的清晰分层和职责分离:
┌─────────────────────────────────────┐
│ 第四层:应用层 │
│ (业务代码使用 BluetoothManager) │
└────────────────┬────────────────────┘
│
┌────────────────▼────────────────────┐
│ 第三层:蓝牙管理器封装层 │
│ (BluetoothManager.ts) │
└────────────────┬────────────────────┘
│
┌────────────────▼────────────────────┐
│ 第二层:React Native 桥接层 │
│ (BlePro.ts) │
└────────────────┬────────────────────┘
│
┌────────────────▼────────────────────┐
│ 第一层:Android 原生层 │
│ (BleProModule.kt) │
│ ↓ │
│ BLE SDK / Android BLE API │
└─────────────────────────────────────┘
架构层次说明
第一层:Android 原生层
- 职责:直接调用 Android BLE API 或第三方 SDK,处理平台特有的权限检查和状态管理
-
文件:
android/app/src/main/java/***/xxx/BleProModule.kt - 特点:处理 Android 平台底层操作,与硬件直接交互
第二层:React Native 桥接层
- 职责:负责 JavaScript 与原生层之间的通信,处理事件转发和数据桥接
-
文件:
src/native/BlePro.ts - 特点:提供类型安全的接口,处理跨平台通信
第三层:蓝牙管理器封装层
- 职责:封装所有蓝牙操作,提供统一的 API 接口,管理状态和生命周期
-
文件:
src/utils/bluetoothManager.ts - 特点:提供高级抽象,处理权限、状态管理、错误处理等
第四层:应用层
- 职责:业务代码调用蓝牙管理器,实现业务逻辑
- 特点:只需关注业务逻辑,无需关心底层实现细节
核心文件说明
| 层级 | 文件路径 | 说明 |
|---|---|---|
| 第一层 | android/app/src/main/java/***/xxx/BleProModule.kt |
Android 原生模块,实现 BLE API 调用 |
| 第一层 | android/app/src/main/AndroidManifest.xml |
Android 权限配置文件 |
| 第二层 | src/native/BlePro.ts |
React Native 桥接层,处理原生模块通信 |
| 第三层 | src/utils/bluetoothManager.ts |
蓝牙管理器封装,统一管理蓝牙操作 |
| 第四层 | 业务代码文件 | 应用层业务逻辑实现 |
第一层:Android 原生层
Android 原生层负责直接调用 BLE API,处理平台特有的权限检查和状态管理。这是整个架构的基础层。
权限配置
权限配置是 BLE 开发的关键环节,Android 不同版本对蓝牙权限的要求存在显著差异,需要特别注意版本兼容性。
AndroidManifest.xml 配置
在 AndroidManifest.xml 中声明所需的蓝牙权限:
<!-- Android 12+ (API 31+) 新权限系统 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
<!-- Android 12 以下旧权限系统 -->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<!-- Android 10-11 需要位置权限(系统要求) -->
<uses-permission android:name="android.permission.A***ESS_FINE_LOCATION"/>
<!-- BLE 功能声明(建议设为 false,确保无蓝牙设备的兼容性) -->
<uses-feature android:name="android.hardware.bluetooth_le"
android:required="false"/>
关键要点:
- Android 12+ 引入新的权限系统,
BLUETOOTH_SCAN和BLUETOOTH_CONNECT必须同时声明,两者缺一不可 - Android 10-11 版本要求
A***ESS_FINE_LOCATION权限,系统认为 BLE 扫描可能用于位置定位 -
neverForLocation标志用于声明该权限不用于定位目的,避免 Google Play 审核时被拒绝
原生模块实现
原生模块(BleProModule.kt)负责直接调用 BLE API,处理平台特有的逻辑。
模块注册
在 MainApplication.kt 中注册原生模块:
class MainApplication : Application(), ReactApplication {
override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
add(BleProPackage()) // 注册蓝牙模块
}
}
}
核心方法实现
原生模块提供以下核心方法:
-
initialize()- 初始化 BLE 服务,检查蓝牙状态 -
scan()- 开始扫描设备 -
stopScan()- 停止扫描 -
connect(deviceId)- 连接设备 -
disconnect()- 断开连接 -
checkBluetoothEnabled()- 检查蓝牙是否开启 -
checkBluetoothSupported()- 检查蓝牙是否支持
事件发送
原生层通过 React Native 事件系统向 JavaScript 层发送事件:
-
ble.serviceReady- 服务就绪事件 -
ble.deviceFound- 设备发现事件 -
ble.connectState- 连接状态事件 -
ble.error- 错误事件
运行时权限处理
除了在 AndroidManifest.xml 中声明权限外,还需要在代码中处理运行时权限请求。不同 Android 版本的权限处理机制存在差异,需要分别处理。
权限检查实现
根据 Android 版本动态检查对应权限:
const checkBluetoothPermissions = async (): Promise<boolean> => {
if (Platform.OS !== 'android') return true;
const sdk = Number(Platform.Version);
if (sdk >= 31) {
// Android 12+ 需要两个新权限
const hasScan = await PermissionsAndroid.check(
PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN
);
const hasConnect = await PermissionsAndroid.check(
PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT
);
return hasScan && hasConnect;
} else {
// Android 10-11 需要位置权限(没办法,Google 的要求)
const hasLocation = await PermissionsAndroid.check(
PermissionsAndroid.PERMISSIONS.A***ESS_FINE_LOCATION
);
return hasLocation;
}
};
请求权限
如果权限未授予,需要主动请求:
const requestBluetoothPermissions = async (): Promise<boolean> => {
if (Platform.OS !== 'android') return true;
try {
const sdk = Number(Platform.Version);
if (sdk >= 31) {
// Android 12+ 需要同时请求两个权限
const res = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
]);
return (
res[PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN] === 'granted' &&
res[PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT] === 'granted'
);
} else {
// Android 10-11 只请求位置权限
const fine = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.A***ESS_FINE_LOCATION
);
return fine === 'granted';
}
} catch (error) {
console.log('请求权限失败:', error);
return false;
}
};
实现建议:
- 采用按需请求策略,在用户实际使用蓝牙功能时再请求权限,而非应用启动时,以提升用户体验
- 当用户拒绝权限时,应提供清晰的提示信息,引导用户前往系统设置手动开启权限
第二层:React Native 桥接层
桥接层负责 JavaScript 与原生层之间的通信,提供类型安全的接口,处理事件转发和数据桥接。
桥接层实现
桥接层文件 src/native/BlePro.ts 的主要职责:
模块导入
import {
NativeModules,
NativeEventEmitter,
type NativeModule,
EmitterSubscription,
} from 'react-native';
const { BleProModule } = NativeModules;
核心 API 封装
桥接层封装原生模块的方法:
export const BlePro = {
// 基础操作
initialize: () => BleProModule?.initialize(),
scan: () => BleProModule?.scan(),
stopScan: () => BleProModule?.stopScan(),
connect: (deviceId: string) => BleProModule?.connect(deviceId),
disconnect: () => BleProModule?.disconnect(),
// 状态检查
checkBluetoothEnabled: () => BleProModule?.checkBluetoothEnabled(),
checkBluetoothSupported: () => BleProModule?.checkBluetoothSupported(),
// 事件监听器
addServiceReadyListener: (cb: (ready: boolean) => void) => ...,
addDeviceFoundListener: (cb: (d: BleDevice) => void) => ...,
addConnectStateListener: (cb: (s: { model: number; state: number }) => void) => ...,
addErrorListener: (cb: (error: ErrorInfo) => void) => ...,
};
事件系统
桥接层通过 NativeEventEmitter 处理原生层事件:
- 将原生事件转换为 JavaScript 事件
- 提供类型安全的事件监听接口
- 处理事件订阅和取消订阅
第三层:蓝牙管理器封装层
蓝牙管理器封装层提供高级抽象,统一管理所有蓝牙操作,处理权限、状态管理、错误处理等复杂逻辑。
BluetoothManager 设计
BluetoothManager 类是蓝牙功能的核心封装,提供统一的 API 接口。
类结构设计
export class BluetoothManager {
private state: BluetoothState;
private callbacks: BluetoothCallbacks;
private subscriptions: any[];
private scanTimer: number | null;
constructor(callbacks: BluetoothCallbacks);
async initialize(): Promise<void>;
async scan(): Promise<void>;
async stopScan(): Promise<void>;
async connect(device: BleDevice): Promise<void>;
async disconnect(): Promise<void>;
getState(): BluetoothState;
destroy(): void;
}
回调接口定义
export interface BluetoothCallbacks {
onStateChange?: (state: BluetoothState) => void;
onDeviceFound?: (device: BleDevice) => void;
onDeviceConnected?: (model: number) => void;
onDeviceDisconnected?: () => void;
onError?: (error: string) => void;
on***patibilityError?: (error: ErrorInfo) => void;
onNavigateToSettings?: () => void;
}
核心功能实现
1. 初始化
初始化蓝牙服务,检查权限和蓝牙状态:
try {
// initialize() 会自动检查蓝牙状态和权限
await bluetoothManager.initialize();
console.log('蓝牙服务初始化成功');
} catch (error) {
if (error.message === 'BLUETOOTH_DISABLED') {
// 蓝牙未开启,提示用户
Alert.alert('蓝牙未开启', '请先开启设备蓝牙功能');
} else {
console.error('初始化失败:', error);
}
}
初始化流程包括:
- 断开现有连接(如果有)
- 调用桥接层的
BlePro.initialize() - 检查错误类型,处理蓝牙未开启等特殊情况
- 注册事件监听器
2. 蓝牙状态检查
提供蓝牙状态检查方法:
// 检查蓝牙是否开启
async checkBluetoothEnabled(): Promise<boolean> {
return await BlePro.checkBluetoothEnabled();
}
// 检查蓝牙是否支持
async checkBluetoothSupported(): Promise<boolean> {
return await BlePro.checkBluetoothSupported();
}
3. 设备扫描
扫描功能实现:
async scan(): Promise<void> {
// 1. 检查是否已在扫描中
if (this.state.scanning) return;
// 2. 请求权限
const ok = await this.requestBlePermissions();
if (!ok) return;
// 3. 更新状态
this.updateState({ scanning: true, devices: [], error: null });
// 4. 调用桥接层方法
await BlePro.scan();
// 5. 设置超时自动停止
this.scanTimer = setTimeout(() => {
this.stopScan();
}, 15000);
}
扫描流程:
- 检查扫描状态,避免重复扫描
- 请求 BLE 权限
- 更新状态为扫描中
- 调用桥接层的
BlePro.scan()方法 - 设置 15 秒超时自动停止
- 通过
onDeviceFound回调接收设备
4. 设备连接
连接功能实现:
async connect(device: BleDevice): Promise<void> {
// 1. 设置设备接口
await BlePro.setInterfaces(device.model);
// 2. 停止扫描
await this.stopScan();
// 3. 执行连接
const deviceId = device.address || device.name;
await BlePro.connect(deviceId);
// 4. 更新连接状态
this.updateState({ connectedModel: device.model });
}
连接流程:
- 设置设备接口类型
- 停止当前扫描
- 调用桥接层的
BlePro.connect()方法 - 使用设备 MAC 地址或名称连接
- 通过
ble.connectState事件接收连接结果(state = 3 表示成功)
5. 状态管理
BluetoothManager 维护内部状态,并通过回调通知应用层:
export interface BluetoothState {
ready: boolean; // 服务是否就绪
devices: BleDevice[]; // 已发现的设备列表
scanning: boolean; // 是否正在扫描
connectedModel: number | null; // 已连接的设备型号
error: string | null; // 错误信息
}
6. 生命周期管理
提供销毁方法,清理资源:
destroy(): void {
// 移除所有事件监听器
this.subscriptions.forEach(sub => sub.remove());
// 清除扫描定时器
if (this.scanTimer) {
clearTimeout(this.scanTimer);
}
}
第四层:应用层
应用层是业务代码使用蓝牙功能的地方,只需调用 BluetoothManager 提供的 API 接口即可。
错误处理
BLE 开发过程中可能遇到多种错误情况。本节整理了常见错误类型及对应的解决方案。
错误代码定义
项目定义了以下错误代码:
// 在 BleProModule.kt 中定义
const ERROR_INIT_FAILED = "INIT_FAILED"
const ERROR_SCAN_FAILED = "SCAN_FAILED"
const ERROR_CONNECT_FAILED = "CONNECT_FAILED"
const ERROR_DISCONNECT_FAILED = "DISCONNECT_FAILED"
const ERROR_SET_INTERFACE_FAILED = "SET_INTERFACE_FAILED"
const ERROR_DEVICE_NOT_SUPPORTED = "DEVICE_NOT_SUPPORTED"
const ERROR_PERMISSION_DENIED = "PERMISSION_DENIED"
const ERROR_BLUETOOTH_DISABLED = "BLUETOOTH_DISABLED"
错误监听
const bluetoothManager = new BluetoothManager({
onError: (error) => {
// 通用错误
console.error('蓝牙错误:', error);
},
on***patibilityError: (error) => {
// 兼容性错误处理(提供用户友好的错误提示)
// error: {
// errorCode: string,
// errorMessage: string,
// details?: string
// }
switch (error.errorCode) {
case 'BLUETOOTH_DISABLED':
// 蓝牙未开启
Alert.alert('蓝牙未开启', '请先开启设备蓝牙功能');
break;
case 'PERMISSION_DENIED':
// 权限被拒绝
Alert.alert('权限不足', '需要蓝牙权限以扫描与连接设备', [
{ text: '取消', style: 'cancel' },
{
text: '去设置',
onPress: () => Linking.openSettings()
},
]);
break;
case 'DEVICE_NOT_SUPPORTED':
// 设备不支持
Alert.alert('设备不支持', error.errorMessage);
break;
// ... 其他错误
}
},
});
原生层错误处理
原生层会通过 ble.error 事件发送错误:
private fun sendErrorEvent(
errorCode: String,
errorMessage: String,
details: String? = null
) {
val map = Arguments.createMap()
map.putString("errorCode", errorCode)
map.putString("errorMessage", errorMessage)
if (details != null) {
map.putString("details", details)
}
map.putLong("timestamp", System.currentTimeMillis())
sendEvent("ble.error", map)
}
最佳实践
基于实际项目经验,总结以下最佳实践,有助于提升开发效率和代码质量:
1. 生命周期管理
useEffect(() => {
// 创建蓝牙管理器
const manager = new BluetoothManager({ /* callbacks */ });
// 初始化
manager.initialize().then(() => {
manager.scan();
});
// 清理
return () => {
manager.disconnect();
manager.destroy(); // 移除所有监听器
};
}, []);
2. 状态管理
使用 useRef 存储蓝牙状态,避免不必要的重新渲染:
const bluetoothStateRef = useRef<BluetoothState>({
ready: false,
devices: [],
scanning: false,
connectedModel: null,
error: null,
});
// 在回调中更新 ref
onStateChange: (state) => {
bluetoothStateRef.current = { ...bluetoothStateRef.current, ...state };
}
3. 自动重连
设备断开后自动尝试重连:
onDeviceDisconnected: () => {
// 停止当前活动
stopMeasurement();
// 延迟后重新扫描
setTimeout(async () => {
try {
await bluetoothManager.scan();
} catch (error) {
console.error('重连失败:', error);
}
}, 1000);
}
4. 蓝牙状态监控
定期检查蓝牙状态,处理用户手动关闭蓝牙的情况:
useEffect(() => {
const interval = setInterval(async () => {
const isEnabled = await bluetoothManager.checkBluetoothEnabled();
if (!isEnabled && bluetoothStateRef.current.ready) {
// 蓝牙被关闭,停止所有活动
await bluetoothManager.stopScan();
bluetoothStateRef.current.ready = false;
}
}, 1000);
return () => clearInterval(interval);
}, []);
5. 权限请求时机
在真正需要蓝牙功能时再请求权限,避免应用启动时弹窗:
// 扫描前请求权限
async scan() {
const ok = await this.requestBlePermissions();
if (!ok) {
// 权限被拒绝时,提供相应提示信息
return;
}
// 继续扫描...
}
6. 错误恢复
对于可恢复的错误,自动重试:
let retryCount = 0;
const MAX_RETRIES = 3;
const initializeWithRetry = async () => {
try {
await bluetoothManager.initialize();
retryCount = 0; // 成功后重置
} catch (error) {
if (retryCount < MAX_RETRIES) {
retryCount++;
setTimeout(initializeWithRetry, 1000 * retryCount); // 指数退避
} else {
// 达到最大重试次数,显示错误
Alert.alert('初始化失败', '请检查蓝牙设置');
}
}
};
基础使用
1. 创建蓝牙管理器
import { BluetoothManager } from '../utils/bluetoothManager';
const bluetoothManager = new BluetoothManager({
onStateChange: (state) => {
// 蓝牙状态更新
console.log('蓝牙状态:', state);
},
onDeviceFound: (device) => {
// 发现新设备
console.log('发现设备:', device);
},
onDeviceConnected: (model) => {
// 设备连接成功
console.log('设备连接成功:', model);
},
onDeviceDisconnected: () => {
// 设备断开
console.log('设备断开连接');
},
onError: (error) => {
// 错误处理
console.error('蓝牙错误:', error);
},
});
2. 初始化和扫描
// 初始化蓝牙服务
await bluetoothManager.initialize();
// 开始扫描设备
await bluetoothManager.scan();
3. 连接设备
const device = bluetoothManager.getState().devices[0];
await bluetoothManager.connect(device);
4. 监听蓝牙状态变化
useEffect(() => {
const checkBluetoothState = async () => {
const isEnabled = await bluetoothManager.checkBluetoothEnabled();
if (!isEnabled) {
// 蓝牙被关闭,停止扫描
await bluetoothManager.stopScan();
}
};
const interval = setInterval(checkBluetoothState, 1000);
return () => clearInterval(interval);
}, []);
完整示例
import React, { useEffect, useRef, useState } from 'react';
import { BluetoothManager } from '../utils/bluetoothManager';
import { BleDevice } from '../native/BlePro';
import { Alert, Platform } from 'react-native';
const BluetoothExample: React.FC = () => {
const [devices, setDevices] = useState<BleDevice[]>([]);
const [connectedModel, setConnectedModel] = useState<number | null>(null);
const [scanning, setScanning] = useState(false);
const bluetoothManagerRef = useRef<BluetoothManager | null>(null);
useEffect(() => {
// 创建蓝牙管理器
bluetoothManagerRef.current = new BluetoothManager({
onStateChange: (state) => {
setDevices(state.devices);
setConnectedModel(state.connectedModel);
setScanning(state.scanning);
},
onDeviceFound: (device) => {
console.log('发现设备:', device);
},
onDeviceConnected: (model) => {
console.log('设备连接成功:', model);
},
onDeviceDisconnected: () => {
console.log('设备断开');
},
onError: (error) => {
Alert.alert('蓝牙错误', error);
},
on***patibilityError: (error) => {
if (error.errorCode === 'BLUETOOTH_DISABLED') {
Alert.alert('蓝牙未开启', '请先开启蓝牙');
}
},
});
// 初始化并开始扫描
const initBluetooth = async () => {
try {
await bluetoothManagerRef.current?.initialize();
await bluetoothManagerRef.current?.scan();
} catch (error) {
console.error('初始化失败:', error);
}
};
initBluetooth();
// 清理
return () => {
bluetoothManagerRef.current?.disconnect();
bluetoothManagerRef.current?.destroy();
};
}, []);
// 连接设备
const handleConnect = async (device: BleDevice) => {
try {
await bluetoothManagerRef.current?.connect(device);
} catch (error) {
Alert.alert('连接失败', String(error));
}
};
// 断开连接
const handleDisconnect = async () => {
try {
await bluetoothManagerRef.current?.disconnect();
} catch (error) {
console.error('断开失败:', error);
}
};
return (
<View>
<Text>扫描中: {scanning ? '是' : '否'}</Text>
<Text>已连接: {connectedModel || '无'}</Text>
<Text>设备数量: {devices.length}</Text>
{devices.map((device, index) => (
<TouchableOpacity
key={index}
onPress={() => handleConnect(device)}
>
<Text>{device.name} ({device.model})</Text>
</TouchableOpacity>
))}
{connectedModel && (
<Button title="断开连接" onPress={handleDisconnect} />
)}
</View>
);
};
权限检查示例
import { PermissionsAndroid, Platform } from 'react-native';
const checkBluetoothPermissions = async (): Promise<boolean> => {
if (Platform.OS !== 'android') return true;
try {
const sdk = Number(Platform.Version);
if (sdk >= 31) {
// Android 12+
const hasScan = await PermissionsAndroid.check(
PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN
);
const hasConnect = await PermissionsAndroid.check(
PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT
);
return hasScan && hasConnect;
} else {
// Android 10-11
const hasLocation = await PermissionsAndroid.check(
PermissionsAndroid.PERMISSIONS.A***ESS_FINE_LOCATION
);
return hasLocation;
}
} catch (error) {
console.error('检查权限失败:', error);
return false;
}
};
总结
本文介绍了一套完整的 React Native BLE 开发方案,采用三层架构设计,通过 BluetoothManager 统一管理蓝牙操作,开发者可专注于业务逻辑实现,底层权限管理、状态监控、设备扫描和连接等细节已封装处理。
核心要点
- 权限管理 - Android 不同版本的权限机制差异显著,需特别关注版本兼容性
- 状态监控 - 实时监听蓝牙状态变化,处理用户手动关闭蓝牙等场景
- 错误处理 - 建立完善的错误处理机制,为用户提供友好的错误提示
- 自动重连 - 实现设备断开后的自动重连机制,提升用户体验
- 资源管理 - 组件卸载时及时清理蓝牙资源,防止内存泄漏
常见问题
-
Android 12+ 权限配置 - 仅声明
BLUETOOTH_SCAN会导致连接功能不可用,必须同时声明BLUETOOTH_CONNECT - 状态监听缺失 - 未实现蓝牙状态监听可能导致应用无法响应蓝牙关闭事件,停留在扫描状态
- 权限请求时机 - 在应用启动时请求权限影响用户体验,建议采用按需请求策略
建议
- 优先完成权限配置,这是最容易出现问题的环节
- 实现完整的蓝牙状态监控,确保应用能正确响应状态变化
- 建立全面的错误处理机制,覆盖各种异常情况
- 在不同 Android 版本设备上充分测试,特别是 Android 12 前后的版本差异
通过遵循本文介绍的方案和最佳实践,可以有效解决 React Native BLE 开发中的常见问题,提升应用的稳定性和用户体验。