25、蓝牙跨平台开发:Flutter蓝牙库(flutter_blue_plus)、React Native蓝牙库(react-native-ble-plx)、与原生Android对比

说实话,做了这么多年蓝牙开发,我遇到最多的问题就是:「能不能一套代码跑iOS和Android?」

每次听到这个,我都得深吸一口气。蓝牙这玩意儿,底层水太深。但跨平台框架确实在努力填坑。今天我就拿两个最主流的方案——Flutter的flutter_blue_plus和React Native的react-native-ble-plx,跟原生Android做个硬碰硬的对比。

为什么跨平台蓝牙开发让人头疼?

你想想看,蓝牙开发本质上是在跟硬件打交道。Android的蓝牙栈、iOS的Core Bluetooth,底层实现完全不同。跨平台框架要做的事,说白了就是「翻译官」——把上层统一的API翻译成各平台的方言。

我在项目中遇到过好几次:同样的代码,在Android上跑得飞起,换到iOS上就断连。嗯,这里要注意,这不是框架的锅,是平台差异的锅。

核心痛点:

  • Android的蓝牙权限管理极其复杂(位置权限、蓝牙权限、后台扫描权限)
  • iOS的蓝牙限制更多(必须指定Service UUID、后台模式有限制)
  • 不同厂商的Android手机蓝牙行为不一致(小米、华为、三星各有各的脾气)

Flutter蓝牙库:flutter_blue_plus

我个人习惯用Flutter做跨平台原型开发,因为它的热重载太香了。flutter_blue_plusflutter_blue的升级版,修复了大量原库的bug。

基本用法

// 初始化
FlutterBluePlus flutterBlue = FlutterBluePlus.instance;

// 扫描设备
flutterBlue.startScan(timeout: Duration(seconds: 4));

// 监听扫描结果
flutterBlue.scanResults.listen((results) {
  for (ScanResult r in results) {
    print('发现设备: ${r.device.name} - ${r.device.id}');
  }
});

// 连接设备
await device.connect();
// 发现服务
List<BluetoothService> services = await device.discoverServices();
// 读写特征
BluetoothCharacteristic characteristic = services[0].characteristics[0];
await characteristic.write([0x01, 0x02]);
List<int> value = await characteristic.read();

你看,代码很简洁。但我要提醒你:flutter_blue_plus的扫描回调是Stream,记得做好资源释放。我曾经因为忘记取消订阅,导致页面退出后还在疯狂打印日志。

优点与坑点

优点 坑点
API设计优雅,链式调用流畅 Android端扫描有时会漏设备(尤其后台扫描)
支持Android和iOS双平台 iOS端必须提前声明后台模式
社区活跃,更新频繁 MTU协商有时会失败,需要手动处理
内置重连机制 部分国产手机需要额外适配

React Native蓝牙库:react-native-ble-plx

如果你团队里前端出身的人多,React Native可能是更好的选择。react-native-ble-plx这个库我用了两年多,稳定性还不错。

基本用法

import { BleManager } from 'react-native-ble-plx';

const manager = new BleManager();

// 扫描设备
manager.startDeviceScan(null, null, (error, device) => {
  if (error) {
    console.error(error);
    return;
  }
  if (device && device.name) {
    console.log(`发现设备: ${device.name}`);
  }
});

// 连接设备
const connectedDevice = await device.connect();
await connectedDevice.discoverAllServicesAndCharacteristics();

// 读写特征
const service = await connectedDevice.getService('service-uuid');
const characteristic = await service.getCharacteristic('char-uuid');
await characteristic.writeWithResponse(base64.encode([0x01, 0x02]));
const value = await characteristic.read();

注意看,react-native-ble-plx的数据传输用的是Base64编码。这一点跟Flutter直接传字节数组不一样。我刚开始用的时候踩过这个坑——直接传字节数组,结果iOS端直接崩溃。

我的经验:在React Native中处理蓝牙数据,建议封装一个工具函数,专门做ArrayBuffer和Base64的互转。这样业务层就不用关心底层编码了。

