Flutter调用手机原生功能之BLE低功耗蓝牙功能开发

230次阅读
没有评论

Flutter 蓝牙开发指南 (flutter_blue)

flutter_blue 是 Flutter 中用于蓝牙低功耗(BLE)开发的流行插件。以下是完整的实现指南:

1. 添加依赖和配置

1.1 添加依赖

pubspec.yaml 中添加:

dependencies:
  flutter_blue: ^0.8.0  # 请使用最新版本
  permission_handler: ^10.2.0  # 用于权限管理

运行 flutter pub get

1.2 平台配置

Android 配置

  1. android/app/src/main/AndroidManifest.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" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
  1. android/app/build.gradle 中设置 minSdkVersion 至少为 21

iOS 配置

ios/Runner/Info.plist 中添加:

<key>NSBluetoothAlwaysUsageDescription</key>
<string>需要蓝牙权限以连接设备</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>需要蓝牙权限以连接设备</string>

2. 基本使用

2.1 初始化蓝牙适配器

import 'package:flutter_blue/flutter_blue.dart';

FlutterBlue flutterBlue = FlutterBlue.instance;

Future<void> initBluetooth() async {
  // 检查蓝牙是否可用
  bool isAvailable = await flutterBlue.isAvailable;
  if (!isAvailable) {
    print("蓝牙不可用");
    return;
  }

  // 检查蓝牙是否开启
  bool isOn = await flutterBlue.isOn;
  if (!isOn) {
    print("蓝牙未开启");
    // 可以提示用户开启蓝牙
    // 在Android上可以跳转到蓝牙设置
    // 在iOS上无法以编程方式开启蓝牙
    return;
  }
}

2.2 扫描蓝牙设备

List<ScanResult> scanResults = [];
StreamSubscription? scanSubscription;

void startScan() {
  // 停止之前的扫描
  stopScan();

  // 请求位置权限 (Android需要)
  if (await Permission.location.request().isGranted) {
    scanSubscription = flutterBlue.scan(
      timeout: Duration(seconds: 10),  // 扫描超时时间
    ).listen((scanResult) {
      // 发现设备
      setState(() {
        scanResults.add(scanResult);
      });
    }, onError: (error) {
      print("扫描错误: $error");
    });
  }
}

void stopScan() {
  scanSubscription?.cancel();
  scanSubscription = null;
  flutterBlue.stopScan();
}

2.3 显示扫描结果

ListView.builder(
  itemCount: scanResults.length,
  itemBuilder: (context, index) {
    ScanResult result = scanResults[index];
    BluetoothDevice device = result.device;
    return ListTile(
      title: Text(device.name.isEmpty ? '未知设备' : device.name),
      subtitle: Text(device.id.toString()),
      trailing: Text(result.rssi.toString()),
      onTap: () => connectToDevice(device),
    );
  },
)

3. 设备连接与通信

3.1 连接设备

BluetoothDevice? connectedDevice;
StreamSubscription? connectionSubscription;

Future<void> connectToDevice(BluetoothDevice device) async {
  // 断开现有连接
  if (connectedDevice != null) {
    await disconnectFromDevice();
  }

  connectionSubscription = device.state.listen((state) {
    print("设备状态: $state");
    if (state == BluetoothDeviceState.connected) {
      setState(() {
        connectedDevice = device;
      });
      discoverServices(device);
    } else if (state == BluetoothDeviceState.disconnected) {
      setState(() {
        connectedDevice = null;
      });
    }
  });

  try {
    await device.connect(autoConnect: false, timeout: Duration(seconds: 15));
    print("连接成功");
  } catch (e) {
    print("连接失败: $e");
  }
}

Future<void> disconnectFromDevice() async {
  if (connectedDevice != null) {
    await connectedDevice!.disconnect();
    connectionSubscription?.cancel();
    connectionSubscription = null;
    setState(() {
      connectedDevice = null;
    });
  }
}

3.2 发现服务

List<BluetoothService> services = [];

Future<void> discoverServices(BluetoothDevice device) async {
  try {
    services = await device.discoverServices();
    setState(() {});
    print("发现 ${services.length} 个服务");
  } catch (e) {
    print("发现服务失败: $e");
  }
}

3.3 读取特征值

Future<void> readCharacteristic(BluetoothCharacteristic characteristic) async {
  try {
    List<int> value = await characteristic.read();
    print("特征值: $value");
    // 处理读取到的数据
  } catch (e) {
    print("读取特征值失败: $e");
  }
}

3.4 写入特征值

