在鸿蒙上使用 fluttertpc_flutter_blue_plus 插件

插件介绍

fluttertpc_flutter_blue_plus 是一个功能强大的蓝牙低功耗(BLE)插件,专为 Flutter 应用设计,支持在鸿蒙(HarmonyOS)平台上进行 BLE 设备的扫描、连接和通信。该插件基于 flutter_blue_plus 进行了鸿蒙平台的适配,提供了完整的 BLE 中央角色功能支持。

主要功能特性

  • ✅ 支持检查设备是否支持蓝牙
  • ✅ 支持启用蓝牙适配器
  • ✅ 支持扫描 BLE 设备
  • ✅ 支持与 BLE 设备建立连接
  • ✅ 支持发现设备的服务和特征
  • ✅ 支持读写特征值
  • ✅ 支持设置特征通知
  • ✅ 支持读取设备 RSSI 值
  • ✅ 支持设置 MTU 值
  • ✅ 支持蓝牙适配器状态监听
  • ✅ 支持连接状态变化监听

如何使用插件

1. 包的引入

由于这是一个自定义修改版本的插件,需要通过 Git 形式引入依赖。在项目的 pubspec.yaml 文件中添加以下配置:

dependencies:
  flutter:
    sdk: flutter
  flutter_blue_plus:
    git:
      url: "https://gitcode.com/openharmony-sig/fluttertpc_flutter_blue_plus.git"
      path: "."
  flutter_blue_plus_ohos:
    git:
      url: "https://gitcode.com/openharmony-sig/fluttertpc_flutter_blue_plus.git"
      path: "ohos"

添加依赖后,执行以下命令获取包:

flutter pub get

2. 权限配置

在鸿蒙平台上使用蓝牙功能需要配置相应的权限。打开 entry/src/main/module.json5 文件,添加以下权限配置:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.ACCESS_BLUETOOTH",
        "reason": "需要蓝牙权限来扫描和连接BLE设备",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.PERSISTENT_BLUETOOTH_PEERS_MAC"
      }
    ]
  }
}

同时,在 entry/src/main/resources/base/element/string.json 文件中添加权限说明:

{
  "string": [
    {
      "name": "bluetooth_reason",
      "value": "需要蓝牙权限来扫描和连接BLE设备"
    }
  ]
}

3. API 的调用

3.1 初始化与蓝牙状态检查
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

// 检查设备是否支持蓝牙
bool isSupported = await FlutterBluePlus.isSupported;
if (!isSupported) {
  print("此设备不支持蓝牙");
  return;
}

// 设置日志级别
FlutterBluePlus.setLogLevel(LogLevel.verbose, color: false);

// 监听蓝牙适配器状态变化
StreamSubscription<BluetoothAdapterState> adapterStateSubscription =
    FlutterBluePlus.adapterState.listen((state) {
  print("蓝牙状态: $state");
  if (state == BluetoothAdapterState.on) {
    // 蓝牙已开启,可以开始扫描设备
  } else {
    // 蓝牙未开启,需要提示用户
  }
});

// 开启蓝牙(仅Android和HarmonyOS平台支持)
if (defaultTargetPlatform == TargetPlatform.android ||
    defaultTargetPlatform == TargetPlatform.ohos) {
  await FlutterBluePlus.turnOn(timeout: 60);
}
3.2 扫描 BLE 设备
// 监听扫描结果
StreamSubscription<List<ScanResult>> scanResultsSubscription =
    FlutterBluePlus.scanResults.listen((results) {
  for (ScanResult result in results) {
    print('发现设备: ${result.device.platformName}, RSSI: ${result.rssi}');
    print('设备ID: ${result.device.remoteId}');
    print('广播名称: ${result.advertisementData.advName}');
    print('服务UUID: ${result.advertisementData.serviceUuids}');
  }
});

// 开始扫描
await FlutterBluePlus.startScan(
  withServices: [], // 可以指定要扫描的服务UUID
  withNames: [], // 可以指定要扫描的设备名称
  timeout: Duration(seconds: 30), // 扫描超时时间
);

// 等待扫描停止
await FlutterBluePlus.isScanning.where((val) => val == false).first;

// 清理订阅
scanResultsSubscription.cancel();
3.3 连接 BLE 设备
// 获取扫描结果中的设备
ScanResult scanResult = results.first;
BluetoothDevice device = scanResult.device;

// 监听连接状态变化
StreamSubscription<BluetoothConnectionState> connectionStateSubscription =
    device.connectionState.listen((state) {
  print('连接状态: $state');
  if (state == BluetoothConnectionState.connected) {
    // 连接成功,可以发现服务
    discoverServices(device);
  } else if (state == BluetoothConnectionState.disconnected) {
    // 连接断开
    print('设备已断开连接');
  }
});

