1. Flutter BLE设备扫描项目概述
在移动应用开发中,蓝牙低功耗(BLE)设备交互是一个常见需求。这个Flutter项目展示了如何使用FlutterBluePlus插件实现BLE设备扫描功能。项目采用典型的分层架构设计,包含完整的权限管理、设备发现、连接管理和状态反馈机制。
作为开发者,我最初接触这个项目是为了解决智能硬件产品的手机端对接需求。经过多次迭代,现在这个实现方案已经能够稳定运行在Android和iOS平台,支持设备发现、信号强度显示、连接状态管理等功能。下面我将详细拆解这个项目的技术实现,分享其中的关键设计思路和实战经验。
2. 项目架构设计解析
2.1 分层架构设计
项目采用经典的三层架构设计,各层职责明确:
code复制UI层 → 业务逻辑层 → 数据访问层 → 原生层
这种分层设计使得代码结构清晰,便于维护和扩展。我在实际开发中发现,明确划分层级能有效避免代码耦合,特别是在处理跨平台差异时优势明显。
2.2 核心模块划分
- 权限管理模块:处理蓝牙和位置权限申请
- 设备扫描模块:负责BLE设备发现和过滤
- 连接管理模块:处理设备连接/断开操作
- 状态管理模块:维护扫描状态和设备列表
- UI展示模块:呈现设备列表和交互界面
3. 技术实现细节
3.1 环境准备与依赖配置
首先需要在pubspec.yaml中添加依赖:
yaml复制dependencies:
flutter_blue_plus: ^1.10.0
permission_handler: ^11.0.1
对于Android平台,需要在AndroidManifest.xml中配置权限:
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"/>
<!-- Android 12+新增权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
注意:从Android 12开始需要额外声明BLUETOOTH_SCAN和BLUETOOTH_CONNECT权限,这是很多开发者容易忽略的点。
3.2 设备模型设计
BleDeviceInfo类封装了设备信息,包含以下核心属性:
dart复制class BleDeviceInfo {
final String id; // 设备唯一标识
final String? name; // 设备名称
final String? localName; // 广播名称
final int rssi; // 信号强度
final BluetoothDevice device; // 底层设备对象
final int? manufacturerData; // 制造商数据
// 计算属性
String get displayName => ... // 显示名称逻辑
String get formattedRssi => ... // 格式化信号强度
Color get signalColor => ... // 根据信号强度返回颜色
IconData get signalIcon => ... // 信号强度图标
}
这种设计将原始设备数据与UI展示逻辑分离,使得业务代码更加清晰。我在实际项目中还扩展了设备类型识别功能,通过manufacturerData区分不同厂商设备。
3.3 蓝牙状态管理
蓝牙适配器状态通过Stream监听:
dart复制enum BluetoothAdapterState {
unknown, // 状态未知
unavailable, // 不可用
unauthorized,// 未授权
turningOn, // 正在开启
on, // 已开启
turningOff, // 正在关闭
off // 已关闭
}
// 初始化蓝牙状态监听
void _initBluetooth() async {
// 获取当前状态
BluetoothAdapterState state = await FlutterBluePlus.adapterState.first;
_updateBluetoothStatus(state);
// 监听状态变化
_bluetoothStateSubscription = FlutterBluePlus.adapterState.listen((state) {
_updateBluetoothStatus(state);
});
}
这种响应式设计确保UI能实时反映蓝牙状态变化。我在项目中还添加了自动重试机制,当蓝牙关闭时提示用户开启。
3.4 权限管理实现
完整的权限检查流程:
dart复制Future<bool> _checkPermissions() async {
// 1. 检查蓝牙状态
if (!await _checkBluetoothState()) return false;
// 2. 检查位置权限(Android需要)
if (!await Permission.location.isGranted) {
var status = await Permission.location.request();
if (!status.isGranted) return false;
}
// 3. 检查蓝牙权限
if (!await Permission.bluetooth.isGranted) {
var status = await Permission.bluetooth.request();
if (!status.isGranted) return false;
}
return true;
}
经验分享:Android和iOS的权限模型不同,iOS不需要位置权限即可扫描BLE设备,但需要在使用说明中声明蓝牙用途。
3.5 设备扫描实现
扫描流程的核心代码:
dart复制Future<void> _startScanning() async {
if (_isScanning) return;
// 检查权限
if (!await _checkPermissions()) return;
// 初始化状态
setState(() {
_isLoading = true;
_devices.clear();
});
try {
// 获取已连接设备
_connectedDevices = await FlutterBluePlus.connectedDevices;
// 开始扫描
await FlutterBluePlus.startScan(timeout: Duration(seconds: 15));
// 设置超时定时器
_scanTimeoutTimer = Timer(Duration(seconds: 15), _stopScanning);
// 监听扫描结果
_scanSubscription = FlutterBluePlus.scanResults.listen((results) {
if (!mounted) return;
setState(() {
for (var result in results) {
// 过滤无效设备
if (result.device.platformName.isEmpty) continue;
// 创建设备信息对象
var deviceInfo = BleDeviceInfo(...);
// 更新设备列表
_devices[deviceInfo.id] = deviceInfo;
}
});
});
setState(() {
_isScanning = true;
_isLoading = false;
_scanStatus = '扫描中...';
});
} catch (e) {
// 错误处理
_handleScanError(e);
}
}
关键点说明:
- 扫描前必须检查权限和蓝牙状态
- 使用Stream监听扫描结果,实时更新UI
- 设置超时机制避免长时间扫描
- 对扫描结果进行过滤和去重处理
3.6 设备连接管理
连接设备的实现:
dart复制Future<void> _connectToDevice(BleDeviceInfo deviceInfo) async {
if (_isDeviceConnected(deviceInfo.id)) {
_showConnectedDeviceOptions(deviceInfo);
return;
}
// 显示加载对话框
showDialog(...);
try {
// 发起连接
await deviceInfo.device.connect(
autoConnect: false,
timeout: Duration(seconds: 10),
);
// 更新连接状态
_connectedDevices = await FlutterBluePlus.connectedDevices;
// 显示成功提示
ScaffoldMessenger.of(context).showSnackBar(...);
// 刷新UI
setState(() {});
} catch (e) {
// 错误处理
_handleConnectionError(e);
} finally {
Navigator.pop(context);
}
}
连接优化技巧:
- 设置合理的连接超时时间(通常10-15秒)
- 禁用autoConnect以获得更快的首次连接速度
- 连接成功后立即获取服务特征,避免延迟
4. UI设计与实现
4.1 设备列表项设计
dart复制Widget _buildDeviceItem(BleDeviceInfo device) {
bool isConnected = _isDeviceConnected(device.id);
return Card(
child: ListTile(
leading: _buildDeviceIcon(device, isConnected),
title: _buildDeviceTitle(device, isConnected),
subtitle: _buildDeviceSubtitle(device),
trailing: _buildActionButton(device, isConnected),
onTap: () => _connectToDevice(device),
onLongPress: () => _showDeviceDetails(device),
),
);
}
UI设计要点:
- 使用不同颜色区分已连接/未连接设备
- 信号强度用图标和数值双重表示
- 提供长按查看详情功能
- 操作按钮状态随连接状态变化
4.2 状态反馈设计
dart复制// 扫描状态指示器
Text(
_scanStatus,
style: TextStyle(
color: _isScanning ? Colors.blue : Colors.grey[800],
),
)
// 设备数量统计
Text('${_devices.length} 个')
// 控制按钮
ElevatedButton.icon(
icon: Icon(_isScanning ? Icons.stop : Icons.play_arrow),
label: Text(_isScanning ? '停止扫描' : '开始扫描'),
style: ElevatedButton.styleFrom(
backgroundColor: _isScanning ? Colors.red : Colors.blue,
),
)
良好的状态反馈能显著提升用户体验。我在项目中还添加了扫描动画效果,使交互更加生动。
5. 常见问题与解决方案
5.1 扫描不到设备
可能原因:
- 缺少位置权限(Android 6.0+)
- 蓝牙适配器未开启
- 设备不在广播状态
解决方案:
dart复制// 1. 检查权限
if (!await Permission.location.isGranted) {
await Permission.location.request();
}
// 2. 检查蓝牙状态
BluetoothAdapterState state = await FlutterBluePlus.adapterState.first;
if (state != BluetoothAdapterState.on) {
// 提示用户开启蓝牙
}
// 3. 确保设备在广播状态
// 可以尝试使用其他BLE扫描工具验证
5.2 连接不稳定
优化方案:
- 调整连接参数:
dart复制await device.connect(
autoConnect: false,
timeout: Duration(seconds: 15),
);
- 实现自动重连机制:
dart复制device.connectionState.listen((state) {
if (state == BluetoothConnectionState.disconnected) {
// 延迟后尝试重连
Future.delayed(Duration(seconds: 2), () => device.connect());
}
});
5.3 iOS与Android差异处理
主要差异:
- 权限模型不同
- 后台行为限制不同
- 设备标识符不同
兼容方案:
dart复制String getDeviceId(BluetoothDevice device) {
if (Platform.isIOS) {
return device.remoteId.uuidString;
} else {
return device.remoteId.str;
}
}
6. 性能优化建议
-
扫描优化:
- 设置合理的扫描时间(通常15-30秒)
- 在UI不可见时停止扫描
- 使用扫描过滤器减少不必要的结果
-
内存管理:
dart复制@override void dispose() { _scanSubscription?.cancel(); _scanTimeoutTimer?.cancel(); _bluetoothStateSubscription?.cancel(); super.dispose(); } -
UI性能:
- 使用ListView.builder构建长列表
- 避免在setState中处理大量数据
- 对设备列表进行分页加载
7. 项目扩展方向
-
服务发现:
dart复制List<BluetoothService> services = await device.discoverServices(); -
数据读写:
dart复制List<int> value = await characteristic.read(); await characteristic.write(data); -
通知监听:
dart复制characteristic.setNotifyValue(true); characteristic.value.listen((data) { // 处理数据 }); -
OTA升级:实现固件无线升级功能
-
状态管理升级:引入Riverpod或Bloc管理复杂状态
8. 开发调试技巧
-
日志记录:
dart复制
FlutterBluePlus.setLogLevel(LogLevel.verbose); -
测试工具:
- nRF Connect(通用BLE调试工具)
- LightBlue(iOS平台调试工具)
- Bluetooth LE Scanner(Android平台调试工具)
-
常见调试场景:
- 使用真实设备调试(模拟器支持有限)
- 准备多个测试设备(不同厂商/型号)
- 测试各种信号强度下的表现
在实际开发中,我发现保持蓝牙调试日志的详细记录能极大提高问题排查效率。建议在开发阶段开启详细日志,发布时再调整到适当级别。