1.1 BLE GATT 回调机制:原理与核心要素
BLE GATT(通用属性协议)回调是 Android 蓝牙框架中 主机(Central/GATT Client) 与 外设(Peripheral/GATT Server) 异步通信的关键桥梁。当应用发起属性读写、通知使能、MTU 更新等操作时,系统不会立即返回结果,而是通过注册的 BluetoothGattCallback 代理对象,在特定事件触发时回调对应方法。
理解回调的四种核心维度——类型、线程、数据格式、陷阱,是后续构建可靠 Room 存储管道的基础。
1.1.1 回调类型
| 回调方法 | 触发条件 | 典型用途 |
|---|---|---|
onConnectionStateChange |
连接建立 / 断开 / 重连 | 管理连接状态机 |
onServicesDiscovered |
服务发现完成 | 获取 Service / Characteristic 句柄 |
onCharacteristicRead |
读取特征值成功 | 获取传感器数据、设备信息 |
onCharacteristicWrite |
写入特征值完毕 | 确认配置指令已送达 |
onCharacteristicChanged |
外设主动推送通知 / 指示 | 实时数据流(心率、温度、姿态) |
onDescriptorWrite |
配置 CCCD 完成 | 使能 / 禁用通知通道 |
onMtuChanged |
MTU 协商结束 | 调整最大单包传输长度 |
1.1.2 回调线程
所有 GATT 回调默认在 系统 Binder 线程池 中执行,并非主线程(UI 线程)。这意味着:
- 回调内部不可以直接更新 UI / 访问 View;
- 不可以执行长时间阻塞操作(如文件 I/O、网络请求);
- 必须通过 Handler / LiveData / Coroutine 切换到指定线程再处理数据。
线程切换示例(Handler 方案)
private val mainHandler = Handler(Looper.getMainLooper())
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic
) {
val data = characteristic.value
mainHandler.post {
// 在此处安全更新 UI 或发射 LiveData
viewModel.onDataReceived(data)
}
}
1.1.3 回调数据格式
特征值数据通过 characteristic.value 属性返回,类型为 ByteArray。常见数据格式与解析场景:
| 数据场景 | 长度 | 解析方法 | 示例(原始 Hex) |
|---|---|---|---|
| 心率测量 | 2~8 字节 | 按 BLE 心率规范解析 Flags + HR Value | 16 54 → 84 bpm |
| 温度传感器 | 4 字节 | Little-Endian Int32 / Float32 | E1 7A 48 42 → 50.12°C |
| 自定义指令 | 1~512 字节 | 按协议头+负载解析 | AA 01 02 03 55 → 命令=0x01 |
注意:characteristic.value 在 API 33+ 可能为 null,应使用 characteristic.getValue() 替代。
1.1.4 常见回调陷阱
- 陷阱 1:多次回调同一特征值
连续两次调用readCharacteristic可能导致第二次回调丢失或数据错乱。应使用排队机制确保同一特征值串行读取。 - 陷阱 2:回调未释放导致内存泄漏
在 Activity/Fragment 销毁后未调用gatt.close(),回调中持有 View 引用引发泄漏。应在onDestroy中主动清理。 - 陷阱 3:多特征通知乱序
多个特征同时使能通知时,onCharacteristicChanged可能交叉触发。需在回调入口通过characteristic.uuid做分发路由。 - 陷阱 4:线程安全
回调在 Binder 线程执行,如果直接写入 Room DAO 而不做线程切换,会触发IllegalStateException: Cannot access database on main thread或死锁。
特别注意:Android 12+ 对后台 BLE 扫描和连接有严格限制。若 App 在后台收到回调后立即写入 Room,可能因进程优先级被降级导致写入失败。建议使用 WorkManager 延迟处理离线数据。
1.1.5 回调全流程关系图
下图展示了典型 BLE GATT 回调的完整数据流向,从外设事件到应用层数据解析,以及最终进入 Room 存储的必经路径。
1.1.6 实践要点:回调与 Room 的第一层对接
在回调函数中接收数据后,应立刻完成以下两件事:
- 数据校验:检查
status是否为GATT_SUCCESS,特征值非空且长度合理; - 线程切换:将解析后的数据对象传递给 ViewModel,由 ViewModel 在 IO 协程中调用 Room DAO 写入。
以下是一个最小可用实现框架(省略了 Service/Characteristic 查找):
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic
) {
if (characteristic.uuid == HEART_RATE_MEASUREMENT_UUID) {
val hrData = parseHeartRate(characteristic.value)
viewModelScope.launch(Dispatchers.IO) {
heartRateDao.insert(hrData)
}
}
}