跳转到正文
YGJoon
返回

Flutter 与蓝牙设备桥接

编辑文章

故事开始:小王的智能家居项目

小王是一位 IoT 爱好者,他正在开发一个智能家居控制应用,需要通过蓝牙连接各种智能设备。在 Flutter 中实现蓝牙功能时,他发现这比想象中要复杂。

“蓝牙连接涉及设备扫描、配对、连接管理、数据传输等多个环节,而且 Android 和 iOS 的实现差异很大。“小王在技术日记中写道。

第一章:蓝牙技术基础

1.1 蓝牙技术概述

蓝牙是一种短距离无线通信技术,主要分为两种类型:

经典蓝牙(Classic Bluetooth)

低功耗蓝牙(Bluetooth Low Energy, BLE)

1.2 Flutter 蓝牙开发生态

Flutter 中蓝牙开发主要有以下几种方案:

  1. flutter_blue_plus - 最流行的蓝牙插件
  2. flutter_reactive_ble - 专注于 BLE 的插件
  3. 自定义平台通道 - 完全自定义实现

第二章:环境搭建与基础配置

2.1 添加依赖

dependencies:
  flutter:
    sdk: flutter
  flutter_blue_plus: ^1.31.5
  permission_handler: ^11.0.1

2.2 权限配置

