配景:
之前用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. 创建一个蓝牙管理类
① 创建一个蓝牙管理类来管理毗连的扫描、毗连、通讯等。
② 创建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[index];
- 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[100] : Colors.grey[200],
- borderRadius: BorderRadius.circular(12)
- ),
- child: Text(
- device.isConnected ? "已连接" : "未连接",
- style: TextStyle(
- color: device.isConnected ? Colors.green[700] : Colors.grey[700],
- fontSize: 14
- ),
- ),
- ),
- onTap: () {
- controller.connectToDevice(context, device);
- },
- ),
- );
- },
- )
- );
- }),
- );
- });
- }
- }
复制代码 ① 创建一个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(); } } @override void onInit() { super.onInit(); PermissionUtil.requestBluetoothConnectPermission();
- } @override void 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 = ""; } @override void onClose() { super.onClose(); } @override void 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); } @override void onConnectSuccess() { print('毗连成功'); } @override void onDisconnected() { print('断开毗连'); SnackBarManager.instance.showSnackBar("蓝牙断开毗连", ""); bluetoothInfo.value.isConnected = false; for (var device in devices) { device.isConnected = false; } bluetoothInfo.refresh(); devices.refresh(); } @override void onConnectFailed(String error) { print('毗连失败: $error'); } @override void 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后进行通讯。
- 实现效果
- demo地点:https://gitee.com/hfyangi/bluetooth
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |