前言
基于上一篇文章 对 经典蓝牙、BLE等理论知识的 分享,在这篇文章我们进一步分享技术方案上的具体实现的设计内容。
基于概要设计可以选择对应不同类型的平台自己去实践详细设计与编码的部分
DTBluetoothProvider 概要设计文档
📋 项目概述
DTBluetoothProvider 是一个跨平台的高级蓝牙服务封装库,提供了完整的蓝牙设备管理解决方案。该库同时支持经典蓝牙(Classic Bluetooth)和低功耗蓝牙(BLE),支持多设备连接、智能指令管理、自动重连和数据包封装等核心功能。
设计目标:
- 提供统一的蓝牙设备管理接口,屏蔽不同平台的底层实现差异
- 支持多设备并发连接和管理
- 提供智能化的指令管理和自动重连机制
- 支持数据包封装和格式转换工具
- 具备良好的可扩展性和可维护性
✨ 功能特性
核心功能特性
-
设备扫描管理
- 支持扫描、停止扫描、重新扫描
- 支持设备过滤和筛选
- 实时更新设备列表
-
设备连接管理
- 支持连接、断开、心跳检测
- 带自动重试机制
- 支持连接参数优化
-
数据通信
- 支持读取、写入、订阅通知
- 自动处理数据包封装和解析
- 支持多种数据格式转换
-
多设备支持(核心特性)
- 支持同时连接多个同类型设备(通过 channelNumb 区分)
- 支持同时连接多个不同类型设备(通过 Channel 区分)
- 每个设备都有独立的连接上下文(DeviceConnectionContext)
- 自动管理设备连接状态和资源
-
指令缓冲工具(核心特性)
- 串行执行:确保指令按顺序发送,避免硬件处理冲突
- 智能去重:无参数指令自动忽略重复,有参数指令可自定义比较逻辑
- 参数更新:支持根据业务需求决定是否更新队列中的指令参数
- 持久化缓存:支持指令队列持久化,应用重启后可恢复未完成的指令
- 超时机制:内置指令超时检测,自动处理超时指令
-
设备绑定缓存(核心特性)
- 自动保存连接成功的设备信息到硬盘
- 根据绑定记录智能决策是否启动自动重连
- 支持启用/禁用单个设备的自动重连
- 持久化存储,应用重启后仍可恢复
-
重连状态机(核心特性)
- 支持多种重连策略(立即、固定延迟、指数退避、自定义)
- 状态机管理重连流程(空闲、重连中、暂停、成功、失败)
- 支持暂停、恢复、停止等操作
- 连接超时和冷却期机制
- 基于 RSSI 的连接质量评估
-
数据帧封装(核心特性)
- 支持多种数据包格式(默认、带帧头、带长度、完整格式)
- 自动解析和构建数据包
- 校验和验证
- 便捷的数据访问方法
-
数据格式转换工具(核心特性)
- 支持 Data、String、整数类型、浮点数之间的转换
- 支持二进制、八进制、十进制、十六进制之间的转换
- 支持进制运算(算术运算、按位运算、移位运算、比较运算)
- 支持字节序转换(大端序/小端序)
- 支持 Base64 编码/解码
- 支持校验和计算(简单累加、CRC16)
- Data 截取功能:提供 11 种数据截取方法
- Array 截取功能:提供 15 种数组元素截取方法,支持安全访问
-
事件回调
- 完整的回调机制,实时监听蓝牙状态和设备信息
- 支持多个观察者
- 响应式数据流
-
日志支持
- 内置日志功能,方便调试
- 可配置日志级别
- 支持日志回调
-
错误处理
- 完善的错误处理和状态管理
- 详细的错误类型定义
- Result 类型返回
-
连接参数优化
- 支持 BLE 连接参数配置
- 根据应用类型自动优化连接间隔、延迟和超时
- 支持实时应用、批量传输、低功耗应用等场景
-
连接质量评估
- 基于 RSSI 和丢包率评估连接质量
- 支持动态调整重连策略
- 提供连接质量等级(excellent, good, fair, poor)
📱 系统要求
平台要求
| 平台 | 最低版本要求 | 开发工具 | 语言版本 |
|---|---|---|---|
| iOS | iOS 13.0+ | Xcode 12.0+ | Swift 5.0+ |
| Android | Android 5.0+ (API 21+) | Android Studio | Kotlin/Java |
| HarmonyOS | HarmonyOS 2.0+ | DevEco Studio | ArkTS/Java |
| Flutter | Flutter 2.0+ | VS Code / Android Studio | Dart |
注意:蓝牙功能必须在真机上测试,模拟器不支持蓝牙功能。
🔐 多平台权限申请
iOS 平台权限申请
1. Info.plist 配置
在 Info.plist 中添加以下权限说明:
<!-- iOS 13+ 必需 -->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>应用需要访问蓝牙以连接设备</string>
<!-- iOS 12 及以下 -->
<key>NSBluetoothPeripheralUsageDescription</key>
<string>应用需要访问蓝牙以连接设备</string>
2. 权限申请流程
class iOSBluetoothPermissionManager {
// 检查蓝牙权限状态
func checkBluetoothPermission() -> BluetoothPermissionStatus {
switch CBPeripheralManager.authorizationStatus() {
case .notDetermined:
return .notDetermined
case .restricted:
return .restricted
case .denied:
return .denied
case .authorized:
return .authorized
}
}
// 请求蓝牙权限
func requestBluetoothPermission(completion: @escaping (Bool) -> Void) {
let status = checkBluetoothPermission()
if status == .notDetermined {
// 创建 CBPeripheralManager 会自动触发权限请求
let manager = CBPeripheralManager(delegate: self, queue: nil)
// 权限结果通过 delegate 回调返回
} else if status == .authorized {
completion(true)
} else {
completion(false)
// 引导用户到设置页面
openSettings()
}
}
// 打开系统设置页面
func openSettings() {
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
}
}
}
3. 权限申请流程图
开始
↓
检查权限状态
↓
┌─────────────────┐
│ 权限状态判断 │
└────────┬────────┘
│
┌────┴────┬──────────┬──────────┐
│ │ │ │
未确定 已授权 已拒绝 受限
│ │ │ │
↓ ↓ ↓ ↓
请求权限 允许使用 引导设置 提示受限
│ │ │ │
↓ │ │ │
等待用户响应 │ │ │
│ │ │ │
└─────────┴──────────┴──────────┘
↓
完成
Android 平台权限申请
1. AndroidManifest.xml 配置
在 AndroidManifest.xml 中添加以下权限:
<!-- 蓝牙扫描权限(Android 12+ 需要) -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<!-- 蓝牙连接权限(Android 12+ 需要) -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- 位置权限(Android 12 以下需要,用于扫描) -->
<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" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- 蓝牙功能声明 -->
<uses-feature android:name="android.hardware.bluetooth" android:required="true" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
2. 权限申请流程
class AndroidBluetoothPermissionManager {
// Android 12+ 需要的权限
private val BLUETOOTH_PERMISSIONS_12_PLUS = [
"android.permission.BLUETOOTH_SCAN",
"android.permission.BLUETOOTH_CONNECT"
]
// Android 12 以下需要的权限
private val BLUETOOTH_PERMISSIONS_BELOW_12 = [
"android.permission.BLUETOOTH",
"android.permission.BLUETOOTH_ADMIN",
"android.permission.ACCESS_FINE_LOCATION"
]
// 检查权限状态
func checkBluetoothPermissions() -> Map<String, Boolean> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Android 12+
return checkPermissions(BLUETOOTH_PERMISSIONS_12_PLUS)
} else {
// Android 12 以下
return checkPermissions(BLUETOOTH_PERMISSIONS_BELOW_12)
}
}
// 请求权限
func requestBluetoothPermissions(activity: Activity, callback: PermissionCallback) {
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
BLUETOOTH_PERMISSIONS_12_PLUS
} else {
BLUETOOTH_PERMISSIONS_BELOW_12
}
val missingPermissions = filterMissingPermissions(permissions)
if (missingPermissions.isEmpty()) {
callback.onAllPermissionsGranted()
} else {
ActivityCompat.requestPermissions(
activity,
missingPermissions.toTypedArray(),
REQUEST_CODE_BLUETOOTH_PERMISSIONS
)
}
}
// 处理权限请求结果
func onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
if (requestCode == REQUEST_CODE_BLUETOOTH_PERMISSIONS) {
val allGranted = grantResults.all { it == PackageManager.PERMISSION_GRANTED }
if (allGranted) {
callback.onAllPermissionsGranted()
} else {
callback.onPermissionsDenied(permissions)
}
}
}
}
3. 权限申请流程图
开始
↓
检查 Android 版本
↓
┌─────────────────┐
│ 版本判断 │
└────────┬─────────┘
│
┌────┴────┐
│ │
Android 12+ Android 12-
│ │
↓ ↓
检查新权限 检查旧权限
(BLE_SCAN) (BLUETOOTH)
(BLE_CONNECT) (LOCATION)
│ │
└────┬────┘
↓
权限是否已授予?
│
┌────┴────┐
│ │
是 否
│ │
↓ ↓
允许使用 请求权限
│ │
│ ↓
│ 等待用户响应
│ │
│ ┌────┴────┐
│ │ │
│ 已授予 已拒绝
│ │ │
│ ↓ ↓
│ 允许使用 引导设置
│ │ │
└────┴─────────┘
↓
完成
HarmonyOS 平台权限申请
1. module.json5 配置
在 module.json5 中添加以下权限:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.USE_BLUETOOTH",
"reason": "应用需要访问蓝牙以连接设备",
"usedScene": {
"abilities": ["MainAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.DISCOVER_BLUETOOTH",
"reason": "应用需要扫描蓝牙设备",
"usedScene": {
"abilities": ["MainAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.LOCATION",
"reason": "应用需要位置权限以扫描蓝牙设备",
"usedScene": {
"abilities": ["MainAbility"],
"when": "inuse"
}
}
]
}
}
2. 权限申请流程
class HarmonyOSBluetoothPermissionManager {
// 需要的权限列表
private val BLUETOOTH_PERMISSIONS = [
"ohos.permission.USE_BLUETOOTH",
"ohos.permission.DISCOVER_BLUETOOTH",
"ohos.permission.LOCATION"
]
// 检查权限状态
func checkBluetoothPermissions(context: Context) -> Map<String, PermissionStatus> {
val result = Map<String, PermissionStatus>()
for (permission in BLUETOOTH_PERMISSIONS) {
val status = context.verifySelfPermission(permission)
result[permission] = status
}
return result
}
// 请求权限
func requestBluetoothPermissions(
context: Context,
callback: PermissionRequestCallback
) {
val missingPermissions = filterMissingPermissions(BLUETOOTH_PERMISSIONS)
if (missingPermissions.isEmpty()) {
callback.onAllPermissionsGranted()
} else {
context.requestPermissionsFromUser(
missingPermissions.toTypedArray(),
REQUEST_CODE_BLUETOOTH_PERMISSIONS
)
}
}
// 处理权限请求结果
func onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
if (requestCode == REQUEST_CODE_BLUETOOTH_PERMISSIONS) {
val allGranted = grantResults.all {
it == PermissionRequestResult.PERMISSION_GRANTED
}
if (allGranted) {
callback.onAllPermissionsGranted()
} else {
callback.onPermissionsDenied(permissions)
}
}
}
}
3. 权限申请流程图
开始
↓
检查权限状态
↓
┌─────────────────┐
│ 权限状态判断 │
└────────┬────────┘
│
┌────┴────┬──────────┐
│ │ │
未确定 已授权 已拒绝
│ │ │
↓ ↓ ↓
请求权限 允许使用 引导设置
│ │ │
↓ │ │
等待用户响应 │ │
│ │ │
└─────────┴──────────┘
↓
完成
Flutter 平台权限申请
1. pubspec.yaml 配置
在 pubspec.yaml 中添加权限插件:
dependencies:
permission_handler: ^11.0.0
flutter_blue: ^0.8.0
2. Android 配置
在 android/app/src/main/AndroidManifest.xml 中添加权限(同 Android 平台配置)
3. iOS 配置
在 ios/Runner/Info.plist 中添加权限(同 iOS 平台配置)
4. 权限申请流程
class FlutterBluetoothPermissionManager {
// 检查权限状态
Future<Map<Permission, PermissionStatus>> checkBluetoothPermissions() async {
if (Platform.isAndroid) {
if (await Permission.bluetoothScan.isGranted &&
await Permission.bluetoothConnect.isGranted) {
return {Permission.bluetoothScan: PermissionStatus.granted,
Permission.bluetoothConnect: PermissionStatus.granted}
}
} else if (Platform.isIOS) {
return await Permission.bluetooth.status
}
return {}
}
// 请求权限
Future<bool> requestBluetoothPermissions() async {
if (Platform.isAndroid) {
// Android 12+
if (await Permission.bluetoothScan.request().isGranted &&
await Permission.bluetoothConnect.request().isGranted) {
return true
}
// Android 12 以下
if (await Permission.location.request().isGranted) {
return true
}
} else if (Platform.isIOS) {
return await Permission.bluetooth.request().isGranted
}
return false
}
// 打开设置页面
Future<bool> openSettings() async {
return await openAppSettings()
}
}
5. 权限申请流程图
开始
↓
检查平台类型
↓
┌─────────────────┐
│ 平台判断 │
└────────┬─────────┘
│
┌────┴────┐
│ │
Android iOS
│ │
↓ ↓
检查权限 检查权限
(BLE_SCAN) (Bluetooth)
(BLE_CONNECT)
│ │
└────┬────┘
↓
权限是否已授予?
│
┌────┴────┐
│ │
是 否
│ │
↓ ↓
允许使用 请求权限
│ │
│ ↓
│ 等待用户响应
│ │
│ ┌────┴────┐
│ │ │
│ 已授予 已拒绝
│ │ │
│ ↓ ↓
│ 允许使用 引导设置
│ │ │
└────┴─────────┘
↓
完成
统一权限管理接口设计
为了屏蔽不同平台的权限申请差异,设计统一的权限管理接口:
// 权限状态枚举
enum BluetoothPermissionStatus {
NotDetermined, // 未确定
Authorized, // 已授权
Denied, // 已拒绝
Restricted, // 受限
Unavailable // 不可用
}
// 统一权限管理接口
interface BluetoothPermissionManager {
// 检查权限状态
checkPermissionStatus(): BluetoothPermissionStatus
// 请求权限
requestPermission(callback: (status: BluetoothPermissionStatus) -> Void)
// 打开系统设置页面
openSettings()
// 检查蓝牙是否可用
isBluetoothAvailable(): Boolean
}
// iOS 实现
class iOSBluetoothPermissionManager implements BluetoothPermissionManager {
checkPermissionStatus(): BluetoothPermissionStatus {
status = CBPeripheralManager.authorizationStatus()
return convertToUnifiedStatus(status)
}
requestPermission(callback: (status: BluetoothPermissionStatus) -> Void) {
// iOS 权限申请实现
}
}
// Android 实现
class AndroidBluetoothPermissionManager implements BluetoothPermissionManager {
checkPermissionStatus(): BluetoothPermissionStatus {
// Android 权限检查实现
}
requestPermission(callback: (status: BluetoothPermissionStatus) -> Void) {
// Android 权限申请实现
}
}
// HarmonyOS 实现
class HarmonyOSBluetoothPermissionManager implements BluetoothPermissionManager {
checkPermissionStatus(): BluetoothPermissionStatus {
// HarmonyOS 权限检查实现
}
requestPermission(callback: (status: BluetoothPermissionStatus) -> Void) {
// HarmonyOS 权限申请实现
}
}
// 工厂类创建对应平台的权限管理器
class BluetoothPermissionManagerFactory {
static createManager(platform: Platform): BluetoothPermissionManager {
switch (platform) {
case iOS:
return new iOSBluetoothPermissionManager()
case Android:
return new AndroidBluetoothPermissionManager()
case HarmonyOS:
return new HarmonyOSBluetoothPermissionManager()
default:
throw UnsupportedPlatformException()
}
}
}
权限申请最佳实践
-
权限申请时机
- 在需要使用蓝牙功能前申请权限
- 避免应用启动时立即申请,影响用户体验
- 提供清晰的权限说明,告知用户为什么需要权限
-
权限被拒绝后的处理
- 提供友好的提示信息
- 引导用户到系统设置页面手动开启权限
- 检测权限状态变化,权限开启后自动恢复功能
-
权限状态监听
class BluetoothPermissionObserver { // 监听权限状态变化 observePermissionStatus(callback: (status: BluetoothPermissionStatus) -> Void) { // 平台特定的权限状态监听实现 } } -
权限申请流程图(统一流程)
应用启动
↓
检查蓝牙权限状态
↓
┌─────────────────┐
│ 权限状态判断 │
└────────┬────────┘
│
┌────┴────┬──────────┬──────────┐
│ │ │ │
未确定 已授权 已拒绝 受限
│ │ │ │
↓ ↓ ↓ ↓
显示说明 允许使用 显示说明 提示受限
并请求权限 │ 并引导设置 │
│ │ │ │
↓ │ │ │
等待用户响应 │ │ │
│ │ │ │
└─────────┴──────────┴──────────┘
↓
权限授予?
│
┌────┴────┐
│ │
是 否
│ │
↓ ↓
允许使用 引导设置
│ │
│ ↓
│ 监听权限变化
│ │
│ 权限开启?
│ │
│ ┌────┴────┐
│ │ │
│ 是 否
│ │ │
│ ↓ ↓
│ 恢复功能 继续等待
│ │ │
└────┴─────────┘
↓
完成
模块导入
iOS
import CoreBluetooth
Android
import android.bluetooth.*
import androidx.core.app.ActivityCompat
HarmonyOS
import bluetoothManager from '@ohos.bluetoothManager'
import abilityAccessCtrl from '@ohos.abilityAccessCtrl'
Flutter
import 'package:permission_handler/permission_handler.dart'
import 'package:flutter_blue/flutter_blue.dart'
🏗️ 架构设计思想
一、分层架构设计
项目采用清晰的分层架构,从业务层到底层实现,职责分明:
┌─────────────────────────────────────────┐
│ 业务层 (Business Layer) │
│ - BluetoothViewModel │
│ - 封装常用操作,集成所有工具 │
└──────────────┬──────────────────────────┘
│
┌──────────────▼──────────────────────────┐
│ 服务层 (Service Layer) │
│ - BleServiceImpl │
│ - 多设备管理,连接重试 │
└──────────────┬──────────────────────────┘
│
┌──────────────▼──────────────────────────┐
│ 底层实现层 (Implementation Layer) │
│ - DTBleCentralProviderInternal │
│ - DefaultBleCentralProvider │
│ - ClassicBluetoothProvider (经典蓝牙) │
│ - BleProvider (低功耗蓝牙) │
│ - 基于平台原生蓝牙 API │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ 工具层 (Utility Layer) │
│ - BleCommandBuffer (指令缓冲) │
│ - DTDeviceBindingCache (绑定缓存) │
│ - DTReconnectionStateMachine (重连) │
│ - DTDataPacket (数据帧封装) │
│ - DTDataFormatConverter (数据格式转换) │
└─────────────────────────────────────────┘
设计优势
- 职责分离:每一层专注于自己的职责,降低耦合度
- 易于扩展:新功能可以在对应层级添加,不影响其他层
- 便于测试:各层可以独立测试,支持依赖注入
- 代码复用:工具层可以被多个业务场景复用
- 平台无关:通过接口抽象,底层实现可适配不同平台
二、核心设计模式
1. 单例模式 (Singleton Pattern)
应用场景:
DTDeviceBindingCache.getInstance()- 设备绑定缓存单例- 确保全局唯一的设备绑定记录管理
设计意图:
- 保证设备绑定数据的一致性
- 简化跨模块访问
- 统一管理持久化存储
伪代码实现:
class DTDeviceBindingCache {
private static shared: DTDeviceBindingCache = null
static func getInstance(): DTDeviceBindingCache {
if shared == null {
shared = new DTDeviceBindingCache()
}
return shared
}
}
单例模式流程图:
首次调用 getInstance()
↓
检查 shared 是否为 null
↓
┌─────────────────┐
│ shared == null? │
└────────┬────────┘
│
┌────┴────┐
│ │
是 否
│ │
↓ ↓
创建实例 返回已有实例
│ │
↓ │
赋值给 shared │
│ │
└─────────┘
↓
返回实例
2. 策略模式 (Strategy Pattern)
应用场景:
ReconnectionStrategy- 重连策略immediate- 立即重连fixedDelay- 固定延迟exponentialBackoff- 指数退避custom- 自定义策略
设计意图:
- 灵活配置重连行为
- 支持运行时切换策略
- 易于扩展新的重连策略
伪代码实现:
interface ReconnectionStrategy {
func calculateDelay(attempt: Integer): Long
}
class ImmediateStrategy implements ReconnectionStrategy {
func calculateDelay(attempt: Integer): Long {
return 0
}
}
class FixedDelayStrategy implements ReconnectionStrategy {
private delay: Long
constructor(delay: Long) {
this.delay = delay
}
func calculateDelay(attempt: Integer): Long {
return delay
}
}
class ExponentialBackoffStrategy implements ReconnectionStrategy {
private initialDelay: Long
private maxDelay: Long
constructor(initialDelay: Long, maxDelay: Long) {
this.initialDelay = initialDelay
this.maxDelay = maxDelay
}
func calculateDelay(attempt: Integer): Long {
delay = initialDelay * (2 ^ attempt)
return min(delay, maxDelay)
}
}
策略模式流程图:
重连状态机需要计算延迟
↓
获取当前重连策略
↓
┌─────────────────┐
│ 策略类型判断 │
└────────┬────────┘
│
┌────┴────┬──────────────┬──────────────┐
│ │ │ │
立即策略 固定延迟策略 指数退避策略 自定义策略
│ │ │ │
↓ ↓ ↓ ↓
返回 0 返回固定值 计算指数延迟 调用自定义函数
│ │ │ │
│ │ │ │
└─────────┴──────────────┴──────────────┘
↓
返回延迟时间
3. 状态机模式 (State Machine Pattern)
应用场景:
DTReconnectionStateMachine- 重连状态机DTBleCentralModeStateMachine- 连接状态机
状态流转:
idle → reconnecting → succeeded/failed/paused
设计意图:
- 清晰的状态管理
- 防止非法状态转换
- 便于状态追踪和调试
伪代码实现:
enum ReconnectionState {
Idle,
Reconnecting(attempt: Integer),
Succeeded,
Failed,
Paused
}
class DTReconnectionStateMachine {
private currentState: ReconnectionState = ReconnectionState.Idle
func transitionTo(newState: ReconnectionState) throws {
if isValidTransition(from: currentState, to: newState) {
currentState = newState
notifyStateChanged(newState)
} else {
throw InvalidStateTransitionException()
}
}
}
状态机模式流程图:
初始状态: Idle
↓
startReconnection()
↓
┌─────────────────┐
│ 状态转换判断 │
└────────┬────────┘
│
┌────┴────┐
│ │
Idle 其他状态
│ │
↓ ↓
转换到 拒绝转换
Reconnecting
│
↓
┌─────────────────┐
│ 重连过程中 │
└────────┬────────┘
│
┌────┴────┬──────────┬──────────┐
│ │ │ │
连接成功 连接失败 暂停请求 达到最大重试
│ │ │ │
↓ ↓ ↓ ↓
Succeeded 继续重试 Paused Failed
│ │ │ │
│ │ │ │
│ │ │ │
└─────────┴──────────┴──────────┘
↓
转换回 Idle
4. 观察者模式 (Observer Pattern)
应用场景:
- 回调机制:
onDiscoveredDevicesChanged、onConnectedDevicesChanged等 - 事件通知:蓝牙状态变化、连接状态变化
- 使用响应式编程框架实现数据流
设计意图:
- 解耦业务逻辑和 UI 层
- 支持多个观察者
- 实时响应状态变化
伪代码实现:
class BleServiceImpl {
private discoveredDevices: Observable<List<BluetoothDevice>>
private connectedDevices: Observable<List<BluetoothDevice>>
// 或者使用回调方式
var onDiscoveredDevicesChanged: Callback<List<BluetoothDevice>>
var onConnectedDevicesChanged: Callback<List<BluetoothDevice>>
// 通知观察者
func notifyDevicesChanged(devices: List<BluetoothDevice>) {
discoveredDevices.emit(devices)
if onDiscoveredDevicesChanged != null {
onDiscoveredDevicesChanged.invoke(devices)
}
}
}
观察者模式流程图:
设备状态发生变化
↓
BleServiceImpl 检测到变化
↓
┌─────────────────┐
│ 通知所有观察者 │
└────────┬────────┘
│
┌────┴────┬──────────────┐
│ │ │
Observable 回调函数1 回调函数2
│ │ │
↓ ↓ ↓
发送事件 执行回调 执行回调
│ │ │
│ │ │
└─────────┴──────────────┘
↓
观察者更新状态
5. 工厂模式 (Factory Pattern)
应用场景:
BluetoothProviderFactory- 创建不同类型的 ProviderDeviceConnectionContext- 根据 Channel 创建不同的上下文
设计意图:
- 统一创建逻辑
- 隐藏复杂的初始化过程
- 支持多设备类型扩展
伪代码实现:
class BluetoothProviderFactory {
static func createProvider(
type: BluetoothType,
platformContext: PlatformContext
): BluetoothProvider {
switch (type) {
case CLASSIC:
return new ClassicBluetoothProvider(platformContext)
case BLE:
return new BleProvider(platformContext)
default:
throw UnsupportedBluetoothTypeException()
}
}
}
工厂模式流程图:
需要创建 BluetoothProvider
↓
调用 Factory.createProvider()
↓
传入蓝牙类型和平台上下文
↓
┌─────────────────┐
│ 类型判断 │
└────────┬────────┘
│
┌────┴────┐
│ │
经典蓝牙 BLE
│ │
↓ ↓
创建经典 创建 BLE
蓝牙Provider Provider
│ │
└────┬────┘
↓
返回 Provider
6. 适配器模式 (Adapter Pattern)
应用场景:
BluetoothProvider统一接口,屏蔽经典蓝牙和 BLE 的差异- 适配不同平台的蓝牙 API
设计意图:
- 统一接口抽象
- 屏蔽底层实现差异
- 简化业务层使用
三、经典蓝牙与 BLE 双模式支持
核心设计
DTBluetoothProvider 同时支持经典蓝牙(Classic Bluetooth)和低功耗蓝牙(BLE),通过统一的接口抽象,屏蔽底层实现差异:
-
经典蓝牙(Classic Bluetooth)
- 适用于大数据传输、音频传输等场景
- 支持 RFCOMM、L2CAP 等协议
- 连接方式:通过 UUID 或固定端口
-
低功耗蓝牙(BLE)
- 适用于低功耗、小数据传输场景
- 支持 GATT 服务和特征值操作
- 连接方式:通过 Service UUID 和 Characteristic UUID
统一接口抽象
interface BluetoothProvider {
// 扫描设备
func scanDevices(
type: BluetoothType,
callback: Callback<List<BluetoothDevice>>
)
// 连接设备
func connect(
device: BluetoothDevice,
type: BluetoothType
): Boolean
// 断开设备
func disconnect(deviceAddress: String)
// 读取数据
func readData(
deviceAddress: String,
characteristic: BluetoothCharacteristic
): ByteArray?
// 写入数据
func writeData(
deviceAddress: String,
characteristic: BluetoothCharacteristic,
data: ByteArray
): Boolean
// 订阅通知
func subscribeNotify(
deviceAddress: String,
characteristic: BluetoothCharacteristic,
callback: Callback<ByteArray>
)
}
enum BluetoothType {
CLASSIC, // 经典蓝牙
BLE // 低功耗蓝牙
}
// 统一的数据结构
enum BluetoothCharacteristic {
// 经典蓝牙使用 UUID 或端口号
Classic(uuid: UUID?, port: Integer?),
// BLE 使用 Service UUID 和 Characteristic UUID
Ble(serviceUuid: UUID, characteristicUuid: UUID)
}
统一接口抽象流程图:
业务层调用蓝牙操作
↓
通过 BluetoothProvider 接口
↓
┌─────────────────┐
│ 根据类型选择实现 │
└────────┬────────┘
│
┌────┴────┐
│ │
经典蓝牙 BLE
│ │
↓ ↓
ClassicBluetooth BleProvider
Provider 实现
│ │
└────┬────┘
↓
调用平台API
↓
返回结果
实现示例
// 经典蓝牙实现
class ClassicBluetoothProvider implements BluetoothProvider {
private connectedSockets: Map<String, BluetoothSocket> = {}
func connect(device: BluetoothDevice, type: BluetoothType): Boolean {
if (type != BluetoothType.CLASSIC) {
return false
}
try {
// 使用 RFCOMM 连接
socket = device.createRfcommSocket(uuid)
socket.connect()
connectedSockets[device.address] = socket
return true
} catch (Exception e) {
return false
}
}
func writeData(
deviceAddress: String,
characteristic: BluetoothCharacteristic,
data: ByteArray
): Boolean {
socket = connectedSockets[deviceAddress]
if (socket == null) {
return false
}
try {
socket.outputStream.write(data)
return true
} catch (Exception e) {
return false
}
}
}
// BLE 实现
class BleProvider implements BluetoothProvider {
private connectedGatts: Map<String, BluetoothGatt> = {}
func connect(device: BluetoothDevice, type: BluetoothType): Boolean {
if (type != BluetoothType.BLE) {
return false
}
gatt = device.connectGatt(callback)
if (gatt.connectionState == CONNECTED) {
connectedGatts[device.address] = gatt
return true
}
return false
}
func writeData(
deviceAddress: String,
characteristic: BluetoothCharacteristic,
data: ByteArray
): Boolean {
gatt = connectedGatts[deviceAddress]
if (gatt == null) {
return false
}
if (characteristic.type != BLE) {
return false
}
service = gatt.getService(characteristic.serviceUuid)
char = service.getCharacteristic(characteristic.characteristicUuid)
if (char != null) {
char.value = data
gatt.writeCharacteristic(char)
return true
}
return false
}
}
// 工厂类根据类型创建对应的 Provider
class BluetoothProviderFactory {
static func createProvider(
type: BluetoothType,
platformContext: PlatformContext
): BluetoothProvider {
switch (type) {
case CLASSIC:
return new ClassicBluetoothProvider(platformContext)
case BLE:
return new BleProvider(platformContext)
default:
throw UnsupportedBluetoothTypeException()
}
}
}
经典蓝牙与 BLE 双模式支持流程图:
需要连接蓝牙设备
↓
获取设备类型
↓
┌─────────────────┐
│ 设备类型判断 │
└────────┬────────┘
│
┌────┴────┐
│ │
经典蓝牙 BLE
│ │
↓ ↓
使用RFCOMM 使用GATT
协议连接 协议连接
│ │
↓ ↓
建立Socket 建立GATT
连接 连接
│ │
└────┬────┘
↓
连接成功
↓
统一接口返回
设计优势
- ✅ 统一接口:经典蓝牙和 BLE 使用相同的接口,降低使用复杂度
- ✅ 自动适配:根据设备类型自动选择合适的实现
- ✅ 灵活切换:支持运行时切换不同的蓝牙类型
- ✅ 向后兼容:保持与现有代码的兼容性
- ✅ 平台无关:接口设计不依赖特定平台实现
四、多设备管理设计
核心概念
-
Channel(设备类型)
- 定义:设备类型枚举
- 作用:区分不同类型的蓝牙设备
- 示例:
_zdeer_ai_earphones、_tj_ej121
-
ChannelNumb(设备编号)
- 定义:同一类型设备的序号
- 作用:支持同时连接多个同类型设备
- 规则:从 0 开始递增
-
RealChannelValue(真实通道值)
- 格式:
"_" + channel.rawValue + "_" + channelNumb - 示例:
"_zdeer_ai_0"、"_zdeer_ai_1" - 作用:唯一标识一个设备连接上下文
- 格式:
设计优势
// 支持同时连接多个设备
viewModel.initDevice1() // Channel: _zdeer_ai_0
viewModel.initDevice2() // Channel: _zdeer_ai_1
// 每个设备独立的连接上下文
context1 = bleService.queryConnectionContext(device1.address)
context2 = bleService.queryConnectionContext(device2.address)
多设备管理流程图:
初始化设备连接上下文
↓
┌─────────────────┐
│ 设备类型判断 │
└────────┬────────┘
│
┌────┴────┐
│ │
同类型设备 不同类型设备
│ │
↓ ↓
使用channelNumb 使用Channel
区分(0,1,2...) 区分
│ │
└────┬────┘
↓
生成 RealChannelValue
↓
创建 DeviceConnectionContext
↓
存储到连接上下文映射
↓
每个设备独立管理
(指令队列/连接状态/重连状态机)
优势:
- ✅ 支持同时连接多个同类型设备
- ✅ 支持同时连接多个不同类型设备
- ✅ 每个设备独立的指令队列
- ✅ 线程安全的连接管理
五、指令缓冲工具设计
核心功能
BleCommandBuffer 实现了智能指令队列管理:
-
串行执行
- 确保指令按顺序发送
- 一个指令完成后再发送下一条
- 避免硬件处理冲突
-
智能去重
- 无参数指令(readData, subscribe_notifyData):自动忽略重复
- 有参数指令(writeData):可自定义比较逻辑
-
参数更新决策
- 通过
shouldUpdateCommand回调自定义 - 支持根据数据内部结构决定是否更新
- 通过
设计实现
// 指令唯一标识
abstract class BleCommand {
abstract uniqueKey: String
abstract isParameterless: Boolean
}
class ReadDataCommand extends BleCommand {
characteristicUuid: String
uniqueKey = "read_" + characteristicUuid
isParameterless = true
}
class WriteDataCommand extends BleCommand {
characteristicUuid: String
data: ByteArray
uniqueKey = "write_" + characteristicUuid
isParameterless = false
equals(other: Object): Boolean {
if (other is not WriteDataCommand) {
return false
}
return characteristicUuid == other.characteristicUuid &&
data.equals(other.data)
}
}
class SubscribeNotifyCommand extends BleCommand {
characteristicUuid: String
uniqueKey = "subscribe_" + characteristicUuid
isParameterless = true
}
// 智能去重逻辑
class BleCommandBuffer {
private commandQueues: Map<String, Queue<BleCommand>> = {}
var shouldUpdateCommand: Callback<BleCommand, BleCommand, Boolean> = null
func addCommand(command: BleCommand, deviceAddress: String): Boolean {
queue = commandQueues.getOrCreate(deviceAddress)
existingCommand = queue.find(command.uniqueKey)
if (existingCommand != null) {
if (command.isParameterless) {
// 无参数指令,直接忽略
return false
} else {
// 有参数指令,由业务层决定
shouldUpdate = shouldUpdateCommand?.invoke(existingCommand, command) ?? true
if (!shouldUpdate) {
return false
}
queue.remove(existingCommand)
}
}
queue.offer(command)
return true
}
}
指令缓冲工具流程图:
添加指令到队列
↓
检查设备队列是否存在
↓
┌─────────────────┐
│ 队列是否存在? │
└────────┬────────┘
│
┌────┴────┐
│ │
否 是
│ │
↓ ↓
创建新队列 检查队列中
│ 是否存在相同指令
│ │
│ ┌────┴────┐
│ │ │
│ 否 是
│ │ │
│ ↓ ↓
│ 直接添加 检查指令类型
│ │ │
│ │ ┌────┴────┐
│ │ │ │
│ │ 无参数 有参数
│ │ │ │
│ │ ↓ ↓
│ │ 忽略指令 调用更新策略
│ │ │ │
│ │ │ ┌────┴────┐
│ │ │ │ │
│ │ │ 需要更新 不需要更新
│ │ │ │ │
│ │ │ ↓ ↓
│ │ │ 移除旧指令 忽略新指令
│ │ │ │ │
│ │ │ ↓ │
│ │ │ 添加新指令 │
│ │ │ │ │
│ └────┴────┴─────────┘
│ │
└──────────────┘
↓
指令已添加
↓
触发执行(如果队列为空)
设计优势:
- ✅ 防止指令冲突
- ✅ 减少不必要的网络请求
- ✅ 灵活的自定义策略
- ✅ 线程安全的队列管理
持久化缓存机制
指令缓冲工具支持持久化缓存,应用重启后可以恢复未完成的指令:
class BleCommandBuffer {
var isPersistenceEnabled: Boolean = true // 默认启用
// 手动保存到磁盘
func saveToDisk() {
if (isPersistenceEnabled) {
// 将指令队列序列化为 JSON 并保存到本地存储
json = serializeCommandQueues()
storage.save(json, key: "command_queues")
}
}
// 从磁盘加载
func loadFromDisk() {
json = storage.load(key: "command_queues")
if (json != null) {
deserializeCommandQueues(json)
}
}
// 设备连接后恢复指令
func restoreCommands(device: BluetoothDevice, bleService: BleServiceImpl): Integer {
savedCommands = loadCommandsForDevice(device.address)
if (savedCommands == null) {
return 0
}
restoredCount = 0
for (command in savedCommands) {
if (addCommand(command, deviceAddress: device.address)) {
restoredCount++
}
}
return restoredCount
}
}
持久化缓存流程图:
应用关闭/崩溃
↓
自动保存指令队列
↓
序列化为 JSON
↓
保存到本地存储
↓
┌─────────────────┐
│ 应用重启 │
└────────┬────────┘
↓
从本地存储加载
↓
反序列化 JSON
↓
恢复指令队列
↓
设备重新连接
↓
┌─────────────────┐
│ 恢复指令到队列 │
└────────┬────────┘
↓
继续执行指令
自定义指令更新策略
业务层可以通过 shouldUpdateCommand 回调自定义指令比较逻辑:
class BluetoothViewModel {
private func setupCommandUpdatePolicy() {
commandBuffer.shouldUpdateCommand = function(existingCommand, newCommand) {
// 确保两个指令都是有参数的写入指令,且特征值相同
if (existingCommand.type != WRITE_DATA ||
newCommand.type != WRITE_DATA ||
existingCommand.characteristicUuid != newCommand.characteristicUuid) {
return existingCommand.data != newCommand.data
}
existingData = existingCommand.data
newData = newCommand.data
// 策略1: 根据 Data 的第一个字节(指令类型)来决定
if (existingData.length > 0 && newData.length > 0) {
existingCmdType = existingData[0]
newCmdType = newData[0]
if (existingCmdType == newCmdType) {
// 指令类型相同,比较完整数据
return existingData != newData
} else {
// 指令类型不同,需要更新
return true
}
}
// 策略2: 如果数据长度不同,总是更新
if (existingData.length != newData.length) {
return true
}
// 策略3: 默认行为:数据不同则更新
return existingData != newData
}
}
}
指令更新策略流程图:
检测到重复指令
↓
检查指令类型
↓
┌─────────────────┐
│ 指令类型判断 │
└────────┬────────┘
│
┌────┴────┐
│ │
无参数指令 有参数指令
│ │
↓ ↓
直接忽略 调用更新策略
│ │
│ ┌────┴────┐
│ │ │
│ 比较特征值 │
│ │ │
│ ↓ │
│ 特征值相同? │
│ │ │
│ ┌──┴──┐ │
│ │ │ │
│是 否 │
│ │ │ │
│ ↓ ↓ │
│比较数据 直接更新
│ │ │ │
│ ↓ │ │
│数据不同? │
│ │ │ │
│ ┌──┴──┐ │
│ │ │ │
│是 否 │
│ │ │ │
│ ↓ ↓ │
│更新 忽略 │
│ │ │ │
└─┴─────┴─────┘
↓
完成处理
指令超时机制
指令缓冲工具内置超时检测机制:
class BleCommandBuffer {
private defaultTimeouts: Map<CommandType, Long> = {
READ_DATA: 5000, // 5秒
WRITE_DATA: 5000, // 5秒
SUBSCRIBE_NOTIFY: 3000 // 3秒
}
private func executeCommand(command: BleCommand, deviceAddress: String) {
timeout = defaultTimeouts.get(command.type, 5000)
async {
try {
result = await withTimeout(timeout) {
performCommand(command, deviceAddress: deviceAddress)
}
// 指令完成,继续下一条
processNextCommand(deviceAddress: deviceAddress)
} catch (TimeoutException e) {
// 超时处理
handleCommandTimeout(command, deviceAddress: deviceAddress)
processNextCommand(deviceAddress: deviceAddress)
}
}
}
}
指令超时机制流程图:
开始执行指令
↓
获取指令类型
↓
┌─────────────────┐
│ 获取超时时间 │
└────────┬────────┘
↓
启动超时计时器
↓
执行指令操作
↓
┌─────────────────┐
│ 指令执行结果 │
└────────┬────────┘
│
┌────┴────┬──────────┐
│ │ │
成功完成 超时 失败
│ │ │
↓ ↓ ↓
停止计时器 触发超时 处理失败
│ 处理 │
│ │ │
│ ↓ │
│ 记录超时日志 │
│ │ │
│ ↓ │
└─────────┴──────────┘
↓
继续下一条指令
业务场景示例
- 音量调节:左右耳音量不同,需要分别发送 → 应该更新
- 相同指令类型:如果 Data 的第一个字节相同,且完整数据相同 → 忽略
- 不同指令类型:如果 Data 的第一个字节不同 → 应该更新
六、设备绑定缓存设计
核心功能
DTDeviceBindingCache 实现了设备绑定记录的持久化存储:
-
持久化存储
- JSON 格式保存到本地存储
- 应用启动时自动加载
- 操作后自动保存
-
绑定记录管理
- 保存设备信息(设备地址、名称、连接时间等)
- 记录连接次数和最后连接/断开时间
- 支持启用/禁用单个设备的自动重连
-
重连决策
shouldStartReconnection()方法根据绑定记录决定是否启动重连- 检查是否有绑定记录
- 检查是否启用自动重连
数据结构
class DeviceBindingRecord {
deviceAddress: String // 设备地址
deviceName: String
bindingTime: Long // 首次绑定时间(时间戳)
lastConnectionTime: Long // 最后连接时间
lastDisconnectionTime: Long? // 最后断开时间
connectionCount: Integer = 0 // 连接次数
autoReconnectEnabled: Boolean = true // 是否启用自动重连
channel: String? // 设备类型
metadata: Map<String, String>? // 自定义元数据
toJson(): String {
return JSON.serialize(this)
}
static fromJson(json: String): DeviceBindingRecord? {
return JSON.deserialize(json, DeviceBindingRecord)
}
}
class DTDeviceBindingCache {
private static shared: DTDeviceBindingCache = null
private bindingRecords: Map<String, DeviceBindingRecord> = {}
private storage: LocalStorage
static func getInstance(): DTDeviceBindingCache {
if (shared == null) {
shared = new DTDeviceBindingCache()
}
return shared
}
constructor() {
this.storage = new LocalStorage()
loadFromStorage()
}
func saveBinding(
device: BluetoothDevice,
channel: String? = null,
autoReconnectEnabled: Boolean = true
) {
now = currentTime()
existingRecord = bindingRecords[device.address]
if (existingRecord != null) {
existingRecord.lastConnectionTime = now
existingRecord.connectionCount++
existingRecord.autoReconnectEnabled = autoReconnectEnabled
bindingRecords[device.address] = existingRecord
} else {
newRecord = new DeviceBindingRecord(
deviceAddress: device.address,
deviceName: device.name ?? "Unknown",
bindingTime: now,
lastConnectionTime: now,
lastDisconnectionTime: null,
connectionCount: 1,
autoReconnectEnabled: autoReconnectEnabled,
channel: channel,
metadata: null
)
bindingRecords[device.address] = newRecord
}
saveToStorage()
}
func shouldStartReconnection(deviceAddress: String): Boolean {
record = bindingRecords[deviceAddress]
if (record == null) {
return false
}
return record.autoReconnectEnabled
}
private func loadFromStorage() {
json = storage.getString("binding_records")
if (json != null) {
bindingRecords = JSON.deserialize(json, Map<String, DeviceBindingRecord>)
}
}
private func saveToStorage() {
json = JSON.serialize(bindingRecords)
storage.putString("binding_records", json)
}
}
设备绑定缓存流程图:
设备连接成功
↓
调用 saveBinding()
↓
┌─────────────────┐
│ 检查是否已有记录 │
└────────┬────────┘
│
┌────┴────┐
│ │
是 否
│ │
↓ ↓
更新记录 创建新记录
(连接次数+1) (首次绑定)
(更新时间) (记录时间)
│ │
└────┬────┘
↓
保存到内存
↓
序列化为 JSON
↓
保存到本地存储
↓
完成
重连决策流程图:
设备断开连接
↓
调用 shouldStartReconnection()
↓
┌─────────────────┐
│ 查找绑定记录 │
└────────┬────────┘
│
┌────┴────┐
│ │
找到记录 未找到记录
│ │
↓ ↓
检查自动 返回 false
重连标志 (不重连)
│
┌───┴───┐
│ │
启用 未启用
│ │
↓ ↓
返回true 返回false
(启动重连) (不重连)
设计优势:
- ✅ 持久化存储,应用重启后仍可恢复
- ✅ 智能决策,避免不必要的重连
- ✅ 线程安全,支持并发访问
- ✅ 灵活配置,支持自定义元数据
- ✅ 平台无关,使用通用的本地存储接口
工作流程
设备连接成功
↓
自动保存绑定记录(内存 + 硬盘)
↓
设备断开连接
↓
检查是否有绑定记录
↓
有绑定记录 && 启用自动重连?
↓
是 → 启动重连状态机
否 → 不重连(可能是用户主动断开)
使用方式
在 BluetoothViewModel 中已自动集成,无需手动调用:
// 连接成功时自动保存(已集成)
// 断开连接时自动决策(已集成)
// 手动操作示例
let bindingCache = DTDeviceBindingCache.getInstance()
// 检查是否有绑定记录
if bindingCache.hasBinding(for: device.address) {
print("设备已绑定")
}
// 获取绑定记录
if let record = bindingCache.getBinding(for: device.address) {
print("绑定时间: \(record.bindingTime)")
print("连接次数: \(record.connectionCount)")
}
// 禁用自动重连
bindingCache.setAutoReconnectEnabled(false, for: device.address)
// 删除绑定记录(取消绑定)
bindingCache.removeBinding(for: device.address)
// 获取所有已绑定设备
let boundDevices = bindingCache.getAllBindings()
重连机制说明
冷启动重连:依赖于 DTDeviceBindingCache.shared 中的缓存
- 扫描回调中,匹配最后一次连接的设备
- 若一个扫描周期内(默认15秒)仍未匹配成功,则触发历史绑定痕迹遍历
- 优先重连距离当前最近时间的设备绑定痕迹
- 若扫描周期内匹配成功,结束扫描并自动重连
热启动重连:依赖于重连状态机的具体实现
- 设备断开后,根据绑定记录自动启动重连状态机
- 支持多种重连策略和状态管理
七、重连状态机设计
核心功能
DTReconnectionStateMachine 管理设备断开后的重连逻辑:
-
状态管理
idle- 空闲状态reconnecting- 正在重连中paused- 暂停重连failed- 重连失败succeeded- 重连成功
-
重连策略
immediate- 立即重连fixedDelay- 固定延迟exponentialBackoff- 指数退避custom- 自定义策略
-
配置选项
- 最大重试次数
- 连接超时时间
- 蓝牙关闭时暂停
- 应用进入后台时暂停
- 冷却期机制
状态流转图
[idle]
│
│ startReconnection()
▼
[reconnecting]
│
├─→ notifyConnectionSucceeded() → [succeeded] → [idle]
│
├─→ pauseReconnection() → [paused]
│ │
│ │ resumeReconnection()
│ └─→ [reconnecting]
│
└─→ maxRetries reached → [failed] → [idle]
伪代码实现:
class DTReconnectionStateMachine {
private strategy: ReconnectionStrategy
private maxRetries: Integer = 5
private connectionTimeout: Long = 30000
private currentState: ReconnectionState = ReconnectionState.Idle
private retryCount: Integer = 0
private reconnectionTask: Task = null
private stateObservable: Observable<ReconnectionState>
constructor(strategy: ReconnectionStrategy) {
this.strategy = strategy
this.stateObservable = new Observable()
}
func startReconnection(
deviceAddress: String,
connectFunction: Function<String, Boolean>
) {
if (currentState != ReconnectionState.Idle) {
return
}
currentState = ReconnectionState.Reconnecting(0)
stateObservable.emit(currentState)
retryCount = 0
reconnectionTask = async {
while (retryCount < maxRetries &&
currentState is ReconnectionState.Reconnecting) {
delay = strategy.calculateDelay(retryCount)
if (delay > 0) {
sleep(delay)
}
try {
success = timeout(connectionTimeout) {
connectFunction(deviceAddress)
}
if (success) {
currentState = ReconnectionState.Succeeded
stateObservable.emit(currentState)
currentState = ReconnectionState.Idle
stateObservable.emit(currentState)
return
} else {
retryCount++
currentState = ReconnectionState.Reconnecting(retryCount)
stateObservable.emit(currentState)
}
} catch (TimeoutException e) {
retryCount++
currentState = ReconnectionState.Reconnecting(retryCount)
stateObservable.emit(currentState)
} catch (Exception e) {
retryCount++
currentState = ReconnectionState.Reconnecting(retryCount)
stateObservable.emit(currentState)
}
}
if (retryCount >= maxRetries) {
currentState = ReconnectionState.Failed
stateObservable.emit(currentState)
currentState = ReconnectionState.Idle
stateObservable.emit(currentState)
}
}
}
func pauseReconnection() {
if (currentState is ReconnectionState.Reconnecting) {
reconnectionTask?.cancel()
currentState = ReconnectionState.Paused
stateObservable.emit(currentState)
}
}
func resumeReconnection(deviceAddress: String, connectFunction: Function<String, Boolean>) {
if (currentState is ReconnectionState.Paused) {
startReconnection(deviceAddress, connectFunction)
}
}
func stopReconnection() {
reconnectionTask?.cancel()
currentState = ReconnectionState.Idle
stateObservable.emit(currentState)
retryCount = 0
}
}
重连状态机完整流程图:
初始状态: Idle
↓
startReconnection()
↓
┌─────────────────┐
│ 状态检查 │
└────────┬────────┘
│
┌────┴────┐
│ │
Idle 其他状态
│ │
↓ ↓
转换到 拒绝启动
Reconnecting
│
↓
初始化重试计数 = 0
↓
┌─────────────────┐
│ 重连循环 │
└────────┬────────┘
│
┌────┴────┐
│ │
重试次数<最大 重试次数>=最大
│ │
↓ ↓
计算延迟 转换到 Failed
│ │
↓ │
等待延迟 │
│ │
↓ │
执行连接 │
│ │
↓ │
┌───┴───┐ │
│ │ │
成功 失败 │
│ │ │
↓ ↓ │
转换到 重试计数++ │
Succeeded │ │
│ 转换到 │
│Reconnecting│
│ │ │
│ └─────┘
│ │
└─────┘
↓
转换回 Idle
设计优势:
- ✅ 清晰的状态管理
- ✅ 灵活的重连策略
- ✅ 支持暂停和恢复
- ✅ 完善的错误处理
- ✅ 平台无关的异步实现
快速配置预设
// 立即重连
config1 = new ReconnectionConfiguration(
maxRetries: 3,
strategy: new ImmediateStrategy(),
connectionTimeout: 10000
)
// 温和重连(较长延迟)
config2 = new ReconnectionConfiguration(
maxRetries: 5,
strategy: new FixedDelayStrategy(5000),
connectionTimeout: 30000
)
// 积极重连(快速重试)
config3 = new ReconnectionConfiguration(
maxRetries: 10,
strategy: new ExponentialBackoffStrategy(1000, 10000),
connectionTimeout: 15000
)
// 智能重连(带冷却期)
config4 = new ReconnectionConfiguration(
maxRetries: 5,
strategy: new ExponentialBackoffStrategy(2000, 60000),
connectionTimeout: 30000,
cooldownPeriod: 300000 // 5分钟冷却期
)
连接质量评估
重连状态机支持基于 RSSI 的连接质量评估和策略调整:
class DTReconnectionStateMachine {
private rssiHistory: List<Integer> = []
func updateRSSI(rssi: Integer) {
rssiHistory.add(rssi)
// 保持最近 N 个 RSSI 值
if (rssiHistory.size() > 10) {
rssiHistory.remove(0)
}
}
func assessConnectionQuality(): ConnectionQuality {
if (rssiHistory.isEmpty()) {
return ConnectionQuality.Unknown
}
averageRSSI = rssiHistory.sum() / rssiHistory.size()
if (averageRSSI >= -50) {
return ConnectionQuality.Excellent
} else if (averageRSSI >= -65) {
return ConnectionQuality.Good
} else if (averageRSSI >= -80) {
return ConnectionQuality.Fair
} else {
return ConnectionQuality.Poor
}
}
func adjustStrategyBasedOnRSSI(rssi: Integer) {
quality = assessConnectionQuality()
switch (quality) {
case Excellent, Good:
// 信号良好,使用快速重连策略
strategy = new ImmediateStrategy()
case Fair:
// 信号一般,使用固定延迟
strategy = new FixedDelayStrategy(2000)
case Poor:
// 信号较差,使用指数退避
strategy = new ExponentialBackoffStrategy(5000, 60000)
case Unknown:
break
}
}
}
enum ConnectionQuality {
Excellent,
Good,
Fair,
Poor,
Unknown
}
连接质量评估流程图:
更新 RSSI 值
↓
添加到历史记录
↓
保持最近 N 个值
↓
计算平均 RSSI
↓
┌─────────────────┐
│ RSSI 范围判断 │
└────────┬────────┘
│
┌────┴────┬──────────┬──────────┐
│ │ │ │
>= -50 >= -65 >= -80 < -80
│ │ │ │
↓ ↓ ↓ ↓
Excellent Good Fair Poor
│ │ │ │
↓ ↓ ↓ ↓
立即重连 立即重连 固定延迟 指数退避
│ │ │ │
└─────────┴──────────┴──────────┘
↓
调整重连策略
使用方式
在 BluetoothViewModel 中已自动集成,设备断开时会根据绑定缓存自动启动:
// 手动创建重连状态机
let stateMachine = DTReconnectionStateMachine(
deviceUUID: device.address,
deviceName: device.name,
configuration: ReconnectionConfiguration(
maxRetries: 5,
strategy: .exponentialBackoff(initialDelay: 2.0, multiplier: 2.0, maxDelay: 60.0),
connectionTimeout: 30.0
)
)
// 设置回调
stateMachine.onStateChanged = { newState, oldState in
print("状态变化: \(oldState) -> \(newState)")
}
stateMachine.onReconnectionSucceeded = { elapsedTime in
print("重连成功,耗时: \(elapsedTime)秒")
}
// 开始重连
stateMachine.startReconnection { completion in
// 执行重连操作
bleService.connect(peripheral: device) { success in
completion(success)
}
}
// 通知连接结果
stateMachine.notifyConnectionSucceeded() // 或 notifyConnectionFailed()
八、数据包封装设计
核心功能
DTDataPacket 用于包装和解析硬件数据:
-
数据包构建
- 从命令类型和载荷构建数据包
- 支持多种格式:默认、带帧头、带长度、完整格式
- 自动计算长度和校验和
-
数据包解析
- 从原始字节数组解析数据包
- 支持多种格式的自动识别和解析
- 提取帧头、命令类型、载荷、校验和、帧尾等
-
支持的格式
- 默认格式:命令类型 + 载荷
- 带帧头格式:帧头 + 命令类型 + 载荷
- 带长度格式:帧头 + 长度 + 命令类型 + 载荷
- 完整格式:帧头 + 长度 + 命令类型 + 载荷 + 校验和 + 帧尾
设计实现
class DTDataPacket {
frameHeader: Byte? = null
length: Integer? = null
commandType: Byte
payload: ByteArray
checksum: Byte? = null
frameTail: Byte? = null
private static DEFAULT_FRAME_HEADER: Byte = 0xAA
private static DEFAULT_FRAME_TAIL: Byte = 0x55
constructor(
frameHeader: Byte? = null,
length: Integer? = null,
commandType: Byte,
payload: ByteArray,
checksum: Byte? = null,
frameTail: Byte? = null
) {
this.frameHeader = frameHeader
this.length = length
this.commandType = commandType
this.payload = payload
this.checksum = checksum
this.frameTail = frameTail
}
static func forBluetoothSend(
commandType: Byte,
payload: ByteArray,
format: PacketFormat = PacketFormat.DEFAULT
): DTDataPacket {
switch (format) {
case DEFAULT:
return new DTDataPacket(
commandType: commandType,
payload: payload
)
case WITH_HEADER:
return new DTDataPacket(
frameHeader: DEFAULT_FRAME_HEADER,
commandType: commandType,
payload: payload
)
case WITH_LENGTH:
return new DTDataPacket(
frameHeader: DEFAULT_FRAME_HEADER,
length: payload.length + 1, // +1 for commandType
commandType: commandType,
payload: payload
)
case FULL:
data = [commandType] + payload
checksum = calculateChecksum(data)
return new DTDataPacket(
frameHeader: DEFAULT_FRAME_HEADER,
length: data.length,
commandType: commandType,
payload: payload,
checksum: checksum,
frameTail: DEFAULT_FRAME_TAIL
)
}
}
static func fromBluetoothData(data: ByteArray): DTDataPacket? {
if (data.length < 2 ||
data[0] != DEFAULT_FRAME_HEADER ||
data[data.length - 1] != DEFAULT_FRAME_TAIL) {
return null
}
// 完整格式解析
length = data[1] & 0xFF
if (data.length < 3 + length) {
return null
}
commandType = data[2]
payload = data[3..(3 + length - 1)]
checksum = data[data.length - 2]
return new DTDataPacket(
frameHeader: data[0],
length: length,
commandType: commandType,
payload: payload,
checksum: checksum,
frameTail: data[data.length - 1]
)
}
private static func calculateChecksum(data: ByteArray): Byte {
sum = 0
for (byte in data) {
sum += byte & 0xFF
}
return (sum & 0xFF) as Byte
}
func toByteArray(): ByteArray {
result = []
if (frameHeader != null) {
result.add(frameHeader)
}
if (length != null) {
result.add(length as Byte)
}
result.add(commandType)
result.addAll(payload)
if (checksum != null) {
result.add(checksum)
}
if (frameTail != null) {
result.add(frameTail)
}
return result
}
func verifyChecksum(): Boolean {
if (checksum == null) {
return true
}
data = [commandType] + payload
calculatedChecksum = calculateChecksum(data)
return checksum == calculatedChecksum
}
}
enum PacketFormat {
DEFAULT,
WITH_HEADER,
WITH_LENGTH,
FULL
}
数据包构建流程图:
调用 forBluetoothSend()
↓
传入命令类型、载荷、格式
↓
┌─────────────────┐
│ 格式类型判断 │
└────────┬────────┘
│
┌────┴────┬──────────┬──────────┐
│ │ │ │
默认格式 带帧头格式 带长度格式 完整格式
│ │ │ │
↓ ↓ ↓ ↓
命令+载荷 帧头+命令+载荷 帧头+长度+命令+载荷 帧头+长度+命令+载荷+校验+帧尾
│ │ │ │
│ │ │ ↓
│ │ │ 计算校验和
│ │ │ │
└─────────┴──────────┴──────────┘
↓
创建数据包对象
↓
返回数据包
数据包解析流程图:
接收原始数据
↓
检查数据长度
↓
┌─────────────────┐
│ 检查帧头和帧尾 │
└────────┬────────┘
│
┌────┴────┐
│ │
匹配 不匹配
│ │
↓ ↓
读取长度 返回 null
↓
检查数据完整性
↓
┌─────────────────┐
│ 数据是否完整? │
└────────┬────────┘
│
┌────┴────┐
│ │
是 否
│ │
↓ ↓
提取各部分 返回 null
(帧头/长度/命令/载荷/校验/帧尾)
│
↓
创建数据包对象
│
↓
返回数据包
设计优势:
- ✅ 统一的数据包格式
- ✅ 自动校验和验证
- ✅ 便捷的数据访问方法
- ✅ 支持多种硬件协议格式
- ✅ 平台无关的实现
便捷方法
class DTDataPacket {
// 获取十六进制字符串
func getHexString(): String { ... }
func getHexStringUppercase(): String { ... }
func getHexStringWithSpaces(): String { ... }
func getPayloadHexString(): String { ... }
// 访问原始数据
func byteAt(index: Integer): Byte? { ... }
func bytesInRange(range: Range<Integer>): ByteArray? { ... }
// 访问载荷数据
func payloadByteAt(index: Integer): Byte? { ... }
func payloadBytesInRange(range: Range<Integer>): ByteArray? { ... }
// 访问数据包结构
func getHeader(): ByteArray? { ... }
func getCommandType(): Byte? { ... }
func getLength(): Integer? { ... }
func getPayload(): ByteArray { ... }
func getChecksum(): Byte? { ... }
func getFooter(): ByteArray? { ... }
// 验证校验和
func verifyChecksum(): Boolean? { ... }
// 获取时间戳
func getTimestamp(): Long { ... }
}
数据包格式配置
enum PacketFormat {
/// 默认格式:命令类型 + 载荷
DEFAULT,
/// 带帧头格式
WITH_HEADER(headerBytes: ByteArray),
/// 带长度字段格式
WITH_LENGTH(headerBytes: ByteArray, lengthOffset: Integer?, lengthSize: Integer),
/// 完整格式:帧头 + 长度 + 命令 + 载荷 + 校验 + 帧尾
FULL(
headerBytes: ByteArray,
lengthOffset: Integer?,
lengthSize: Integer,
checksumOffset: Integer?,
footerBytes: ByteArray?
)
}
在蓝牙通信中使用
// 发送数据包(默认格式)
packet = DTDataPacket.forBluetoothSend(
commandType: 0x10,
payload: [50], // 音量值
format: PacketFormat.DEFAULT
)
viewModel.writeData(characteristicUUID, data: packet.toByteArray())
// 发送带帧头的数据包
headerPacket = DTDataPacket.forBluetoothSend(
commandType: 0x10,
payload: [50],
format: PacketFormat.WITH_HEADER([0xAA, 0xBB])
)
viewModel.writeData(characteristicUUID, data: headerPacket.toByteArray())
// 接收数据包
receivedPacket = DTDataPacket.fromBluetoothData(receivedData)
if (receivedPacket != null) {
// 根据命令类型处理
switch (receivedPacket.getCommandType()) {
case 0x20:
// 处理特定命令
status = receivedPacket.payloadByteAt(0)
if (status != null) {
print("状态: " + status)
}
default:
break
}
// 验证校验和
isValid = receivedPacket.verifyChecksum()
if (isValid != null) {
print("校验和验证: " + (isValid ? "通过" : "失败"))
}
}
数据包发送流程图:
需要发送数据
↓
创建数据包对象
↓
选择数据包格式
↓
┌─────────────────┐
│ 格式类型判断 │
└────────┬────────┘
│
┌────┴────┬──────────┬──────────┐
│ │ │ │
默认格式 带帧头格式 带长度格式 完整格式
│ │ │ │
↓ ↓ ↓ ↓
构建数据包 构建数据包 构建数据包 构建数据包
│ │ │ (含校验和)
└─────────┴──────────┴──────────┘
↓
转换为字节数组
↓
发送到蓝牙设备
数据包接收流程图:
接收到原始数据
↓
调用 fromBluetoothData()
↓
检查数据格式
↓
┌─────────────────┐
│ 格式识别 │
└────────┬────────┘
│
┌────┴────┬──────────┬──────────┐
│ │ │ │
默认格式 带帧头格式 带长度格式 完整格式
│ │ │ │
↓ ↓ ↓ ↓
解析数据包 解析数据包 解析数据包 解析数据包
│ │ │ (验证校验和)
│ │ │ │
│ │ │ ┌─────┴─────┐
│ │ │ │ │
│ │ │ 通过 失败
│ │ │ │ │
│ │ │ ↓ ↓
│ │ │ 继续处理 返回 null
│ │ │ │ │
└─────────┴──────────┴────┘ │
↓ │
返回数据包对象 │
↓ │
业务层处理 │
│ │
└───────────────────────────┘
九、数据格式转换工具设计
核心功能
DTDataFormatConverter 是一个专为物联网开发设计的数据格式转换工具库,提供了丰富的数据类型转换和进制运算功能。通过 Swift Extension 的方式,为常用数据类型添加了便捷的转换方法。
-
数据类型转换
- Data ↔ 十六进制字符串
- Data ↔ 字节数组([UInt8])
- Data ↔ 整数类型(UInt8/16/32/64, Int)
- Data ↔ 浮点数(Float, Double)
- String ↔ 十六进制字符串
- String ↔ Base64 编码
-
进制转换
- 支持二进制、八进制、十进制、十六进制之间的转换
- 支持 2-36 进制的任意转换
- 整数类型(UInt8/16/32/64, Int)支持进制转换
- 字符串支持跨进制转换
-
进制运算
- 算术运算:加法、减法、乘法、除法、取模
- 按位运算:按位与、按位或、按位异或、按位取反
- 移位运算:左移、右移
- 比较运算:大于、小于、等于
-
字节序处理
- 支持大端序(Big Endian)和小端序(Little Endian)
- 自动字节序转换
- 整数类型支持字节序转换
-
数据校验
- 简单累加校验和
- CRC16 校验(支持自定义多项式)
-
数据操作
- 数据填充和截取
- 字节序交换
- 数据子集提取
- Data 截取:11 种便捷的数据截取方法
- Array 截取:15 种数组元素截取方法,支持安全访问
Data 截取功能
extension Data {
// 从指定偏移量截取指定长度
func subData(offset: Integer, length: Integer): Data?
// 从指定位置截取到末尾
func subData(from: Integer): Data?
// 从开头截取到指定位置
func subData(to: Integer): Data?
// 使用 Range 截取
func subData(range: Range<Integer>): Data?
func subData(range: ClosedRange<Integer>): Data?
// 从末尾截取指定长度
func subDataFromEnd(length: Integer): Data?
// 跳过指定数量的字节
func subData(skip: Integer): Data?
// 截取前/后 N 个字节
func subData(prefix count: Integer): Data?
func subData(suffix count: Integer): Data?
// 移除前/后 N 个字节
func subData(removingPrefix count: Integer): Data?
func subData(removingSuffix count: Integer): Data?
}
Data 截取流程图:
调用 subData 方法
↓
检查参数有效性
↓
┌─────────────────┐
│ 参数验证 │
└────────┬────────┘
│
┌────┴────┐
│ │
有效 无效
│ │
↓ ↓
检查边界 返回 null
↓
┌─────────────────┐
│ 边界检查 │
└────────┬────────┘
│
┌────┴────┐
│ │
在范围内 超出范围
│ │
↓ ↓
执行截取 返回 null
↓
返回新 Data 对象
Array 截取功能
extension Array {
// 从指定偏移量截取指定长度
func subArray(offset: Integer, length: Integer): Array<Element>?
// 从指定位置截取到末尾
func subArray(from: Integer): Array<Element>?
// 从开头截取到指定位置
func subArray(to: Integer): Array<Element>?
// 使用 Range 截取
func subArray(range: Range<Integer>): Array<Element>?
func subArray(range: ClosedRange<Integer>): Array<Element>?
// 从末尾截取指定长度
func subArrayFromEnd(length: Integer): Array<Element>?
// 跳过指定数量的元素
func subArray(skip: Integer): Array<Element>?
// 截取前/后 N 个元素
func subArray(prefix count: Integer): Array<Element>?
func subArray(suffix count: Integer): Array<Element>?
// 移除前/后 N 个元素
func subArray(removingPrefix count: Integer): Array<Element>?
func subArray(removingSuffix count: Integer): Array<Element>?
// 数组切片(不复制)
func slice(range: Range<Integer>): ArraySlice<Element>?
func slice(range: ClosedRange<Integer>): ArraySlice<Element>?
// 安全访问
func safeGet(at index: Integer): Element?
func safeGet(range: Range<Integer>): Array<Element>?
func safeGet(range: ClosedRange<Integer>): Array<Element>?
}
Array 截取流程图:
调用 subArray 方法
↓
检查参数有效性
↓
┌─────────────────┐
│ 参数验证 │
└────────┬────────┘
│
┌────┴────┐
│ │
有效 无效
│ │
↓ ↓
检查边界 返回 null
↓
┌─────────────────┐
│ 边界检查 │
└────────┬────────┘
│
┌────┴────┐
│ │
在范围内 超出范围
│ │
↓ ↓
执行截取 返回 null
↓
创建新数组
↓
返回新数组对象
使用示例
更多详细的使用示例,请参考 DTDataFormatConverter+Examples.swift 文件,其中包含了 26 个完整的示例:
- Data 与十六进制字符串转换
- 整数类型转换
- 从 Data 中读取整数
- 浮点数转换
- 构建复合数据包
- 解析复合数据包
- 字符串与十六进制转换
- Base64 编码/解码
- 字节序转换
- 数据填充和截取(包含 Data 截取方法演示)
- CRC16 校验
- 数组与 Data 转换
- 字符串进制转换
- 整数类型进制转换
- 进制算术运算
- 进制按位运算
- 进制移位运算
- 进制比较运算
- 协议数据包构建
- 数据解析与验证
- 进制转换在配置参数中的应用
- 位掩码操作
- 数据校验和计算
- subData 在数据解析中的应用(新增)
- 数组元素截取操作(新增)
- 数组截取在数据处理中的应用(新增)
十、系统要求与平台适配
平台适配要求
DTBluetoothProvider 设计为跨平台库,需要各平台实现以下接口:
-
蓝牙适配器接口
- 检查蓝牙是否支持
- 检查蓝牙是否启用
- 启用蓝牙(需要用户授权)
-
权限管理接口
- 检查权限是否授予
- 请求权限
- 处理权限回调
-
本地存储接口
- 保存字符串数据
- 读取字符串数据
- 删除数据
-
异步执行接口
- 异步任务执行
- 延迟执行
- 超时控制
-
响应式编程接口
- 可观察对象(Observable)
- 数据流(Stream/Flow)
- 订阅和取消订阅
平台实现要求
| 平台 | 蓝牙 API | 异步框架 | 存储方案 | 响应式框架 |
|---|---|---|---|---|
| Android | BluetoothAdapter / BluetoothGatt | Coroutines | SharedPreferences / Room | Flow / LiveData |
| iOS | CoreBluetooth | DispatchQueue / Combine | UserDefaults / CoreData | Combine |
| HarmonyOS | @ohos.bluetoothManager | Promise / async/await | dataPreferences | Emitter |
| Flutter | flutter_blue | Future / async/await | SharedPreferences | Stream |
系统要求
各平台需要满足以下最低要求:
- 蓝牙硬件支持:设备必须支持蓝牙功能
- 权限支持:平台必须支持蓝牙权限管理
- 异步支持:平台必须支持异步编程模型
- 存储支持:平台必须提供本地持久化存储方案
📐 项目结构
DTBluetoothProvider/
├── Business/ # 业务层
│ └── BluetoothViewModel # 业务逻辑封装
│
├── Service/ # 服务层
│ └── BleServiceImpl # 服务层实现
│
├── Implementation/ # 实现层
│ ├── BluetoothProvider # 统一接口
│ ├── ClassicBluetoothProvider # 经典蓝牙实现
│ ├── BleProvider # BLE 实现
│ └── PlatformAdapter/ # 平台适配器
│ ├── Android/ # Android 平台实现
│ ├── iOS/ # iOS 平台实现
│ ├── HarmonyOS/ # HarmonyOS 平台实现
│ └── Flutter/ # Flutter 平台实现
│
└── Utility/ # 工具层
├── BleCommandBuffer # 指令缓冲工具
├── DTDeviceBindingCache # 设备绑定缓存
├── DTReconnectionStateMachine # 重连状态机
├── DTDataPacket # 数据包封装
└── DTDataFormatConverter # 数据格式转换工具
三、设计模式应用
| 设计模式 | 应用场景 | 优势 |
|---|---|---|
| 单例模式 | DTDeviceBindingCache.getInstance() | 全局唯一,数据一致性 |
| 策略模式 | ReconnectionStrategy | 灵活配置,易于扩展 |
| 状态机模式 | DTReconnectionStateMachine | 清晰的状态管理 |
| 观察者模式 | Observable / Stream / Flow | 解耦业务和 UI |
| 工厂模式 | BluetoothProviderFactory | 统一创建逻辑 |
| 适配器模式 | BluetoothProvider / PlatformAdapter | 屏蔽平台差异 |
四、线程安全设计
并发控制策略
-
异步执行框架
class BleServiceImpl { private let queue = DispatchQueue(label: "com.ble.service") func connect(_ device: BluetoothDevice) -> Bool { return queue.sync { // 连接操作 } } } -
并发集合
private var connectionContexts: [String: DeviceConnectionContext] = [:] private var commandQueues: [String: [BleCommand]] = [:] private let queue = DispatchQueue(label: "com.ble.service", attributes: .concurrent) // 使用 barrier 确保写操作线程安全 func updateContext(_ context: DeviceConnectionContext, for address: String) { queue.async(flags: .barrier) { self.connectionContexts[address] = context } } -
互斥锁
private let lock = NSLock() func updateState() { lock.lock() defer { lock.unlock() } // 写操作,确保线程安全 }
应用场景:
BleServiceImpl- 连接上下文管理BleCommandBuffer- 指令队列管理DTDeviceBindingCache- 绑定记录管理
五、错误处理机制
错误类型定义
enum DTBluetoothError: Error {
case deviceNotConnected
case deviceNotFound
case characteristicNotFound
case connectionTimeout
case connectionFailed(underlyingError: Error?)
case dataTransmissionFailed
case mtuExceeded(dataSize: Int, mtu: Int)
case bluetoothUnauthorized
case bluetoothPoweredOff
case unknownError(cause: Error)
}
错误处理策略
-
Result 类型返回
func trigger( event: TriggerEvent, device: BluetoothDevice ) -> Result<Void, DTBluetoothError> { // 实现逻辑 } -
异常传播
func connect(_ device: BluetoothDevice) -> Bool { do { return try provider.connect(device: device, type: .ble) } catch { handleError(error) return false } } -
响应式错误处理
var connectionState: Observable<ConnectionState> { didSet { connectionState.onError { error in self.connectionState.emit(.error(convertToBluetoothError(error))) } } }
🎯 设计亮点总结
1. 多设备管理能力
- ✅ 支持同时连接多个同类型设备(通过 channelNumb 区分)
- ✅ 支持同时连接多个不同类型设备(通过 Channel 区分)
- ✅ 每个设备独立的连接上下文和指令队列
2. 智能指令管理
- ✅ 串行执行,避免硬件冲突
- ✅ 智能去重,减少不必要的请求
- ✅ 灵活的参数更新策略
3. 持久化存储
- ✅ 设备绑定记录持久化(平台无关的存储接口)
- ✅ 应用重启后自动恢复
- ✅ 智能重连决策
4. 灵活的重连策略
- ✅ 多种重连策略可选
- ✅ 状态机管理重连流程
- ✅ 支持暂停和恢复
5. 完善的错误处理
- ✅ 详细的错误类型定义
- ✅ Result 类型返回
- ✅ 统一的错误转换机制
6. 线程安全设计
- ✅ 异步执行框架实现异步操作
- ✅ 并发集合保护共享资源
- ✅ 互斥锁确保写安全
7. 清晰的架构分层
- ✅ 业务层、服务层、实现层分离
- ✅ 工具层可复用
- ✅ 易于扩展和维护
8. 响应式编程
- ✅ 使用响应式编程框架实现数据流
- ✅ 支持多种观察者模式实现
- ✅ 自动处理生命周期
9. 经典蓝牙与 BLE 双模式支持
- ✅ 统一接口抽象,屏蔽底层差异
- ✅ 支持经典蓝牙(RFCOMM、L2CAP)
- ✅ 支持低功耗蓝牙(GATT)
- ✅ 根据设备类型自动选择合适的实现
- ✅ 平台无关的设计
10. 跨平台支持
- ✅ 统一的接口设计,适配不同平台
- ✅ 平台适配层隔离平台差异
- ✅ 核心逻辑与平台实现分离
📚 技术栈
核心设计
- 架构模式:分层架构、MVVM
- 设计模式:单例、策略、状态机、观察者、工厂、适配器
- 并发模型:异步编程、响应式编程
- 数据存储:本地持久化存储(平台无关接口)
平台适配
各平台需要实现以下接口:
- 蓝牙适配器接口:扫描、连接、读写等蓝牙操作
- 权限管理接口:权限检查、请求、回调
- 本地存储接口:数据持久化
- 异步执行接口:异步任务、延迟、超时
- 响应式编程接口:可观察对象、数据流
🔍 代码质量特点
- 注释完善:关键类和方法都有详细注释
- 命名规范:遵循平台命名规范
- 类型安全:充分利用类型系统
- 错误处理:完善的错误处理机制
- 线程安全:关键操作都有线程保护
- 可测试性:支持依赖注入,便于单元测试
- 平台无关:核心逻辑不依赖特定平台
💡 设计思想总结
DTBluetoothProvider 的设计体现了以下核心思想:
- 分层架构:清晰的职责分离,便于维护和扩展
- 多设备支持:通过 Channel 和 channelNumb 实现灵活的多设备管理
- 智能管理:指令缓冲、设备绑定、自动重连等智能化功能
- 线程安全:完善的并发控制,确保数据一致性
- 可扩展性:策略模式、工厂模式等设计模式支持灵活扩展
- 用户体验:自动重连、持久化存储等功能提升用户体验
- 响应式编程:使用响应式编程框架实现现代化的数据流
- 平台无关:核心设计不依赖特定平台,通过适配层实现跨平台
- 双模式支持:同时支持经典蓝牙和 BLE,使用统一接口抽象
这是一个企业级的蓝牙管理解决方案,设计思路清晰,代码质量高,具有很强的实用性和可维护性。通过统一的接口设计和平台适配层,可以在多个平台上实现一致的架构和功能。
🔄 跨平台实现对比
| 特性 | Android | iOS | HarmonyOS | Flutter |
|---|---|---|---|---|
| 异步处理 | Coroutines | DispatchQueue / Combine | Promise / async/await | Future / async/await |
| 观察者模式 | Flow / LiveData | Combine | Emitter | Stream |
| 持久化 | SharedPreferences / Room | UserDefaults / CoreData | dataPreferences | SharedPreferences |
| 设备标识 | MAC Address | UUID | MAC Address | UUID |
| 蓝牙框架 | BluetoothAdapter / BluetoothGatt | CoreBluetooth | @ohos.bluetoothManager | flutter_blue |
| 蓝牙类型 | 经典蓝牙 + BLE | BLE only | BLE | BLE |
| 单例实现 | object / getInstance() | static let shared | 单例模式 | 单例模式 |
| 状态管理 | Sealed Class | 枚举 + 结构体 | 枚举 | 枚举 |
⚠️ 注意事项
1. 权限配置
以iOS为例
确保在 Info.plist 中正确配置蓝牙权限:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>应用需要访问蓝牙以连接设备</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>应用需要访问蓝牙以连接设备</string>
2. 真机测试
蓝牙功能必须在真机上测试,iOS 模拟器不支持蓝牙。
3. 指令串行执行
指令缓冲工具确保指令串行执行,避免同时发送多个指令导致硬件处理冲突。
4. 指令去重
- 无参数指令会自动去重
- 有参数指令需要配置
shouldUpdateCommand回调来实现自定义去重逻辑
5. 设备断开处理
设备断开时,该设备的指令队列会自动清空,无需手动处理。
6. 线程安全
- 所有回调都在主线程执行,可以直接更新 UI
BleServiceImpl使用并发队列保护连接上下文,确保线程安全BleCommandBuffer使用串行队列管理指令队列DTDeviceBindingCache使用串行队列保护绑定记录DTReconnectionStateMachine使用串行队列管理状态机
❓ 常见问题
Q1: 如何同时连接多个设备?
A: 使用 BluetoothViewModel 的 initDevice1() 和 initDevice2() 方法,或多次调用 makeConnectionContext。
viewModel.initDevice1() // Channel: _zdeer_ai_0
viewModel.initDevice2() // Channel: _zdeer_ai_1
Q2: 指令为什么没有立即发送?
A: 指令缓冲工具会串行执行指令,需要等待前一个指令完成后再发送下一条。这是为了确保硬件能够正确处理指令。
指令串行执行流程图:
添加指令到队列
↓
检查队列状态
↓
┌─────────────────┐
│ 是否有正在执行的指令?│
└────────┬────────┘
│
┌────┴────┐
│ │
是 否
│ │
↓ ↓
等待执行 立即执行
│ │
│ ↓
│ 执行指令
│ │
│ ↓
│ 等待完成回调
│ │
│ ↓
│ 继续下一条
│ │
└─────────┘
↓
完成
Q3: 如何自定义指令更新策略?
A: 在 BluetoothViewModel.setupCommandUpdatePolicy() 中实现 shouldUpdateCommand 回调,根据业务需求决定是否更新队列中的指令。
commandBuffer.shouldUpdateCommand = function(existingCommand, newCommand) {
// 自定义比较逻辑
return shouldUpdate
}
Q4: 如何知道指令是否执行完成?
A: 通过 onLogMessage 回调可以查看指令执行日志,或者监听 readValue 和 writeValue 回调。
Q5: 设备断开后指令队列会怎样?
A: 设备断开时,该设备的指令队列会自动清空,不会继续执行。如果启用了持久化缓存,未完成的指令会保存到磁盘,设备重新连接后可以恢复。
设备断开处理流程图:
设备断开连接
↓
检测到断开事件
↓
┌─────────────────┐
│ 清空指令队列 │
└────────┬────────┘
↓
检查是否启用持久化
↓
┌─────────────────┐
│ 是否启用持久化? │
└────────┬────────┘
│
┌────┴────┐
│ │
是 否
│ │
↓ ↓
保存到磁盘 直接清空
│ │
│ │
└─────────┘
↓
完成
Q6: 如何获取指令队列状态?
A: 可以通过 commandBuffer.getQueueStatus(deviceUUID) 获取队列状态,返回 (pendingCount: Integer, isExecuting: Boolean),分别表示待执行指令数和是否正在执行指令。
Q7: 设备断开后会自动重连吗?
A: 如果设备有绑定记录且启用了自动重连,断开后会自动启动重连状态机。如果用户主动取消绑定(调用 disconnectAndUnbind),则不会重连。
自动重连决策流程图:
设备断开连接
↓
检查绑定记录
↓
┌─────────────────┐
│ 是否有绑定记录? │
└────────┬────────┘
│
┌────┴────┐
│ │
是 否
│ │
↓ ↓
检查自动 不重连
重连标志
│
┌───┴───┐
│ │
启用 未启用
│ │
↓ ↓
启动重连 不重连
状态机
Q8: 如何禁用某个设备的自动重连?
A: 使用 viewModel.setAutoReconnectEnabled(false, device) 或 bindingCache.setAutoReconnectEnabled(false, device.uuidString)。
Q9: 如何查看已绑定的设备?
A: 使用 viewModel.getBoundDevices() 或 DTDeviceBindingCache.getInstance().getAllBindings() 获取所有已绑定设备。
Q10: 如何自定义重连策略?
A: 创建 ReconnectionConfiguration 时指定 strategy 参数,支持立即、固定延迟、指数退避和自定义策略。
config = new ReconnectionConfiguration(
maxRetries: 5,
strategy: new ExponentialBackoffStrategy(2000, 60000),
connectionTimeout: 30000
)
Q11: 如何使用数据包封装?
A: 使用 DTDataPacket 创建和解析数据包。发送时使用 DTDataPacket.forBluetoothSend() 创建数据包,接收时使用 DTDataPacket.fromBluetoothData() 解析数据包。
Q12: 数据包支持哪些格式?
A: 支持默认格式、带帧头格式、带长度格式和完整格式(包含帧头、长度、命令、载荷、校验和、帧尾)。根据硬件协议文档选择合适的格式。
Q13: 指令缓冲工具支持持久化吗?
A: 是的,指令缓冲工具支持持久化缓存。启用后,指令队列会自动保存到磁盘,应用重启后可以通过 restoreCommands 方法恢复未完成的指令。
Q14: 如何根据连接质量调整重连策略?
A: 使用 DTReconnectionStateMachine 的 updateRSSI 和 adjustStrategyBasedOnRSSI 方法。状态机会根据信号强度自动调整重连延迟和重试次数。
Q15: 如何优化 BLE 连接参数?
A: 在 BleServiceImpl.Configuration 中设置 connectionParameters。可以使用预设的应用类型(REAL_TIME、BATCH_TRANSFER、LOW_POWER、BALANCED)自动优化,也可以自定义连接间隔、延迟和超时参数。
🚀 未来扩展方向
- 支持更多蓝牙协议:BLE Mesh、蓝牙音频(A2DP、HFP)等
- 性能优化:连接池管理、数据压缩、MTU 协商优化等
- 监控和日志:详细的性能监控和日志系统
- 单元测试:完善的单元测试覆盖
- 文档完善:API 文档和使用指南
- 更多平台支持:Web Bluetooth、Windows、macOS 等
📝 总结
本文档详细介绍了 DTBluetoothProvider 的概要设计,主要特点包括:
- 平台无关设计:通过接口抽象和适配层,实现跨平台支持
- 双模式支持:同时支持经典蓝牙和低功耗蓝牙(BLE)
- 统一接口抽象:通过接口设计屏蔽底层实现差异
- 企业级架构:清晰的分层设计,易于维护和扩展
- 完善的工具支持:指令缓冲、设备绑定、自动重连、数据包封装等
通过统一的架构设计和平台适配层,可以在多个平台上实现一致的蓝牙管理功能,是一个企业级的跨平台蓝牙管理解决方案。
本文档描述了 DTBluetoothProvider 的概要设计,核心逻辑与平台实现分离,通过适配层支持不同平台的蓝牙 API。各平台实现需要遵循本文档定义的接口和架构设计。