// 建立连接
await device.connect(
  timeout: Duration(seconds: 35),
  autoConnect: false,
);
3.4 发现设备服务
Future<void> discoverServices(BluetoothDevice device) async {
  // 发现服务
  List<BluetoothService> services = await device.discoverServices();

  for (BluetoothService service in services) {
    print('服务UUID: ${service.uuid}');

    // 遍历服务的特征
    for (BluetoothCharacteristic characteristic in service.characteristics) {
      print('  特征UUID: ${characteristic.uuid}');
      print('  特征属性: ${characteristic.properties}');

      // 读取特征值(如果有读权限)
      if (characteristic.properties.read) {
        List<int> value = await characteristic.read();
        print('  特征值: $value');
      }

      // 订阅特征通知(如果有通知权限)
      if (characteristic.properties.notify) {
        await characteristic.setNotifyValue(true);
        characteristic.onValueReceived.listen((value) {
          print('  接收到通知值: $value');
        });
      }

      // 写入特征值(如果有写权限)
      if (characteristic.properties.write) {
        List<int> valueToWrite = [1, 2, 3, 4, 5];
        await characteristic.write(valueToWrite, withoutResponse: false);
      }

      // 遍历特征的描述符
      for (BluetoothDescriptor descriptor in characteristic.descriptors) {
        print('    描述符UUID: ${descriptor.uuid}');

        // 读取描述符值
        List<int> descriptorValue = await descriptor.read();
        print('    描述符值: $descriptorValue');
      }
    }
  }
}
3.5 读取 RSSI 值
// 读取设备RSSI值
int rssi = await device.readRssi();
print('设备RSSI: $rssi');
3.6 设置 MTU 值
// 请求设置MTU值
int newMtu = await device.requestMtu(desiredMtu: 512);
print('新的MTU值: $newMtu');

// 监听MTU值变化
device.mtu.listen((mtu) {
  print('MTU值已更新: $mtu');
});
3.7 断开连接
// 断开连接
await device.disconnect(timeout: 35);

// 清理订阅
connectionStateSubscription.cancel();
adapterStateSubscription.cancel();

代码示例

下面是一个完整的示例,展示了如何在鸿蒙应用中使用 fluttertpc_flutter_blue_plus 插件:

import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'dart:typed_data';

void main() {
  runApp(const BluetoothApp());
}

class BluetoothApp extends StatelessWidget {
  const BluetoothApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'BLE 示例应用',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const BluetoothHomePage(),
    );
  }
}

class BluetoothHomePage extends StatefulWidget {
  const BluetoothHomePage({Key? key}) : super(key: key);

  
  State<BluetoothHomePage> createState() => _BluetoothHomePageState();
}

class _BluetoothHomePageState extends State<BluetoothHomePage> {
  BluetoothAdapterState _adapterState = BluetoothAdapterState.unknown;
  List<ScanResult> _scanResults = [];
  bool _isScanning = false;
  BluetoothDevice? _connectedDevice;
  List<BluetoothService> _services = [];

  late StreamSubscription<BluetoothAdapterState> _adapterStateSubscription;
  late StreamSubscription<List<ScanResult>> _scanResultsSubscription;
  StreamSubscription<BluetoothConnectionState>? _connectionStateSubscription;

  
  void initState() {
    super.initState();
    _initBluetooth();
  }

  Future<void> _initBluetooth() async {
    // 检查设备是否支持蓝牙
    bool isSupported = await FlutterBluePlus.isSupported;
    if (!isSupported) {
      _showAlert('此设备不支持蓝牙');
      return;
    }

    // 设置日志级别
    FlutterBluePlus.setLogLevel(LogLevel.verbose, color: false);

    // 监听蓝牙适配器状态
    _adapterStateSubscription = FlutterBluePlus.adapterState.listen((state) {
      setState(() {
        _adapterState = state;
      });
    });

    // 开启蓝牙
    await FlutterBluePlus.turnOn(timeout: 60);
  }

  Future<void> _startScan() async {
    setState(() {
      _scanResults.clear();
      _isScanning = true;
    });

    // 监听扫描结果
    _scanResultsSubscription = FlutterBluePlus.scanResults.listen((results) {
      setState(() {
        _scanResults = results;
      });
    });

    // 开始扫描
    await FlutterBluePlus.startScan(timeout: Duration(seconds: 15));

    // 等待扫描停止
    await FlutterBluePlus.isScanning.where((val) => val == false).first;

    setState(() {
      _isScanning = false;
    });

    // 清理订阅
    _scanResultsSubscription.cancel();
  }