Future<void> writeCharacteristic(
  BluetoothCharacteristic characteristic, 
  List<int> value,
) async {
  try {
    await characteristic.write(value);
    print("写入成功");
  } catch (e) {
    print("写入失败: $e");
  }
}

3.5 监听通知

StreamSubscription? notifySubscription;

void listenToCharacteristic(BluetoothCharacteristic characteristic) {
  notifySubscription?.cancel();
  
  notifySubscription = characteristic.value.listen((value) {
    print("收到通知: $value");
    // 处理通知数据
  });

  characteristic.setNotifyValue(true);
}

void stopListening() {
  notifySubscription?.cancel();
  notifySubscription = null;
}

4. 完整示例

import 'package:flutter/material.dart';
import 'package:flutter_blue/flutter_blue.dart';
import 'package:permission_handler/permission_handler.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '蓝牙示例',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: BluetoothScreen(),
    );
  }
}

class BluetoothScreen extends StatefulWidget {
  @override
  _BluetoothScreenState createState() => _BluetoothScreenState();
}

class _BluetoothScreenState extends State<BluetoothScreen> {
  FlutterBlue flutterBlue = FlutterBlue.instance;
  List<ScanResult> scanResults = [];
  StreamSubscription? scanSubscription;
  BluetoothDevice? connectedDevice;
  StreamSubscription? connectionSubscription;
  List<BluetoothService> services = [];
  StreamSubscription? notifySubscription;

  @override
  void initState() {
    super.initState();
    initBluetooth();
  }

  @override
  void dispose() {
    stopScan();
    disconnectFromDevice();
    stopListening();
    super.dispose();
  }

  Future<void> initBluetooth() async {
    bool isAvailable = await flutterBlue.isAvailable;
    if (!isAvailable) {
      print("蓝牙不可用");
      return;
    }

    bool isOn = await flutterBlue.isOn;
    if (!isOn) {
      print("蓝牙未开启");
      return;
    }
  }

  void startScan() {
    stopScan();
    
    Permission.location.request().then((status) {
      if (status.isGranted) {
        scanSubscription = flutterBlue.scan(timeout: Duration(seconds: 10))
            .listen((scanResult) {
          setState(() {
            if (!scanResults.any((result) => result.device.id == scanResult.device.id)) {
              scanResults.add(scanResult);
            }
          });
        }, onError: (error) {
          print("扫描错误: $error");
        });
      }
    });
  }

  void stopScan() {
    scanSubscription?.cancel();
    scanSubscription = null;
    flutterBlue.stopScan();
  }

  Future<void> connectToDevice(BluetoothDevice device) async {
    if (connectedDevice != null) {
      await disconnectFromDevice();
    }

    connectionSubscription = device.state.listen((state) {
      print("设备状态: $state");
      if (state == BluetoothDeviceState.connected) {
        setState(() {
          connectedDevice = device;
        });
        discoverServices(device);
      } else if (state == BluetoothDeviceState.disconnected) {
        setState(() {
          connectedDevice = null;
          services.clear();
        });
      }
    });

    try {
      await device.connect(autoConnect: false, timeout: Duration(seconds: 15));
      print("连接成功");
    } catch (e) {
      print("连接失败: $e");
    }
  }

  Future<void> disconnectFromDevice() async {
    if (connectedDevice != null) {
      await connectedDevice!.disconnect();
      connectionSubscription?.cancel();
      connectionSubscription = null;
      setState(() {
        connectedDevice = null;
        services.clear();
      });
    }
  }

  Future<void> discoverServices(BluetoothDevice device) async {
    try {
      List<BluetoothService> discoveredServices = await device.discoverServices();
      setState(() {
        services = discoveredServices;
      });
      print("发现 ${services.length} 个服务");
    } catch (e) {
      print("发现服务失败: $e");
    }
  }

  void listenToCharacteristic(BluetoothCharacteristic characteristic) {
    stopListening();
    
    notifySubscription = characteristic.value.listen((value) {
      print("收到通知: $value");
    });

    characteristic.setNotifyValue(true);
  }