Android 权限配置(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.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<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_ADVERTISE" />

iOS 权限配置(ios/Runner/Info.plist)

<key>NSBluetoothAlwaysUsageDescription</key>
<string>此应用需要蓝牙权限来连接智能设备</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>此应用需要蓝牙权限来连接智能设备</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>此应用需要位置权限来扫描蓝牙设备</string>

2.3 权限管理实现

import 'package:permission_handler/permission_handler.dart';

class BluetoothPermissionManager {
  static Future<bool> requestPermissions() async {
    // Android 12+ 需要特殊处理
    if (Platform.isAndroid) {
      final androidInfo = await DeviceInfoPlugin().androidInfo;
      final sdkInt = androidInfo.version.sdkInt;

      if (sdkInt >= 31) {
        // Android 12+
        final bluetoothScan = await Permission.bluetoothScan.request();
        final bluetoothConnect = await Permission.bluetoothConnect.request();
        final bluetoothAdvertise = await Permission.bluetoothAdvertise.request();

        return bluetoothScan.isGranted &&
               bluetoothConnect.isGranted &&
               bluetoothAdvertise.isGranted;
      } else {
        // Android 11及以下
        final bluetooth = await Permission.bluetooth.request();
        final bluetoothAdmin = await Permission.bluetoothAdmin.request();
        final location = await Permission.location.request();

        return bluetooth.isGranted &&
               bluetoothAdmin.isGranted &&
               location.isGranted;
      }
    } else {
      // iOS
      final bluetooth = await Permission.bluetooth.request();
      return bluetooth.isGranted;
    }
  }

  static Future<bool> checkPermissions() async {
    if (Platform.isAndroid) {
      final androidInfo = await DeviceInfoPlugin().androidInfo;
      final sdkInt = androidInfo.version.sdkInt;

      if (sdkInt >= 31) {
        return await Permission.bluetoothScan.isGranted &&
               await Permission.bluetoothConnect.isGranted &&
               await Permission.bluetoothAdvertise.isGranted;
      } else {
        return await Permission.bluetooth.isGranted &&
               await Permission.bluetoothAdmin.isGranted &&
               await Permission.location.isGranted;
      }
    } else {
      return await Permission.bluetooth.isGranted;
    }
  }

  static Future<void> openSettings() async {
    await openAppSettings();
  }
}

第三章:蓝牙设备扫描与发现

3.1 蓝牙状态管理

import 'package:flutter_blue_plus/flutter_blue_plus.dart';

class BluetoothManager {
  static final BluetoothManager _instance = BluetoothManager._internal();
  factory BluetoothManager() => _instance;
  BluetoothManager._internal();

  final StreamController<BluetoothState> _stateController =
      StreamController<BluetoothState>.broadcast();

  Stream<BluetoothState> get bluetoothState => _stateController.stream;

  Future<bool> isBluetoothAvailable() async {
    try {
      await FlutterBluePlus.startScan(timeout: Duration(seconds: 1));
      await FlutterBluePlus.stopScan();
      return true;
    } catch (e) {
      return false;
    }
  }

  Future<bool> isBluetoothEnabled() async {
    final state = await FlutterBluePlus.adapterState.first;
    return state == BluetoothAdapterState.on;
  }

  void startListening() {
    FlutterBluePlus.adapterState.listen((state) {
      _stateController.add(state);
    });
  }

  Future<void> enableBluetooth() async {
    if (Platform.isAndroid) {
      // Android需要用户手动开启
      await FlutterBluePlus.openSettings();
    }
    // iOS会自动提示用户开启蓝牙
  }

  void dispose() {
    _stateController.close();
  }
}

3.2 设备扫描实现

class BluetoothScanner {
  final StreamController<List<BluetoothDevice>> _devicesController =
      StreamController<List<BluetoothDevice>>.broadcast();
  final Set<BluetoothDevice> _discoveredDevices = {};
  bool _isScanning = false;

  Stream<List<BluetoothDevice>> get discoveredDevices => _devicesController.stream;
  bool get isScanning => _isScanning;

  Future<void> startScan({Duration timeout = const Duration(seconds: 10)}) async {
    if (_isScanning) return;

    _isScanning = true;
    _discoveredDevices.clear();

    try {
      // 开始扫描
      await FlutterBluePlus.startScan(
        timeout: timeout,
        androidUsesFineLocation: true,
      );

      // 监听扫描结果
      FlutterBluePlus.scanResults.listen((results) {
        for (ScanResult result in results) {
          if (result.device.name.isNotEmpty ||
              result.device.advertisementData.localName.isNotEmpty) {
            _discoveredDevices.add(result.device);
          }
        }
        _devicesController.add(_discoveredDevices.toList());
      });

      // 扫描超时处理
      Timer(timeout, () {
        if (_isScanning) {
          stopScan();
        }
      });
    } catch (e) {
      _isScanning = false;
      throw BluetoothException('扫描失败:${e.toString()}');
    }
  }

  Future<void> stopScan() async {
    if (!_isScanning) return;

    _isScanning = false;
    await FlutterBluePlus.stopScan();
  }

  Future<void> scanForSpecificDevice({
    required String deviceName,
    Duration timeout = const Duration(seconds: 10),
  }) async {
    if (_isScanning) return;

    _isScanning = true;
    _discoveredDevices.clear();

    try {
      await FlutterBluePlus.startScan(
        timeout: timeout,
        androidUsesFineLocation: true,
      );

      FlutterBluePlus.scanResults.listen((results) {
        for (ScanResult result in results) {
          if (result.device.name == deviceName ||
              result.device.advertisementData.localName == deviceName) {
            _discoveredDevices.add(result.device);
            stopScan(); // 找到目标设备后停止扫描
          }
        }
        _devicesController.add(_discoveredDevices.toList());
      });
    } catch (e) {
      _isScanning = false;
      throw BluetoothException('扫描特定设备失败:${e.toString()}');
    }
  }

  void dispose() {
    _devicesController.close();
  }
}

3.3 设备信息获取

class BluetoothDeviceInfo {
  final BluetoothDevice device;
  final ScanResult? scanResult;

  BluetoothDeviceInfo(this.device, [this.scanResult]);

  String get name => device.name.isNotEmpty
      ? device.name
      : scanResult?.advertisementData.localName ?? '未知设备';

  String get id => device.remoteId.str;

  int get rssi => scanResult?.rssi ?? 0;

  int get signalStrength {
    if (rssi >= -50) return 4; // 信号强
    if (rssi >= -70) return 3; // 信号良好
    if (rssi >= -90) return 2; // 信号一般
    return 1; // 信号弱
  }

  List<String> get serviceUuids =>
      scanResult?.advertisementData.serviceUuids.map((uuid) => uuid.str).toList() ?? [];

  Map<String, dynamic> get manufacturerData =>
      scanResult?.advertisementData.manufacturerData ?? {};

  bool get isConnectable => scanResult?.advertisementData.connectable ?? true;

  String get signalStrengthText {
    switch (signalStrength) {
      case 4: return '信号强';
      case 3: return '信号良好';
      case 2: return '信号一般';
      case 1: return '信号弱';
      default: return '无信号';
    }
  }
}

class DeviceInfoProvider {
  static Future<BluetoothDeviceInfo> getDeviceInfo(BluetoothDevice device) async {
    try {
      // 连接设备获取更多信息
      await device.connect();

      // 获取设备服务
      List<BluetoothService> services = await device.discoverServices();

      // 获取设备名称
      String deviceName = device.name;
      if (deviceName.isEmpty) {
        // 尝试从设备信息服务获取名称
        for (BluetoothService service in services) {
          if (service.uuid.toString() == '0000180a-0000-1000-8000-00805f9b34fb') {
            for (BluetoothCharacteristic characteristic in service.characteristics) {
              if (characteristic.uuid.toString() == '00002a00-0000-1000-8000-00805f9b34fb') {
                var value = await characteristic.read();
                deviceName = String.fromCharCodes(value);
                break;
              }
            }
          }
        }
      }

      await device.disconnect();

      return BluetoothDeviceInfo(device);
    } catch (e) {
      return BluetoothDeviceInfo(device);
    }
  }
}

第四章:设备连接与通信

4.1 设备连接管理

class BluetoothConnectionManager {
  final Map<String, BluetoothConnection> _connections = {};
  final StreamController<BluetoothConnectionEvent> _eventController =
      StreamController<BluetoothConnectionEvent>.broadcast();

  Stream<BluetoothConnectionEvent> get connectionEvents => _eventController.stream;

  Future<BluetoothConnection> connectToDevice(BluetoothDevice device) async {
    final deviceId = device.remoteId.str;

    // 检查是否已连接
    if (_connections.containsKey(deviceId)) {
      return _connections[deviceId]!;
    }

    try {
      // 连接设备
      await device.connect(timeout: Duration(seconds: 15));

      // 发现服务
      List<BluetoothService> services = await device.discoverServices();

      // 创建连接对象
      final connection = BluetoothConnection(
        device: device,
        services: services,
      );

      _connections[deviceId] = connection;

      _eventController.add(BluetoothConnectionEvent(
        type: ConnectionEventType.connected,
        deviceId: deviceId,
        connection: connection,
      ));

      // 监听连接状态
      device.connectionState.listen((state) {
        if (state == BluetoothConnectionState.disconnected) {
          _connections.remove(deviceId);
          _eventController.add(BluetoothConnectionEvent(
            type: ConnectionEventType.disconnected,
            deviceId: deviceId,
          ));
        }
      });

      return connection;
    } catch (e) {
      throw BluetoothException('连接设备失败:${e.toString()}');
    }
  }

  Future<void> disconnectFromDevice(String deviceId) async {
    final connection = _connections[deviceId];
    if (connection != null) {
      await connection.device.disconnect();
      _connections.remove(deviceId);

      _eventController.add(BluetoothConnectionEvent(
        type: ConnectionEventType.disconnected,
        deviceId: deviceId,
      ));
    }
  }

  Future<void> disconnectAll() async {
    final connections = List.from(_connections.values);
    for (final connection in connections) {
      await connection.device.disconnect();
    }
    _connections.clear();
  }

  BluetoothConnection? getConnection(String deviceId) {
    return _connections[deviceId];
  }

  List<BluetoothConnection> getAllConnections() {
    return _connections.values.toList();
  }

  bool isConnected(String deviceId) {
    return _connections.containsKey(deviceId);
  }

  void dispose() {
    _eventController.close();
  }
}

class BluetoothConnection {
  final BluetoothDevice device;
  final List<BluetoothService> services;
  final Map<String, BluetoothCharacteristic> _characteristics = {};

  BluetoothConnection({
    required this.device,
    required this.services,
  }) {
    _cacheCharacteristics();
  }

  void _cacheCharacteristics() {
    for (final service in services) {
      for (final characteristic in service.characteristics) {
        _characteristics[characteristic.uuid.str] = characteristic;
      }
    }
  }

  BluetoothCharacteristic? getCharacteristic(String uuid) {
    return _characteristics[uuid];
  }

  List<BluetoothCharacteristic> getCharacteristicsByService(String serviceUuid) {
    final service = services.firstWhere(
      (s) => s.uuid.str == serviceUuid,
      orElse: () => throw Exception('服务未找到:$serviceUuid'),
    );
    return service.characteristics;
  }

  Future<List<int>> readCharacteristic(String uuid) async {
    final characteristic = getCharacteristic(uuid);
    if (characteristic == null) {
      throw BluetoothException('特征值未找到:$uuid');
    }

    if (!characteristic.properties.read) {
      throw BluetoothException('特征值不支持读取:$uuid');
    }

    return await characteristic.read();
  }

  Future<void> writeCharacteristic(String uuid, List<int> value) async {
    final characteristic = getCharacteristic(uuid);
    if (characteristic == null) {
      throw BluetoothException('特征值未找到:$uuid');
    }

    if (!characteristic.properties.write) {
      throw BluetoothException('特征值不支持写入:$uuid');
    }

    await characteristic.write(value);
  }

  Future<void> notifyCharacteristic(String uuid, bool enable) async {
    final characteristic = getCharacteristic(uuid);
    if (characteristic == null) {
      throw BluetoothException('特征值未找到:$uuid');
    }

    if (!characteristic.properties.notify && !characteristic.properties.indicate) {
      throw BluetoothException('特征值不支持通知:$uuid');
    }

    await characteristic.setNotifyValue(enable);
  }

  Stream<List<int>>? getCharacteristicNotifications(String uuid) {
    final characteristic = getCharacteristic(uuid);
    if (characteristic == null) {
      throw BluetoothException('特征值未找到:$uuid');
    }

    return characteristic.value;
  }
}

enum ConnectionEventType {
  connected,
  disconnected,
  connectionFailed,
}

class BluetoothConnectionEvent {
  final ConnectionEventType type;
  final String deviceId;
  final BluetoothConnection? connection;
  final String? error;

  BluetoothConnectionEvent({
    required this.type,
    required this.deviceId,
    this.connection,
    this.error,
  });
}

4.2 数据传输协议

abstract class BluetoothProtocol {
  Future<void> sendData(BluetoothConnection connection, dynamic data);
  Future<dynamic> receiveData(BluetoothConnection connection);
}

class SimpleTextProtocol implements BluetoothProtocol {
  final String serviceUuid;
  final String writeCharacteristicUuid;
  final String readCharacteristicUuid;

  SimpleTextProtocol({
    required this.serviceUuid,
    required this.writeCharacteristicUuid,
    required this.readCharacteristicUuid,
  });

  @override
  Future<void> sendData(BluetoothConnection connection, String data) async {
    final bytes = utf8.encode(data);
    await connection.writeCharacteristic(writeCharacteristicUuid, bytes);
  }

  @override
  Future<String> receiveData(BluetoothConnection connection) async {
    final bytes = await connection.readCharacteristic(readCharacteristicUuid);
    return utf8.decode(bytes);
  }

  Stream<String> receiveDataStream(BluetoothConnection connection) {
    return connection
        .getCharacteristicNotifications(readCharacteristicUuid)!
        .map((bytes) => utf8.decode(bytes));
  }
}

class BinaryProtocol implements BluetoothProtocol {
  final String serviceUuid;
  final String writeCharacteristicUuid;
  final String readCharacteristicUuid;

  BinaryProtocol({
    required this.serviceUuid,
    required this.writeCharacteristicUuid,
    required this.readCharacteristicUuid,
  });

  @override
  Future<void> sendData(BluetoothConnection connection, List<int> data) async {
    // 添加数据包头(长度信息)
    final packet = _createPacket(data);
    await connection.writeCharacteristic(writeCharacteristicUuid, packet);
  }

  @override
  Future<List<int>> receiveData(BluetoothConnection connection) async {
    final packet = await connection.readCharacteristic(readCharacteristicUuid);
    return _parsePacket(packet);
  }

  Stream<List<int>> receiveDataStream(BluetoothConnection connection) {
    return connection
        .getCharacteristicNotifications(readCharacteristicUuid)!
        .map(_parsePacket);
  }

  List<int> _createPacket(List<int> data) {
    // 简单的协议:[长度(2字节)][数据]
    final length = data.length;
    final packet = <int>[];
    packet.add((length >> 8) & 0xFF); // 高字节
    packet.add(length & 0xFF); // 低字节
    packet.addAll(data);
    return packet;
  }

  List<int> _parsePacket(List<int> packet) {
    if (packet.length < 2) {
      throw BluetoothException('数据包格式错误');
    }

    final length = (packet[0] << 8) | packet[1];
    if (packet.length < length + 2) {
      throw BluetoothException('数据包不完整');
    }

    return packet.sublist(2, 2 + length);
  }
}

第五章:实际应用案例

5.1 智能灯泡控制

class SmartLightController {
  final BluetoothConnectionManager _connectionManager;
  final SimpleTextProtocol _protocol;

  SmartLightController(this._connectionManager)
      : _protocol = SimpleTextProtocol(
          serviceUuid: '12345678-1234-1234-1234-123456789abc',
          writeCharacteristicUuid: '12345678-1234-1234-1234-123456789abd',
          readCharacteristicUuid: '12345678-1234-1234-1234-123456789abe',
        );

  Future<void> turnOn(String deviceId) async {
    final connection = _connectionManager.getConnection(deviceId);
    if (connection == null) {
      throw BluetoothException('设备未连接');
    }

    await _protocol.sendData(connection, 'ON');
  }

  Future<void> turnOff(String deviceId) async {
    final connection = _connectionManager.getConnection(deviceId);
    if (connection == null) {
      throw BluetoothException('设备未连接');
    }

    await _protocol.sendData(connection, 'OFF');
  }

  Future<void> setBrightness(String deviceId, int brightness) async {
    final connection = _connectionManager.getConnection(deviceId);
    if (connection == null) {
      throw BluetoothException('设备未连接');
    }

    final command = 'BRIGHTNESS:$brightness';
    await _protocol.sendData(connection, command);
  }

  Future<void> setColor(String deviceId, Color color) async {
    final connection = _connectionManager.getConnection(deviceId);
    if (connection == null) {
      throw BluetoothException('设备未连接');
    }

    final r = color.red;
    final g = color.green;
    final b = color.blue;
    final command = 'COLOR:$r,$g,$b';
    await _protocol.sendData(connection, command);
  }

  Future<String> getStatus(String deviceId) async {
    final connection = _connectionManager.getConnection(deviceId);
    if (connection == null) {
      throw BluetoothException('设备未连接');
    }

    return await _protocol.receiveData(connection);
  }
}

5.2 健康监测设备

class HealthMonitorDevice {
  final BluetoothConnectionManager _connectionManager;
  final BinaryProtocol _protocol;

  HealthMonitorDevice(this._connectionManager)
      : _protocol = BinaryProtocol(
          serviceUuid: '87654321-4321-4321-4321-210987654321',
          writeCharacteristicUuid: '87654321-4321-4321-4321-210987654322',
          readCharacteristicUuid: '87654321-4321-4321-4321-210987654323',
        );

  Stream<HealthData> getHealthDataStream(String deviceId) {
    final connection = _connectionManager.getConnection(deviceId);
    if (connection == null) {
      throw BluetoothException('设备未连接');
    }

    return _protocol
        .receiveDataStream(connection)
        .map(_parseHealthData);
  }

  HealthData _parseHealthData(List<int> data) {
    if (data.length < 8) {
      throw BluetoothException('健康数据格式错误');
    }

    // 解析心率数据
    final heartRate = data[0];

    // 解析血氧数据
    final bloodOxygen = data[1];

    // 解析步数数据(4字节)
    final steps = (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7];

    return HealthData(
      heartRate: heartRate,
      bloodOxygen: bloodOxygen,
      steps: steps,
      timestamp: DateTime.now(),
    );
  }

  Future<void> startMonitoring(String deviceId) async {
    final connection = _connectionManager.getConnection(deviceId);
    if (connection == null) {
      throw BluetoothException('设备未连接');
    }

    // 发送开始监测命令
    await _protocol.sendData(connection, [0x01]);

    // 启用通知
    await connection.notifyCharacteristic(
      '87654321-4321-4321-4321-210987654323',
      true,
    );
  }

  Future<void> stopMonitoring(String deviceId) async {
    final connection = _connectionManager.getConnection(deviceId);
    if (connection == null) {
      throw BluetoothException('设备未连接');
    }

    // 发送停止监测命令
    await _protocol.sendData(connection, [0x00]);

    // 禁用通知
    await connection.notifyCharacteristic(
      '87654321-4321-4321-4321-210987654323',
      false,
    );
  }
}

class HealthData {
  final int heartRate;
  final int bloodOxygen;
  final int steps;
  final DateTime timestamp;

  HealthData({
    required this.heartRate,
    required this.bloodOxygen,
    required this.steps,
    required this.timestamp,
  });

  Map<String, dynamic> toJson() {
    return {
      'heartRate': heartRate,
      'bloodOxygen': bloodOxygen,
      'steps': steps,
      'timestamp': timestamp.toIso8601String(),
    };
  }
}

第六章:UI 实现

6.1 设备扫描页面

class BluetoothScanPage extends StatefulWidget {
  @override
  _BluetoothScanPageState createState() => _BluetoothScanPageState();
}

class _BluetoothScanPageState extends State<BluetoothScanPage> {
  final BluetoothScanner _scanner = BluetoothScanner();
  final BluetoothManager _bluetoothManager = BluetoothManager();
  List<BluetoothDeviceInfo> _devices = [];
  bool _isScanning = false;

  @override
  void initState() {
    super.initState();
    _bluetoothManager.startListening();
    _scanner.discoveredDevices.listen((devices) {
      setState(() {
        _devices = devices.map((device) => BluetoothDeviceInfo(device)).toList();
      });
    });
  }

  @override
  void dispose() {
    _scanner.dispose();
    _bluetoothManager.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('蓝牙设备扫描'),
        actions: [
          IconButton(
            icon: Icon(_isScanning ? Icons.stop : Icons.search),
            onPressed: _toggleScanning,
          ),
        ],
      ),
      body: Column(
        children: [
          _buildBluetoothStatus(),
          Expanded(child: _buildDeviceList()),
        ],
      ),
    );
  }

  Widget _buildBluetoothStatus() {
    return StreamBuilder<BluetoothState>(
      stream: _bluetoothManager.bluetoothState,
      builder: (context, snapshot) {
        final state = snapshot.data ?? BluetoothAdapterState.unknown;

        Color statusColor;
        String statusText;
        IconData statusIcon;

        switch (state) {
          case BluetoothAdapterState.on:
            statusColor = Colors.green;
            statusText = '蓝牙已开启';
            statusIcon = Icons.bluetooth;
            break;
          case BluetoothAdapterState.off:
            statusColor = Colors.red;
            statusText = '蓝牙已关闭';
            statusIcon = Icons.bluetooth_disabled;
            break;
          case BluetoothAdapterState.unavailable:
            statusColor = Colors.grey;
            statusText = '蓝牙不可用';
            statusIcon = Icons.bluetooth_disabled;
            break;
          default:
            statusColor = Colors.grey;
            statusText = '蓝牙状态未知';
            statusIcon = Icons.bluetooth_disabled;
        }

        return Container(
          padding: EdgeInsets.all(16),
          color: statusColor.withOpacity(0.1),
          child: Row(
            children: [
              Icon(statusIcon, color: statusColor),
              SizedBox(width: 12),
              Text(
                statusText,
                style: TextStyle(
                  color: statusColor,
                  fontWeight: FontWeight.bold,
                ),
              ),
              Spacer(),
              if (state == BluetoothAdapterState.off)
                TextButton(
                  onPressed: _enableBluetooth,
                  child: Text('开启蓝牙'),
                ),
            ],
          ),
        );
      },
    );
  }

  Widget _buildDeviceList() {
    if (_devices.isEmpty && !_isScanning) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(Icons.bluetooth_searching, size: 64, color: Colors.grey),
            SizedBox(height: 16),
            Text(
              '未发现设备',
              style: TextStyle(fontSize: 18, color: Colors.grey),
            ),
            SizedBox(height: 16),
            ElevatedButton(
              onPressed: _startScan,
              child: Text('开始扫描'),
            ),
          ],
        ),
      );
    }

    if (_devices.isEmpty && _isScanning) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            CircularProgressIndicator(),
            SizedBox(height: 16),
            Text('正在扫描设备...'),
          ],
        ),
      );
    }

    return ListView.builder(
      itemCount: _devices.length,
      itemBuilder: (context, index) {
        final device = _devices[index];
        return DeviceCard(
          device: device,
          onTap: () => _connectToDevice(device),
        );
      },
    );
  }

  void _toggleScanning() async {
    if (_isScanning) {
      await _scanner.stopScan();
      setState(() {
        _isScanning = false;
      });
    } else {
      await _startScan();
    }
  }

  Future<void> _startScan() async {
    final hasPermission = await BluetoothPermissionManager.checkPermissions();
    if (!hasPermission) {
      final granted = await BluetoothPermissionManager.requestPermissions();
      if (!granted) {
        _showPermissionDialog();
        return;
      }
    }

    final isEnabled = await _bluetoothManager.isBluetoothEnabled();
    if (!isEnabled) {
      await _bluetoothManager.enableBluetooth();
      return;
    }

    await _scanner.startScan();
    setState(() {
      _isScanning = true;
    });
  }

  Future<void> _enableBluetooth() async {
    await _bluetoothManager.enableBluetooth();
  }

  void _connectToDevice(BluetoothDeviceInfo device) async {
    try {
      final connectionManager = Provider.of<BluetoothConnectionManager>(
        context,
        listen: false,
      );

      await connectionManager.connectToDevice(device.device);

      Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => DeviceControlPage(device: device),
        ),
      );
    } catch (e) {
      _showErrorDialog('连接失败', e.toString());
    }
  }

  void _showPermissionDialog() {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('需要权限'),
        content: Text('应用需要蓝牙权限才能扫描和连接设备'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: Text('取消'),
          ),
          TextButton(
            onPressed: () {
              Navigator.pop(context);
              BluetoothPermissionManager.openSettings();
            },
            child: Text('去设置'),
          ),
        ],
      ),
    );
  }

  void _showErrorDialog(String title, String message) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text(title),
        content: Text(message),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: Text('确定'),
          ),
        ],
      ),
    );
  }
}