  Future<void> _connectToDevice(BluetoothDevice device) async {
    try {
      setState(() {
        _connectedDevice = device;
      });

      // 监听连接状态
      _connectionStateSubscription = device.connectionState.listen((state) {
        if (state == BluetoothConnectionState.connected) {
          // 连接成功,发现服务
          _discoverServices(device);
        } else if (state == BluetoothConnectionState.disconnected) {
          setState(() {
            _connectedDevice = null;
            _services.clear();
          });
        }
      });

      // 建立连接
      await device.connect(timeout: Duration(seconds: 35));
    } catch (e) {
      _showAlert('连接失败: $e');
      setState(() {
        _connectedDevice = null;
      });
    }
  }

  Future<void> _discoverServices(BluetoothDevice device) async {
    List<BluetoothService> services = await device.discoverServices();
    setState(() {
      _services = services;
    });
  }

  Future<void> _disconnectFromDevice() async {
    if (_connectedDevice != null) {
      await _connectedDevice!.disconnect();
      _connectionStateSubscription?.cancel();
      setState(() {
        _connectedDevice = null;
        _services.clear();
      });
    }
  }

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

  
  void dispose() {
    _adapterStateSubscription.cancel();
    _connectionStateSubscription?.cancel();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('BLE 示例应用')),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 蓝牙状态
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text('蓝牙状态', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                    const SizedBox(height: 8),
                    Text('当前状态: $_adapterState'),
                    const SizedBox(height: 16),
                    ElevatedButton(
                      onPressed: _adapterState == BluetoothAdapterState.on ? _startScan : null,
                      child: Text(_isScanning ? '正在扫描...' : '开始扫描'),
                    ),
                  ],
                ),
              ),
            ),

            const SizedBox(height: 16),

            // 扫描结果
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text('扫描结果', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                    const SizedBox(height: 8),
                    _scanResults.isEmpty
                        ? const Text('暂无设备')
                        : SizedBox(
                            height: 200,
                            child: ListView.builder(
                              itemCount: _scanResults.length,
                              itemBuilder: (context, index) {
                                ScanResult result = _scanResults[index];
                                return ListTile(
                                  title: Text(result.device.platformName.isNotEmpty
                                      ? result.device.platformName
                                      : '未知设备'),
                                  subtitle: Text('RSSI: ${result.rssi} dBm'),
                                  trailing: ElevatedButton(
                                    onPressed: () => _connectToDevice(result.device),
                                    child: const Text('连接'),
                                  ),
                                );
                              },
                            ),
                          ),
                  ],
                ),
              ),
            ),

            const SizedBox(height: 16),

            // 连接设备信息
            if (_connectedDevice != null)
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text('已连接设备', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                      const SizedBox(height: 8),
                      Text('设备名称: ${_connectedDevice!.platformName}'),
                      Text('设备ID: ${_connectedDevice!.remoteId}'),
                      const SizedBox(height: 16),
                      ElevatedButton(
                        onPressed: _disconnectFromDevice,
                        child: const Text('断开连接'),
                      ),
                    ],
                  ),
                ),
              ),

            const SizedBox(height: 16),

            // 服务列表
            if (_services.isNotEmpty)
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text('服务列表', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                      const SizedBox(height: 8),
                      for (BluetoothService service in _services)
                        Padding(
                          padding: const EdgeInsets.only(bottom: 16.0),
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Text('服务UUID: ${service.uuid}'),
                              const SizedBox(height: 8),
                              for (BluetoothCharacteristic characteristic in service.characteristics)
                                Padding(
                                  padding: const EdgeInsets.only(left: 16.0, bottom: 8.0),
                                  child: Column(
                                    crossAxisAlignment: CrossAxisAlignment.start,
                                    children: [
                                      Text('特征UUID: ${characteristic.uuid}'),
                                      Text('属性: ${characteristic.properties}'),
                                    ],
                                  ),
                                ),
                            ],
                          ),
                        ),
                    ],
                  ),
                ),
              ),
          ],
        ),
      ),
    );
  }
}

注意事项

  1. 在鸿蒙平台上使用蓝牙功能需要正确配置权限,否则会导致蓝牙功能无法正常工作。

  2. 蓝牙扫描可能会消耗较多电量,建议在不需要时及时停止扫描。

  3. 与 BLE 设备的通信应该在连接建立后进行,通信完成后及时断开连接。

  4. 对于频繁的特征值读写操作,建议使用通知方式获取数据,而不是轮询读取。

  5. MTU 值的设置需要考虑设备的支持情况,过大的 MTU 值可能会导致通信不稳定。

