简介

本文基于 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/com/xxx/BleProModule.kt
  • 特点:处理 Android 平台底层操作,与硬件直接交互

第二层:React Native 桥接层

  • 职责:负责 JavaScript 与原生层之间的通信,处理事件转发和数据桥接
  • 文件src/native/BlePro.ts
  • 特点:提供类型安全的接口,处理跨平台通信

第三层:蓝牙管理器封装层

  • 职责:封装所有蓝牙操作,提供统一的 API 接口,管理状态和生命周期
  • 文件src/utils/bluetoothManager.ts
  • 特点:提供高级抽象,处理权限、状态管理、错误处理等

第四层:应用层

  • 职责:业务代码调用蓝牙管理器,实现业务逻辑
  • 特点:只需关注业务逻辑,无需关心底层实现细节

核心文件说明

层级文件路径说明
第一层android/app/src/main/java/com/xxx/BleProModule.ktAndroid 原生模块,实现 BLE API 调用
第一层android/app/src/main/AndroidManifest.xmlAndroid 权限配置文件
第二层src/native/BlePro.tsReact 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.ACCESS_FINE_LOCATION"/>
      <!-- BLE 功能声明(建议设为 false,确保无蓝牙设备的兼容性) -->
          <uses-feature android:name="android.hardware.bluetooth_le"
          android:required="false"/>

关键要点:

  1. Android 12+ 引入新的权限系统,BLUETOOTH_SCANBLUETOOTH_CONNECT 必须同时声明,两者缺一不可
  2. Android 10-11 版本要求 ACCESS_FINE_LOCATION 权限,系统认为 BLE 扫描可能用于位置定位
  3. 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.ACCESS_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.ACCESS_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;
onCompatibilityError?: (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);
}
}

初始化流程包括:

  1. 断开现有连接(如果有)
  2. 调用桥接层的 BlePro.initialize()
  3. 检查错误类型,处理蓝牙未开启等特殊情况
  4. 注册事件监听器

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);
  }

扫描流程:

  1. 检查扫描状态,避免重复扫描
  2. 请求 BLE 权限
  3. 更新状态为扫描中
  4. 调用桥接层的 BlePro.scan() 方法
  5. 设置 15 秒超时自动停止
  6. 通过 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 });
  }

连接流程:

  1. 设置设备接口类型
  2. 停止当前扫描
  3. 调用桥接层的 BlePro.connect() 方法
  4. 使用设备 MAC 地址或名称连接
  5. 通过 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);
},
onCompatibilityError: (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);
      },
      onCompatibilityError: (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.ACCESS_FINE_LOCATION
  );
  return hasLocation;
  }
  } catch (error) {
  console.error('检查权限失败:', error);
  return false;
  }
  };

总结

本文介绍了一套完整的 React Native BLE 开发方案,采用三层架构设计,通过 BluetoothManager 统一管理蓝牙操作,开发者可专注于业务逻辑实现,底层权限管理、状态监控、设备扫描和连接等细节已封装处理。

核心要点

  1. 权限管理 - Android 不同版本的权限机制差异显著,需特别关注版本兼容性
  2. 状态监控 - 实时监听蓝牙状态变化,处理用户手动关闭蓝牙等场景
  3. 错误处理 - 建立完善的错误处理机制,为用户提供友好的错误提示
  4. 自动重连 - 实现设备断开后的自动重连机制,提升用户体验
  5. 资源管理 - 组件卸载时及时清理蓝牙资源,防止内存泄漏

常见问题

  1. Android 12+ 权限配置 - 仅声明 BLUETOOTH_SCAN 会导致连接功能不可用,必须同时声明 BLUETOOTH_CONNECT
  2. 状态监听缺失 - 未实现蓝牙状态监听可能导致应用无法响应蓝牙关闭事件,停留在扫描状态
  3. 权限请求时机 - 在应用启动时请求权限影响用户体验,建议采用按需请求策略

建议

  • 优先完成权限配置,这是最容易出现问题的环节
  • 实现完整的蓝牙状态监控,确保应用能正确响应状态变化
  • 建立全面的错误处理机制,覆盖各种异常情况
  • 在不同 Android 版本设备上充分测试,特别是 Android 12 前后的版本差异

通过遵循本文介绍的方案和最佳实践,可以有效解决 React Native BLE 开发中的常见问题,提升应用的稳定性和用户体验。