class DeviceCard extends StatelessWidget {
  final BluetoothDeviceInfo device;
  final VoidCallback onTap;

  const DeviceCard({
    required this.device,
    required this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: ListTile(
        leading: CircleAvatar(
          backgroundColor: _getSignalColor(device.signalStrength),
          child: Icon(
            Icons.bluetooth,
            color: Colors.white,
          ),
        ),
        title: Text(
          device.name,
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
        subtitle: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('ID: ${device.id}'),
            Text('信号: ${device.signalStrengthText}'),
            if (device.serviceUuids.isNotEmpty)
              Text('服务: ${device.serviceUuids.length}个'),
          ],
        ),
        trailing: Icon(Icons.chevron_right),
        onTap: onTap,
      ),
    );
  }

  Color _getSignalColor(int strength) {
    switch (strength) {
      case 4: return Colors.green;
      case 3: return Colors.lightGreen;
      case 2: return Colors.orange;
      case 1: return Colors.red;
      default: return Colors.grey;
    }
  }
}

第七章:错误处理与调试

7.1 异常处理

class BluetoothException implements Exception {
  final String message;
  final String? code;
  final dynamic originalError;

  BluetoothException(this.message, {this.code, this.originalError});

  @override
  String toString() {
    return 'BluetoothException: $message${code != null ? ' (Code: $code)' : ''}';
  }
}