总结

fluttertpc_flutter_blue_plus 插件为鸿蒙平台提供了完整的 BLE 中央角色功能支持,包括设备扫描、连接建立、服务发现、特征读写等核心功能。通过简单的 API 调用,开发者可以轻松地在 Flutter 应用中集成 BLE 通信功能,实现与各种 BLE 设备的交互。

该插件的主要优势在于:

  1. 跨平台兼容性:除了鸿蒙平台外,还支持 Android、iOS、macOS 等多种平台,便于开发者编写跨平台应用。

  2. 完整的功能支持:覆盖了 BLE 中央角色的大多数常见功能需求,满足各种应用场景。

  3. 简单易用的 API:提供了简洁明了的 API 接口,减少了开发者的学习成本。

  4. 稳定的性能:基于成熟的 flutter_blue_plus 进行开发,具有稳定的性能和良好的兼容性。

通过本指南,开发者可以快速上手并在鸿蒙应用中使用 fluttertpc_flutter_blue_plus 插件,实现高效的 BLE 通信功能。

### 鸿蒙系统对 flutter_downloader 插件的支持 在鸿蒙系统上使用 `flutter_downloader` 插件时,需注意其依赖原生 Android 实现,而鸿蒙系统具备兼容 Android 应用的能力,因此该插件在大多数情况下可以正常运行。然而,由于鸿蒙系统在权限管理、后台任务调度等方面有所调整,因此在使用时需要进行适配[^1]。 ### 插件配置与使用 `flutter_downloader` 是一个基于 `WorkManager` 的插件,支持后台下载、暂停与恢复功能,适用于需要长时间运行的下载任务。其使用方法如下: 1. **添加依赖** 在 `pubspec.yaml` 文件中添加插件依赖: ```yaml dependencies: flutter_downloader: ^1.12.0 ``` 2. **初始化插件** 在应用启动时初始化插件,并设置最大并发任务数: ```dart final dl = FlutterDownloader(); await dl.initialize(maxConcurrentTasks: 3); ``` 3. **下载文件** 使用 `enqueue` 方法发起下载任务: ```dart final taskId = await dl.enqueue( url: "https://example.com/file.apk", savedDir: "/storage/emulated/0/Download", fileName: "file.apk", showNotification: true, openFileFromNotification: true, ); ``` 4. **监听下载状态** 通过 `bindCallback` 方法监听任务状态变化: ```dart dl.bindCallback((id, status, progress) { if (id == taskId) { print("Download status: $status, Progress: $progress%"); } }); ``` 5. **请求权限** 鸿蒙系统对文件访问权限控制较严格,需在 `AndroidManifest.xml` 中添加如下权限: ```xml <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> ``` 并在运行时请求权限: ```dart final status = await PermissionHandler().checkPermissionStatus(PermissionGroup.storage); if (status != PermissionStatus.granted) { await PermissionHandler().requestPermissions([PermissionGroup.storage]); } ``` ### 鸿蒙系统适配要点 - **文件路径限制**:鸿蒙系统从 Android 10 开始限制直接访问外部存储,建议使用 `path_provider` 获取应用专属目录进行文件保存[^1]。 - **后台任务限制**:鸿蒙系统可能会限制后台任务的网络访问,建议结合 `flutter_background_service` 插件管理后台下载流程。 - **断点续传支持**:`flutter_downloader` 支持断点续传功能,但在鸿蒙系统中需确保服务器支持 `Range` 请求头,并在下载时开启相应配置[^2]。 ### 示例代码 以下是一个完整的下载任务配置示例: ```dart import 'package:flutter/material.dart'; import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:permission_handler/permission_handler.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); final dl = FlutterDownloader(); await dl.initialize(maxConcurrentTasks: 3); final status = await PermissionHandler().checkPermissionStatus(PermissionGroup.storage); if (status != PermissionStatus.granted) { await PermissionHandler().requestPermissions([PermissionGroup.storage]); } runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: Text("Flutter Downloader")), body: Center( child: ElevatedButton( onPressed: () async { final taskId = await FlutterDownloader().enqueue( url: "https://example.com/file.apk", savedDir: "/storage/emulated/0/Download", fileName: "file.apk", showNotification: true, openFileFromNotification: true, ); FlutterDownloader().bindCallback((id, status, progress) { if (id == taskId) { print("Download status: $status, Progress: $progress%"); } }); }, child: Text("Download File"), ), ), ), ); } } ``` ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值