Flutter中BLE蓝牙通讯的实现
配景:之前用java写过安卓端的BLE蓝牙通讯测试的demo,最近学习了Flutter干系知识,准备以BLE蓝牙测试为例,写一个flutte的BLE蓝牙测试demo。之前的demo请参考:安卓BLE蓝牙通讯
实现:
[*]权限
① 在android下的AndroidManifest.xml文件中添加安卓设备所需权限。
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
② 在ios下的Info.plist文件中添加ios设备所需权限。
<key>NSBluetoothAlwaysUsageDescription</key>
<string>需要使用蓝牙来连接设备</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>需要使用蓝牙来连接设备</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要使用位置权限来搜索附近的蓝牙设备</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>需要使用位置权限来搜索附近的蓝牙设备</string>
[*]动态权限申请
① 创建一个PermissionUtil来管理权限获取。
import 'package:permission_handler/permission_handler.dart';
class PermissionUtil {
/// 请求蓝牙和位置信息权限
static Future<bool> requestBluetoothConnectPermission() async {
Map<Permission, PermissionStatus> permission = await [
Permission.bluetoothConnect,
Permission.bluetoothScan,
Permission.bluetoothAdvertise,
Permission.location,
].request();
if (await Permission.bluetoothConnect.isGranted) {
print("蓝牙连接权限申请通过");
} else {
print("蓝牙连接权限申请失败");
return false;
}
if (await Permission.bluetoothScan.isGranted) {
print("蓝牙扫描权限申请通过");
} else {
print("蓝牙扫描权限申请失败");
return false;
}
if (await Permission.bluetoothAdvertise.isGranted) {
print("蓝牙广播权限申请通过");
} else {
print("蓝牙广播权限申请失败");
return false;
}
if (await Permission.location.isGranted) {
print("位置权限申请通过");
} else {
print("位置权限申请失败");
return false;
}
return true;
}
}
② 在主页面中调用动态权限获取
PermissionUtil.requestBluetoothConnectPermission();
③ 在蓝牙扫描时调用权限检测。
// 扫描蓝牙
void scanDevices() {
PermissionUtil.requestBluetoothConnectPermission().then((hasPermission) {
if(hasPermission) {
// 权限获取成功
devices.clear();
BleManager.getInstance().setCallback(this);
BleManager.getInstance().startScan(
timeout: Duration(seconds: 10),
);
} else {
// 权限获取失败
SnackBarManager.instance.showSnackBar("权限获取失败", "请先授予权限");
}
});
}
④ ios设备无需进举措态权限获取。
3. 创建一个蓝牙管理类
① 创建一个蓝牙管理类来管理毗连的扫描、毗连、通讯等。
import 'package:bluetooth/util/constants/ble_config.dart';
import 'package:bluetooth/util/snack_bar_manager.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import '../inter/ble_callback.dart';
class BleManager {
static BleManager? _instance;
BluetoothDevice? _device;
BluetoothCharacteristic? _writeCharacteristic;
BluetoothCharacteristic? _notifyCharacteristic;
// 回调接口
BleCallback? _callback;
// 设置回调
void setCallback(BleCallback callback) {
_callback = callback;
}
// 私有构造函数
BleManager._();
// 单例模式
static BleManager getInstance() {
_instance ??= BleManager._();
return _instance!;
}
// 检查蓝牙是否可用
Future<bool> isAvailable() async {
return await FlutterBluePlus.isAvailable;
}
// 检查蓝牙是否开启
Future<bool> isOn() async {
return await FlutterBluePlus.isOn;
}
// 开始扫描
Future<void> startScan({
Duration? timeout,
List<Guid>? withServices,
}) async {
if (!(await isOn())) {
SnackBarManager.instance.showSnackBar("蓝牙未开启", "请打开蓝牙");
}
// 停止之前的扫描
await stopScan();
// 监听扫描结果
FlutterBluePlus.scanResults.listen((results) {
for (ScanResult result in results) {
// print("扫描结果: ${result.device.name} - ${result.device.remoteId}");
if (_callback != null) {
_callback!.onScanResult(result.device);
}
}
});
// 开始扫描
print("开始扫描");
await FlutterBluePlus.startScan(
timeout: timeout ?? const Duration(seconds: 4),
withServices: withServices ?? [],
);
}
// 停止扫描
Future<void> stopScan() async {
await FlutterBluePlus.stopScan();
}
BluetoothDevice? getDeviceFromAddress(String address) {
try {
BluetoothDevice device = BluetoothDevice.fromId(address);
return device;
} catch (e) {
print('获取设备失败: $e');
return null;
}
}
// 连接设备
Future<bool> connect(BluetoothDevice device) async {
try {
await device.connect(
timeout: const Duration(seconds: 4),
autoConnect: false,
);
_device = device;
// 添加断开连接监听
device.connectionState.listen((BluetoothConnectionState state) {
if (state == BluetoothConnectionState.disconnected) {
// 设备断开连接
if (_callback != null) {
_callback!.onDisconnected();
}
_device = null;
_writeCharacteristic = null;
_notifyCharacteristic = null;
}
});
// 发现服务
List<BluetoothService> services = await device.discoverServices();
for (BluetoothService service in services) {
if (service.uuid.toString() == BleConfig.SERVICE_UUID) {
for (BluetoothCharacteristic characteristic in service.characteristics) {
if (characteristic.uuid.toString() == BleConfig.WRITE_CHARACTERISTIC_UUID) {
_writeCharacteristic = characteristic;
}
if (characteristic.uuid.toString() == BleConfig.NOTIFY_CHARACTERISTIC_UUID) {
_notifyCharacteristic = characteristic;
}
}
}
}
if (_notifyCharacteristic != null) {
// 设置通知
await enableNotification();
if (_callback != null) {
_callback!.onConnectSuccess();
}
return true;
} else {
print("未找到指定特征值");
return false;
}
} catch (e) {
if (_callback != null) {
_callback!.onConnectFailed(e.toString());
}
disconnect();
return false;
}
}
// 断开连接
Future<void> disconnect() async {
if (_device != null) {
await _device!.disconnect();
_device = null;
_notifyCharacteristic = null;
_writeCharacteristic = null;
}
}
// 发送数据
Future<bool> sendData(List<int> data) async {
if (_writeCharacteristic != null) {
await _writeCharacteristic!.write(data);
return true;
} else {
print("未持有WRITE_UUID");
return false;
}
}
// 启用通知
Future<void> enableNotification() async {
if (_notifyCharacteristic != null) {
await _notifyCharacteristic!.setNotifyValue(true);
_notifyCharacteristic!.value.listen((value) {
if (_callback != null) {
_callback!.onDataReceived(value);
}
});
} else {
print("未持有NOTIFY_UUID");
}
}
// 获取连接状态
bool isConnected() {
return _device != null && _notifyCharacteristic != null;
}
}
② 创建BleCallback来处置惩罚毗连回调。
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
mixin BleCallback {
// 扫描结果回调
void onScanResult(BluetoothDevice device);
// 连接成功回调
void onConnectSuccess();
// 断开连接回调
void onDisconnected();
// 连接失败回调
void onConnectFailed(String error);
// 数据接收回调
void onDataReceived(List<int> data);
}
③ 统一管理蓝牙的UUID。
class BleConfig {
// 服务和特征值 UUID
static const String SERVICE_UUID = "0783b03e-8535-b5a0-7140-a304d2495cb7";
static const String WRITE_CHARACTERISTIC_UUID = "0783b03e-8535-b5a0-7140-a304d2495cba";
static const String NOTIFY_CHARACTERISTIC_UUID = "0783b03e-8535-b5a0-7140-a304d2495cb8";
}
[*]创建页面来展示蓝牙通讯
① 创建一个页面来展示蓝牙的毗连和通讯状况。
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../home/home_contolller.dart';
class BluetoothInfoPage extends StatelessWidget {
const BluetoothInfoPage({super.key});
@override
Widget build(BuildContext context) {
return GetBuilder<HomeController>(builder: (controller) {
return Scaffold(
appBar: AppBar(
title: Text('蓝牙信息'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Obx(() =>
Text("连接状态: ${controller.bluetoothInfo.value
.isConnected ? "已连接" : "未连接"}")),
SizedBox(height: 8),
Obx(() =>
Text("蓝牙名称: ${controller.bluetoothInfo.value
.name}")),
SizedBox(height: 8),
Obx(() =>
Text("蓝牙地址: ${controller.bluetoothInfo.value
.address}")),
SizedBox(height: 16),
Row(
children: [
Expanded(
child: TextField(
onChanged: (value) {
controller.sentMessage.value = value; // 更新发送的消息
},
decoration: InputDecoration(
labelText: '发送的报文',
),
),
),
IconButton(
icon: Icon(Icons.send),
onPressed: () {
controller.sendMessage();
},
tooltip: '发送消息',
),
],
),
SizedBox(height: 16),
const Text("接收的报文:", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
// 接收报文内容区域和按钮
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 接收报文内容
Expanded(
child: Container(
height: 200, // 固定高度
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(4),
),
child: Obx(() => SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(controller.receivedMessage.value),
),
)),
),
),
// 右侧按钮
SizedBox(
height: 200,// 与内容区域等高
child: Center(// 使用 Center 包裹按钮
child: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
controller.clearMessage();
},
tooltip: '清空接收内容',
),
),
),
],
),
],
),
),
);
});
}
}
② 创建一个页面来实现蓝牙的扫描和毗连。
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
import 'package:get/get_state_manager/src/simple/get_state.dart';
import '../home/home_contolller.dart';
class BluetoothListPage extends StatelessWidget {
const BluetoothListPage({super.key});
@override
Widget build(BuildContext context) {
return GetBuilder<HomeController>(builder: (controller)
{
return Scaffold(
appBar: AppBar(
title: Text('蓝牙设备列表'),
),
body: Obx(() {
return RefreshIndicator(
onRefresh: () async {
controller.scanDevices(); // 下拉刷新时重新扫描
},
child: ListView.builder(
itemCount: controller.devices.length,
itemBuilder: (context, index) {
final device = controller.devices;
return Card(
margin: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
elevation: 2,
child: ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
title: Text(
device.name,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16
),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 4),
Text(
device.address,
style: TextStyle(fontSize: 14)
),
],
),
trailing: Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: device.isConnected ? Colors.green : Colors.grey,
borderRadius: BorderRadius.circular(12)
),
child: Text(
device.isConnected ? "已连接" : "未连接",
style: TextStyle(
color: device.isConnected ? Colors.green : Colors.grey,
fontSize: 14
),
),
),
onTap: () {
controller.connectToDevice(context, device);
},
),
);
},
)
);
}),
);
});
}
}
[*]创建一个Controller来管理页面的数据。
① 创建一个HomeController来管理页面数据以及蓝牙的现实毗连、通讯等。
import 'package:bluetooth/util/ble_manager.dart';import 'package:bluetooth/util/permission_util.dart';import 'package:bluetooth/util/ua200_receiver.dart';import 'package:flutter/material.dart';import 'package:flutter_blue_plus/flutter_blue_plus.dart';import 'package:get/get.dart';import '../../data/bluetooth_device_info.dart';import '../../inter/ble_callback.dart';import '../../util/snack_bar_manager.dart';import '../../widget/loading_dialog.dart';import '../ble/bluetooth_info_page.dart';import '../ble/bluetooth_list_page.dart';class HomeController extends GetxController with BleCallback {final List<Widget> pageList = [ const BluetoothInfoPage(), const BluetoothListPage(),];/// 当前界面的索引值int currentIndex = 0;var bluetoothInfo = BluetoothDeviceInfo( name: "", address: "", isConnected: false,).obs;var sentMessage = "".obs;var receivedMessage = "".obs;var devices = <BluetoothDeviceInfo>[].obs;changeIndex(int index) { currentIndex = index; update(); if (devices.isEmpty && currentIndex == 1) { scanDevices(); }}@overridevoid onInit() { super.onInit(); PermissionUtil.requestBluetoothConnectPermission();
}@overridevoid onReady() { super.onReady();}// 扫描蓝牙
void scanDevices() {
PermissionUtil.requestBluetoothConnectPermission().then((hasPermission) {
if(hasPermission) {
// 权限获取成功
devices.clear();
BleManager.getInstance().setCallback(this);
BleManager.getInstance().startScan(
timeout: Duration(seconds: 10),
);
} else {
// 权限获取失败
SnackBarManager.instance.showSnackBar("权限获取失败", "请先授予权限");
}
});
}
Future<void> connectToDevice(BuildContext context, BluetoothDeviceInfo device) async { if (device.isConnected) { // 假如设备已毗连,则断开毗连 device.isConnected = false; // 断开毗连 BleManager.getInstance().disconnect(); bluetoothInfo.value = device; devices.refresh(); return; } for (var dev in devices) { dev.isConnected = false; // 先将所有设备的毗连状态设为 false } // 显示毗连中的状态框 // showDialog( // context: context, // barrierDismissible: false, // builder: (context) { // return const LoadingDialog(); // }, // ); LoadingDialog.show("毗连中..."); // 这里可以判断毗连是否成功 var dev = BleManager.getInstance().getDeviceFromAddress(device.address); if (dev != null) { BleManager.getInstance().connect(dev).then((success) { if (success) { // 毗连成功 LoadingDialog.hide(); // 关闭毗连中的状态框 device.isConnected = true; // 将选中的设备毗连状态设为 true devices.remove(device); // 移除该设备 devices.insert(0, device); // 将设备插入到列表顶部 bluetoothInfo.value = device; } else { LoadingDialog.hide(); // 关闭毗连中的状态框 SnackBarManager.instance.showSnackBar("毗连失败", "无法毗连到 ${device.name},请重试。"); } }); } else { LoadingDialog.hide(); // 关闭毗连中的状态框 SnackBarManager.instance.showSnackBar("毗连非常", ""); }}void sendMessage() { // 发送消息的逻辑 print("发送消息: ${sentMessage.value}"); var data = sentMessage.value.codeUnits; BleManager.getInstance().sendData(data).then((result) { if (result) { print("消息发送成功"); } else { print("消息发送失败"); SnackBarManager.instance.showSnackBar("消息发送失败", "请检查蓝牙毗连状态"); } });}void clearMessage() { receivedMessage.value = "";}@overridevoid onClose() { super.onClose();}@overridevoid onScanResult(BluetoothDevice device) { if (device.name.isEmpty) { return; } if (devices.any((dev) => dev.address == device.remoteId.toString())) { return; } print('扫描到设备: ${device.name}, 地点: ${device.remoteId}'); var dev = BluetoothDeviceInfo(name: device.name, address: device.remoteId.toString()); devices.add(dev);}@overridevoid onConnectSuccess() { print('毗连成功');}@overridevoid onDisconnected() { print('断开毗连'); SnackBarManager.instance.showSnackBar("蓝牙断开毗连", ""); bluetoothInfo.value.isConnected = false; for (var device in devices) { device.isConnected = false; } bluetoothInfo.refresh(); devices.refresh();}@overridevoid onConnectFailed(String error) { print('毗连失败: $error');}@overridevoid onDataReceived(List<int> data) { print('收到数据: $data'); var receivedData = Ua200Receiver.getBleData(data); if (receivedData != "") { print('收到数据: $receivedData'); receivedMessage.value = '$receivedData\n${receivedMessage.value}'; }}} onDataReceived接收的数据为byte数组,可根据自己BLE蓝牙协议进行剖析,此demo收发皆使用string转byte后进行通讯。
[*] 实现效果
https://i-blog.csdnimg.cn/direct/cc48cb35c8ec43bb9d887d0cda65a3f6.gif
[*] demo地点:https://gitee.com/hfyangi/bluetooth
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]