class BluetoothErrorHandler {
  static BluetoothException handleError(dynamic error) {
    if (error is BluetoothException) {
      return error;
    }

    if (error.toString().contains('Bluetooth is not available')) {
      return BluetoothException(
        '蓝牙不可用',
        code: 'BLUETOOTH_UNAVAILABLE',
      );
    }

    if (error.toString().contains('Bluetooth is disabled')) {
      return BluetoothException(
        '蓝牙已关闭',
        code: 'BLUETOOTH_DISABLED',
      );
    }

    if (error.toString().contains('Location permission denied')) {
      return BluetoothException(
        '位置权限被拒绝',
        code: 'LOCATION_PERMISSION_DENIED',
      );
    }

    if (error.toString().contains('Connection timeout')) {
      return BluetoothException(
        '连接超时',
        code: 'CONNECTION_TIMEOUT',
      );
    }

    if (error.toString().contains('Device not found')) {
      return BluetoothException(
        '设备未找到',
        code: 'DEVICE_NOT_FOUND',
      );
    }

    return BluetoothException(
      '未知错误:${error.toString()}',
      code: 'UNKNOWN_ERROR',
      originalError: error,
    );
  }
}

7.2 调试工具

class BluetoothLogger {
  static const String _tag = 'Bluetooth';
  static bool _isDebugMode = kDebugMode;

