1.1 蓝牙服务架构概览

传统蓝牙 BLE(Bluetooth Low Energy)应用的实现,通常将设备扫描、连接管理、服务发现与数据交互三大核心逻辑全部堆砌在一个页面或一个 Service 中。这种“大而全”的单块架构在快速原型验证时可行,但在工程化落地时,会暴露以下痛点:

  • 耦合度高:扫描逻辑与连接握手代码交织,修改扫描策略可能影响连接的建立流程。
  • 难以测试:无法单独 Mock 扫描器或连接器来执行单元测试。
  • 复用困难:不同业务场景(如扫描仪、信标、数据采集设备)需要重复实现相同的连接管理代码。
  • 生命周期混乱:Android/iOS 等平台下,扫描回调、连接回调与 Activity/Fragment 生命周期难以有序管理。

本章将首先解构 BLE 通信的全链路,划分出清晰的模块边界,并阐述引入依赖注入(DI)来解耦模块的设计动机。

1.1.1 整体模块划分

将 BLE 通信链路按职责拆分为四个独立模块,每个模块只关注一个领域:

层次 模块名称 核心职责 对外接口粒度
应用层 BleClient 聚合扫描、连接、服务操作,暴露给 UI 层最简 API startScan / connect / discoverServices / writeCharacteristic
核心层 ScannerModule 蓝牙扫描管理、过滤策略、扫描回调分发 startScan(ScanFilter) / stopScan / onScanResult
核心层 ConnectorModule 连接建立、连接状态机、自动重连、连接参数协商 connect(device) / disconnect / onConnectionStateChange
核心层 ServiceExplorer 服务/特征发现、特征读写订阅、MTU 协商 discoverServices / readCharacteristic / setNotify
平台适配层 BleAdapter 封装平台蓝牙 API(Android BltAdapter、iOS CBCentralManager)差异 startLeScan / stopLeScan / createBond 等系统调用

模块间只通过接口(Interface)交互,不依赖具体实现类。这是后续注入与替换的基础。

1.1.2 分离动机

动机一:关注点分离
ScannerModule 只关心如何找到设备,不关心连接后的数据业务;ConnectorModule 只管理连接状态,不关心扫描过滤策略。逻辑解耦后,每个模块可以独立演进。

动机二:可测试性
可以分别为 ScannerModule 和 ConnectorModule 编写 Mock 测试。单元测试只需注入 Mock 的 BleAdapter,不依赖真实蓝牙硬件。

动机三:可扩展性
当需要支持不同的连接策略(如优先连接、延迟连接、批量连接)时,只需替换 ConnectorModule 的实现,不影响扫描和服务访问代码。

1.1.3 依赖注入设计

依赖注入(DI)的核心思想是:一个对象不应自己创建它依赖的对象,而是由外部容器在创建时将依赖传递给它。对于 BLE 模块,DI 解决了以下具体问题:

  • BleAdapter 的共享:整个应用只应有一个 BluetoothAdapter 实例(Android)或 CBCentralManager(iOS),DI 容器保证单例。
  • 模块组装:BleClient 依赖 ScannerModule、ConnectorModule、ServiceExplorer,由容器负责将这些实例注入给 BleClient。
  • 生命周期管理:DI 容器可以绑定模块生命周期到应用或页面,避免内存泄漏。

下面是一个典型的注入关系示意图:

依赖注入架构概览(BLE 模块分离) DI 容器 (Dependency Injection Container) BleClient (聚合 API) ScannerModule ConnectorModule ServiceExplorer 注入 IScanner 注入 IConnector 注入 IServiceExplorer BleAdapter (Android) CBCentralManager (iOS) 平台抽象层 蓝牙硬件 / BLE Controller

1.1.4 注入示例:接口定义与构造注入

在 Android/Kotlin 项目中,模块分离与注入通常通过接口和构造器参数实现。下面展示 ScannerModule 的最小接口定义及注入方式:

// IScanner.kt — 扫描模块接口
interface IScanner {
    fun startScan(filter: ScanFilter, callback: ScanCallback)
    fun stopScan()
}

// ScannerImpl.kt — 实现类
class ScannerImpl(
    private val adapter: BleAdapter  // 由 DI 容器注入
) : IScanner {
    override fun startScan(filter: ScanFilter, callback: ScanCallback) {
        adapter.startLeScan(filter.uuid, callback)
    }
    override fun stopScan() {
        adapter.stopLeScan()
    }
}

// BleClient.kt — 聚合入口,接收注入的模块
class BleClient(
    private val scanner: IScanner,
    private val connector: IConnector,
    private val serviceExplorer: IServiceExplorer
) {
    fun startSearching() {
        scanner.startScan(ScanFilter(), object : ScanCallback {
            override fun onDeviceFound(device: BleDevice) {
                connector.connect(device)
            }
        })
    }
}

实践提示:使用 Dagger / Hilt (Android) 或 Koin 时,可以将 BleAdapter 标记为 @Singleton,ScannerImpl 和 ConnectorImpl 通过 @Inject constructor 自动获取依赖。iOS 端则可通过 Swinject 或手动注入实现同样的分层。

1.1.5 常见错误与调试方向

  • 循环依赖:ScannerModule 引用了 ConnectorModule,而 ConnectorModule 又回引了 ScannerModule。解决:模块应设计为单向依赖,聚合器(BleClient)为唯一组合点。
  • 重复创建 Adapter:每次注入都 new 一个 BluetoothAdapter,导致扫描/连接状态不一致。必须确保 DI 容器对平台适配层采用单例作用域。
  • 接口过度设计:刚开始就抽象出 IConnect、IScan、IService 等 10+ 接口,增加维护成本。优先按“扫描、连接、服务”三大块抽取接口即可。

性能基线:依赖注入容器本身会带来微秒级的启动开销,但在 BLE 这种毫秒级响应的场景下,注入开销可忽略不计。主要性能瓶颈仍在蓝牙硬件的扫描间隔和连接参数上,模块分离不会引入额外的效率损失。