4. GATT与依赖注入的相遇场景:BLE设备驱动与业务逻辑解耦需求
在嵌入式BLE系统中,GATT协议栈通常由芯片厂商的SDK或操作系统层提供固定实现。传统做法是业务逻辑直接调用GATT API,导致驱动代码与业务场景高度耦合。本章聚焦于依赖注入(DI)介入GATT读写事件的典型场景——当BLE设备驱动需要与上层业务逻辑解耦时,DI成为控制反转的关键边界。
4.1 场景边界:谁依赖谁,谁控制谁
解耦需求的核心在于依赖关系的方向。业务逻辑不应该直接构造或持有GATT驱动实例,而应通过DI容器接收抽象的GattService接口。边界定义如下:
| 边界侧 | 所属层 | 典型组件 | 依赖方向 |
|---|---|---|---|
| GATT驱动 | 硬件抽象层(HAL) | GattCallback、NotificationHandler | 被DI容器管理 |
| 服务接口 | 抽象层 | IGattCharacteristic、IGattReadCallback | 无依赖 |
| 业务逻辑 | 应用层 | DeviceMonitor、DataProcessor | 消费接口,不依赖具体实现 |
| DI容器 | 基础设施层 | DaggerComponent、ManualInjector | 装配依赖,控制实例生命周期 |
4.2 分层与组件职责
为了实现上述边界,系统被划分为四个清晰层级,每一层只暴露并依赖接口,不引用具体类。
- GATT协议适配层:封装系统级BLE API,注册回调,完成MTU协商、通知启停、读写操作。本层不处理业务数据含义。
- 服务抽象层:定义
IGattCharacteristic、INotificationReceiver等接口。接口参数与返回值均为纯业务结构体,不暴露GATT句柄或UUID。 - 业务逻辑层:通过构造函数接收接口实例,消费特征值变化、发起读写请求。不再调用任何系统BLE API。
- DI装配层:负责将GATT驱动实现注入到业务层。注入时机通常发生在设备连接成功后、业务启动前。
设计提示:服务抽象层是整个解耦方案的锚点。接口中不应出现
BluetoothGattCharacteristic、BluetoothGattDescriptor 等平台类型,而应使用自定义数据类型或通用字节流。
4.3 数据流、控制流与信任链路
下图展示了从BLE硬件事件到达后,经过DI边界到达业务逻辑的完整链路:
数据流可以概括为三个步骤:
- GATT事件产生:BLE硬件发出通知或读响应,GATT驱动层收到原始数据。
- 接口回调:驱动层将数据包装为业务友好的格式,回调
IGattService方法。 - 业务消费:业务逻辑层通过DI得到的接口实例接收数据,完成处理、存储或UI更新。
控制流的关键在于DI容器介入时间——在设备连接建立后、业务层启动前完成注入,避免驱动实例在业务层生命周期内被意外替换。
4.4 选型权衡:手动注入 vs 框架注入
在嵌入式BLE场景下,资源受限,DI选型是一个重要权衡点。下表对比两种常见方案:
| 维度 | 手动依赖注入 | 框架注入(Dagger / Hilt) |
|---|---|---|
| 启动开销 | 近乎为零 | 反射/APT编译期生成,运行时少量开销 |
| 代码量 | 需显式编写工厂类 | 注解驱动,自动生成 |
| 调试难度 | 低,调用链可见 | 中等,需要理解作用域和组件树 |
| 生命周期管理 | 需手动绑定连接和断开 | 通过 @ActivityScoped 或 @ServiceScoped 自动管理 |
| 推荐场景 | RAM < 256KB、无Java虚拟机 | Android类系统、支持KSP/APT |
风险警示:在低端MCU上使用框架注入可能导致Flash占用大幅增加(约40-80KB),且初始化时间不可预测。建议资源紧缺的嵌入式环境优先选择手动注入。
4.5 风险点与应对措施
- 生命周期不匹配:BLE连接断开后,DI容器仍然持有驱动实例,导致悬挂引用。应在
onDisconnected中清理或重置作用域。 - 线程安全问题:GATT回调可能在不同线程执行,DI注入的实例需要保证线程安全或使用不可变对象。
- 接口膨胀:每个服务都需要单独接口,导致接口数量激增。采用泛型接口(如
IGattCharacteristic<T>)可缓解。
4.6 落地步骤与验收标准
| 步骤 | 操作 | 预期产物 |
|---|---|---|
| Step 1 | 定义业务所需的GATT服务接口集 | IGattReadService、IGattNotificationService |
| Step 2 | 实现驱动适配层,适配系统BLE回调 | GattAccessor 类 |
| Step 3 | 搭建DI容器(手动工厂或框架组件) | GattComponentFactory |
| Step 4 | 在设备连接后触发注入 | injectGattDependencies(connectionId) |
| Step 5 | 编写测试,模拟接口替代真实驱动 | Mock对象+DI容器测试 |
验收标准:
- 业务层代码不引用任何
BluetoothGatt、GattCallback或BluetoothDevice。 - 断开连接后,DI容器不持有已断开的驱动实例引用。
- 更换GATT驱动实现时(如从系统BLE切换为自定义HCI层),无需修改业务逻辑层任何代码。
- 单元测试中可传递任何符合接口定义的模拟对象,无运行时异常。