  void stopListening() {
    notifySubscription?.cancel();
    notifySubscription = null;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('蓝牙示例')),
      body: SingleChildScrollView(
        child: Column(
          children: [
            _buildControlPanel(),
            _buildScanResults(),
            _buildConnectedDeviceInfo(),
            _buildServicesList(),
          ],
        ),
      ),
    );
  }

  Widget _buildControlPanel() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            ElevatedButton(
              onPressed: startScan,
              child: Text('开始扫描'),
            ),
            ElevatedButton(
              onPressed: stopScan,
              child: Text('停止扫描'),
            ),
            if (connectedDevice != null)
              ElevatedButton(
                onPressed: disconnectFromDevice,
                child: Text('断开连接'),
              ),
          ],
        ),
      ),
    );
  }

  Widget _buildScanResults() {
    return ExpansionTile(
      title: Text('扫描结果 (${scanResults.length})'),
      children: [
        ListView.builder(
          shrinkWrap: true,
          physics: NeverScrollableScrollPhysics(),
          itemCount: scanResults.length,
          itemBuilder: (context, index) {
            ScanResult result = scanResults[index];
            BluetoothDevice device = result.device;
            return ListTile(
              title: Text(device.name.isEmpty ? '未知设备' : device.name),
              subtitle: Text(device.id.toString()),
              trailing: Text(result.rssi.toString()),
              onTap: () => connectToDevice(device),
            );
          },
        ),
      ],
    );
  }

  Widget _buildConnectedDeviceInfo() {
    if (connectedDevice == null) return Container();
    
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('已连接设备:', style: TextStyle(fontWeight: FontWeight.bold)),
            Text('名称: ${connectedDevice!.name}'),
            Text('ID: ${connectedDevice!.id}'),
          ],
        ),
      ),
    );
  }

  Widget _buildServicesList() {
    if (services.isEmpty) return Container();
    
    return ExpansionTile(
      title: Text('服务 (${services.length})'),
      children: [
        ListView.builder(
          shrinkWrap: true,
          physics: NeverScrollableScrollPhysics(),
          itemCount: services.length,
          itemBuilder: (context, serviceIndex) {
            BluetoothService service = services[serviceIndex];
            return ExpansionTile(
              title: Text('服务: ${service.uuid}'),
              children: service.characteristics.map((characteristic) {
                return ListTile(
                  title: Text('特征: ${characteristic.uuid}'),
                  subtitle: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text('属性: ${_getProperties(characteristic.properties)}'),
                      if (characteristic.value != null)
                        Text('值: ${characteristic.value}'),
                    ],
                  ),
                  onTap: () {
                    if (characteristic.properties.notify) {
                      listenToCharacteristic(characteristic);
                    } else if (characteristic.properties.read) {
                      characteristic.read();
                    }
                  },
                );
              }).toList(),
            );
          },
        ),
      ],
    );
  }

  String _getProperties(BluetoothCharacteristicProperties properties) {
    List<String> props = [];
    if (properties.broadcast) props.add('Broadcast');
    if (properties.read) props.add('Read');
    if (properties.writeWithoutResponse) props.add('WriteWithoutResponse');
    if (properties.write) props.add('Write');
    if (properties.notify) props.add('Notify');
    if (properties.indicate) props.add('Indicate');
    if (properties.authenticatedSignedWrites) props.add('AuthenticatedSignedWrites');
    if (properties.extendedProperties) props.add('ExtendedProperties');
    return props.join(', ');
  }
}

5. 常见问题解决

  1. ​Android 上扫描不到设备​​:
    • 确保已授予位置权限
    • 检查设备是否可被发现
    • 在 Android 6.0+ 需要运行时位置权限
  2. ​iOS 上连接失败​​:
    • 确保在 Info.plist 中添加了蓝牙权限描述
    • iOS 需要设备在蓝牙设置中配对
  3. ​特征值操作失败​​:
    • 检查特征是否支持该操作(读/写/通知)
    • 确保设备已连接且服务已发现
  4. ​跨平台差异​​:
    • Android 和 iOS 在蓝牙实现上有差异
    • 测试时应在两个平台上都进行验证
  5. ​性能问题​​:
    • 及时取消订阅和断开连接
    • 避免频繁的蓝牙操作

6. 最佳实践

  1. ​权限处理​​:
    • 在操作前检查并请求必要权限
    • 处理用户拒绝权限的情况
  2. ​错误处理​​:
    • 捕获所有可能的异常
    • 提供用户友好的错误提示
  3. ​资源管理​​:
    • 及时取消订阅和释放资源
    • 在页面销毁时断开连接
  4. ​用户体验​​:
    • 显示连接状态和操作反馈
    • 提供重试机制
  5. ​测试​​:
    • 在不同设备和平台上测试
    • 模拟各种异常情况

通过以上方法,你可以在 Flutter 应用中实现强大的蓝牙功能,与各种 BLE 设备进行交互。

正文完
 0
wujingquan
版权声明:本站原创文章,由 wujingquan 于2025-06-04发表,共计9844字。
转载说明:Unless otherwise specified, all articles are published by cc-4.0 protocol. Please indicate the source of reprint.
评论(没有评论)