优缺点对比

优点 坑点
API稳定,文档完善 数据必须用Base64编码,增加转换开销
支持后台扫描(iOS需配置) Android端需要手动处理权限请求
支持多设备同时连接 断开连接后重连逻辑需要自己实现
TypeScript类型支持好 扫描回调中设备对象可能为null

与原生Android的深度对比

说白了,跨平台框架再怎么封装,底层调的还是原生API。我拿一个典型的蓝牙连接流程来对比:

原生Android代码(简化版)

// 扫描
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
adapter.startLeScan(callback);

// 连接
BluetoothDevice device = adapter.getRemoteDevice(address);
BluetoothGatt gatt = device.connectGatt(context, false, gattCallback);

// 发现服务
gatt.discoverServices();

// 读写
BluetoothGattCharacteristic characteristic = service.getCharacteristic(uuid);
gatt.setCharacteristicNotification(characteristic, true);
characteristic.setValue(data);
gatt.writeCharacteristic(characteristic);

原生代码的优点是:你能控制每一个细节。但缺点也很明显——代码量大,而且Android版本差异让你头疼。比如Android 12之后,蓝牙权限又变了。

三者的核心差异

维度 原生Android Flutter (flutter_blue_plus) React Native (react-native-ble-plx)
开发效率 低(需要写大量模板代码) 高(热重载,代码简洁) 中(需要处理JS桥接)
性能 最高(直接调用系统API) 中(Dart到原生有桥接开销) 中(JS到原生桥接开销更大)
调试难度 中(可以用Android Studio调试) 低(Dart DevTools很强大) 高(JS层和原生层分开调试)
平台适配 需要分别适配Android和iOS 一套代码适配双平台 一套代码适配双平台
社区支持 官方文档完善 社区活跃,但文档一般 文档完善,示例多

我该怎么选?

这个问题没有标准答案。我根据项目经验给你几个建议:

  • 做原型验证或内部工具:选Flutter。开发速度快,改起来也快。
  • 团队前端出身的人多:选React Native。学习成本低,JS生态丰富。
  • 做医疗设备或工业级产品:选原生。稳定性和性能是第一位的。
  • 需要深度定制蓝牙行为:选原生。跨平台框架的抽象层会限制你的操作空间。

重要提醒:无论选哪个框架,一定要在真机上测试。模拟器上的蓝牙行为跟真机完全不同。我曾经在模拟器上跑得好好的,一上真机就各种问题。

知识体系总览

下面这张图总结了本章的核心逻辑,帮你快速建立知识框架:

蓝牙跨平台开发知识体系 原生Android Flutter React Native 底层:Android Bluetooth API / iOS Core Bluetooth 扫描 startScan / startDeviceScan 连接 connect / connectGatt 服务发现 discoverServices 读写 read / write 选型建议 原型验证 → Flutter | 前端团队 → React Native | 工业级 → 原生

避坑指南

最后,分享几个我踩过的坑:

  • 权限问题:Android 12+需要BLUETOOTH_SCANBLUETOOTH_CONNECT权限,别再用旧的BLUETOOTH权限了。
  • 扫描超时:iOS端扫描默认不会超时,但Android端建议设置超时时间,否则一直扫描会耗电。
  • 数据分包:蓝牙一次传输最多20字节(MTU默认23字节)。超过这个大小,需要自己实现分包逻辑。跨平台框架不会帮你做这个。
  • 重连机制:跨平台框架的重连功能都比较弱。我建议自己维护一个重连队列,断连后自动重试3次。

我的习惯:做跨平台蓝牙开发时,我会先在原生层把核心逻辑调通,再用框架封装。这样出了问题,我能快速定位是框架的问题还是底层的问题。

好了,关于跨平台蓝牙开发就聊这么多。记住一点:没有银弹。选什么方案,取决于你的团队、产品和预算。别盲目追新,稳定才是王道。


公众号:蓝海资料掘金营,微信deep3321