4. 特征读取实现与数据解析:onCharacteristicRead 回调、byte[] 转类型、错误码判断

4.1 原理概述

GATT 特征读取是蓝牙 GATT 客户端主动获取服务器端特征值的过程。当客户端调用 readCharacteristic(characteristic) 后,BLE 协议栈会发起属性协议 (ATT) 的 Read Request,服务器响应并返回特征值数据。读取结果通过 onCharacteristicRead() 回调返回,包含原始字节数组和状态码。

数据解析的核心是将 byte[] 按特征值定义的数据类型(如 uint8、uint16、float、字符串、数组等)转换为应用层可用的 Java/Kotlin 类型。错误码判断用于识别读取是否成功(如 GATT_SUCCESS)或通信异常(如 GATT_READ_NOT_PERMITTED、GATT_INSUFFICIENT_AUTHENTICATION 等)。

4.2 实现流程

特征读取全流程(简化步骤)
  1. 发起读取请求:BluetoothGatt 实例调用 readCharacteristic(BluetoothGattCharacteristic)。
  2. 等待回调:系统将请求排入 BLE 协议栈,服务器响应后调用客户端注册的 BluetoothGattCallback.onCharacteristicRead()。
  3. 解析 byte[]:根据特征声明格式,将 byte[] 转换为目标类型(如 int、String、byte[] 原样等)。
  4. 判断错误码:检查 status 参数,若不为 GATT_SUCCESS 则处理错误。
  5. 更新本地缓存/UI:将解析结果保存到内存或本地数据层,并通知 UI 刷新。

4.3 BLE 特征读取逻辑流程(内联 SVG 图)

特征读取与数据解析流程图 发起读取请求 BLE 协议栈执行 onCharacteristicRead() 回调 status? 错误码处理 记录日志/重试/断连 解析 byte[] → 目标类型 更新本地缓存/UI 说明 解析依赖特征声明格式 (如小端字节序、UTF-8 编码等)

4.4 代码实现:Android (Kotlin)

以下示例展示在 BluetoothGattCallback 中处理特征读取并解析数据。

override fun onCharacteristicRead(
    gatt: BluetoothGatt,
    characteristic: BluetoothGattCharacteristic,
    status: Int
) {
    if (status == BluetoothGatt.GATT_SUCCESS) {
        val value: ByteArray = characteristic.value ?: return

        // 根据特征 UUID 或配置决定解析方式
        when (characteristic.uuid) {
            UUID_HEART_RATE_MEASUREMENT -> {
                // 格式: uint8 HR 值,直接取第一个字节
                val heartRate = value[0].toInt() and 0xFF
                updateHeartRateUI(heartRate)
            }
            UUID_DEVICE_NAME -> {
                // 字符串 (UTF-8)
                val deviceName = String(value, Charsets.UTF_8)
                updateDeviceNameUI(deviceName)
            }
            UUID_BATTERY_LEVEL -> {
                // 无符号 uint8
                val battery = value[0].toInt() and 0xFF
                updateBatteryUI(battery)
            }
            UUID_TEMPERATURE -> {
                // 小端 int16 再转 float (例如 0.1°C 单位)
                if (value.size >= 2) {
                    val raw = (value[1].toInt() shl 8) or (value[0].toInt() and 0xFF)
                    val temperature = raw.toFloat() / 10.0f
                    updateTemperatureUI(temperature)
                }
            }
            else -> {
                // 默认将 byte[] 按十六进制显示
                val hexStr = value.joinToString("") { "%02X".format(it) }
                log("Raw data: $hexStr")
            }
        }
    } else {
        handleReadError(status, characteristic.uuid)
    }
}
提示:Android 8.0+ 中 characteristic.value 在回调内直接可用,无需额外读取;但若在回调外使用需注意线程安全。

4.5 常见数据类型的 byte[] 转换方法

目标类型转换方法(Kotlin)
uint8 (无符号8位)value[0].toInt() and 0xFF
uint16 (小端)(value[1].toInt() shl 8) or (value[0].toInt() and 0xFF)
int32 (小端)ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN).int
float (IEEE 754 小端)ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN).float
UTF-8 字符串String(value, Charsets.UTF_8)
十六进制字符串value.joinToString("") { "%02X".format(it) }
注意:特征声明可能采用大端序。务必阅读 BLE 设备的数据规范文档,或从 GATT 服务的 Descriptor(如 Characteristic Presentation Format)推断字节序和对齐方式。

4.6 错误码判断

Android BluetoothGatt 中 status 的常用常量及含义:

错误码(int)常量名含义
0GATT_SUCCESS操作成功
2GATT_READ_NOT_PERMITTED特征不具可读权限
5GATT_INSUFFICIENT_AUTHENTICATION需要配对/认证
8GATT_INSUFFICIENT_ENCRYPTION需要加密链路
15GATT_CONNECTION_TIMEOUT连接超时
133GATT_ERROR通用内部错误

建议对非 GATT_SUCCESS 状态进行分级处理:

  • 临时错误(如超时、忙碌):延迟重试,最多 3 次。
  • 权限错误(如未认证):发起配对流程或提示用户。
  • 致命错误(如设备断开):关闭 GATT 连接并重新扫描。

4.7 调试与日志输出

调试时建议打印完整的 byte[] 内容及状态:

fun logReadResult(uuid: UUID, value: ByteArray?, status: Int) {
    val hex = value?.joinToString("") { "%02X".format(it) } ?: "null"
    Log.d("BLE_GATT", "Read [$uuid] status=$status value=$hex")
}

可通过 nRF Connect 或 LightBlue 工具对比验证原始数据是否正确。

4.8 性能与稳定性优化

  • 批量读取:如果同一 service 下有多个特征需要读取,使用 onServicesDiscovered() 后依次调用 readCharacteristic(),避免并发读取。
  • 缓存策略:若特征值变化频率低(如设备名称、序列号),在本地缓存一次读取结果,后续不再重复读取。
  • 线程安全:回调在 Binder 线程,解析后的数据需通过 Handler 或 LiveData 切换到 UI 线程更新。
  • 超时处理:为每次读取请求设置 5 秒超时定时器,若未收到回调则触发重试。

4.9 常见错误与解决方案

问题现象可能原因解决方案
读取返回 byte[] 全为零设备未准备好、特征未被通知使能、读取时机过早等待设备就绪信号或先写配置描述符使能通知
转换后数值异常字节序错误或解析类型与声明不符核对特征数据格式文档,使用 ByteBuffer 显式指定字节序
始终收到 GATT_READ_NOT_PERMITTED特征属性的“读”位未置位检查设备固件或更换特征;若无读取权限可改用通知接收
回调从未触发未正确注册 BluetoothGattCallback 或 GATT 实例被释放确保 gatt 对象有效且 callback 非匿名内部类(防止 GC)

4.10 部署注意事项

  • 生产代码中不要直接使用 value.size 做假设,应检查长度后再解析,防止数组越界。
  • 对于敏感数据(如健康测量值),建议在解析后做范围校验(0~255 等),防止 UI 显示异常值。
  • 若特征值超过 MTU 限制导致分段读取(目前 BLE 4.2+ 支持长读取),需处理多次回调并拼接数据。