  static void log(String message, {LogLevel level = LogLevel.info}) {
    if (!_isDebugMode) return;

    final timestamp = DateTime.now().toIso8601String();
    final logMessage = '[$timestamp] [$_tag] [${level.name.toUpperCase()}] $message';

    print(logMessage);

    // 可以添加日志文件记录
    _writeToLogFile(logMessage);
  }

  static void logDeviceScan(List<BluetoothDevice> devices) {
    log('扫描完成,发现 ${devices.length} 个设备');
    for (final device in devices) {
      log('设备: ${device.name} (${device.remoteId.str})');
    }
  }

  static void logConnection(String deviceId, bool connected) {
    log('设备 $deviceId ${connected ? '已连接' : '已断开'}');
  }

  static void logDataTransfer(String deviceId, List<int> data, bool isSend) {
    final direction = isSend ? '发送' : '接收';
    final dataHex = data.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ');
    log('$direction数据 [$deviceId]: $dataHex');
  }

  static void logError(String error, StackTrace? stackTrace) {
    log('错误: $error', level: LogLevel.error);
    if (stackTrace != null) {
      log('堆栈跟踪: $stackTrace', level: LogLevel.error);
    }
  }

  static void _writeToLogFile(String message) {
    // 实现日志文件写入
    // 可以使用path_provider获取应用文档目录
  }
}

enum LogLevel { debug, info, warning, error }

故事结局:小王的成功

经过几个月的开发,小王的智能家居应用终于完成了!他成功实现了与各种蓝牙设备的稳定连接和控制。

“蓝牙开发确实复杂,但通过合理的架构设计和错误处理,我们可以构建出稳定可靠的蓝牙应用。“小王在他的技术博客中总结道,“关键是要理解蓝牙协议、处理好平台差异,并提供良好的用户体验。”

小王的应用获得了用户的好评,特别是流畅的设备连接体验和稳定的通信性能。他的成功证明了:掌握蓝牙桥接技术,是开发 IoT 应用的关键技能。

总结

通过小王的智能家居应用开发故事,我们全面学习了 Flutter 蓝牙桥接技术:

核心技术

高级特性

最佳实践

蓝牙桥接技术为 Flutter 应用打开了 IoT 世界的大门,让开发者能够构建出与物理世界交互的创新应用。掌握这些技术,将帮助你在物联网时代占据先机!


编辑文章
分享这篇文章:

上一篇
Flutter 与设备信息桥接技术详解
下一篇
Flutter 与联系人管理桥接技术详解