Android BLE 开发实战:连接心率带并解析 3 种数据格式
Android BLE 开发实战:连接心率带并解析 3 种数据格式
在健康监测和运动追踪领域,低功耗蓝牙(BLE)技术已成为连接智能穿戴设备的主流方案。本文将深入探讨如何通过Android平台与心率带建立稳定连接,并完整解析心率监测器传输的三种标准数据格式(8位、16位和24位)。不同于传统蓝牙开发,BLE协议栈的交互需要开发者更精细地控制连接生命周期和数据解析逻辑。
1. 环境准备与权限配置
开发BLE应用前,需在AndroidManifest.xml中声明必要权限。从Android 12开始,蓝牙权限体系进行了重大调整:
<!-- Android 12+ 新权限体系 --> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <!-- 兼容旧版本的权限声明 --> <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />关键注意事项:
neverForLocation标志声明应用不会通过蓝牙扫描获取物理位置- Android 10+需要处理后台位置权限的特殊情况
- 动态权限请求需包含
BLUETOOTH_CONNECT和BLUETOOTH_SCAN
运行时权限检查逻辑示例:
fun checkPermissions(): Boolean { val requiredPermissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { arrayOf( Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT ) } else { arrayOf(Manifest.permission.ACCESS_FINE_LOCATION) } return requiredPermissions.all { ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED } }2. BLE设备扫描与连接
2.1 优化扫描策略
心率带通常采用间歇性广播模式,建议使用以下扫描配置:
val scanner = bluetoothAdapter.bluetoothLeScanner val settings = ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) .build() val filters = listOf( ScanFilter.Builder() .setServiceUuid(ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB")) // 心率服务UUID .build() ) scanner.startScan(filters, settings, scanCallback)扫描性能优化要点:
- 使用
SCAN_MODE_LOW_LATENCY时功耗较高,建议限制单次扫描时长 - 合理设置
ScanFilter可降低CPU占用率 - Android 8.0+建议使用
MATCH_MODE_AGGRESSIVE提高设备发现率
2.2 建立GATT连接
发现目标设备后,通过BluetoothDevice.connectGatt()建立连接:
val gatt = device.connectGatt( context, false, // 不自动重连 gattCallback, BluetoothDevice.TRANSPORT_LE )连接参数说明:
| 参数 | 类型 | 推荐值 | 作用 |
|---|---|---|---|
| autoConnect | Boolean | false | 设为true会导致连接延迟增加 |
| transport | Int | TRANSPORT_LE | 明确指定使用BLE传输 |
| phy | Int | PHY_LE_1M | Android 8.0+可指定物理层 |
注意:连接操作应在非UI线程执行,避免阻塞主线程导致ANR
3. 心率服务发现与数据解析
3.1 服务发现流程
成功连接后,通过BluetoothGattCallback接收服务发现结果:
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { if (status == BluetoothGatt.GATT_SUCCESS) { val service = gatt.getService(UUID.fromString("0000180D-0000-1000-8000-00805F9B34FB")) val characteristic = service?.getCharacteristic(UUID.fromString("00002A37-0000-1000-8000-00805F9B34FB")) characteristic?.let { enableNotification(gatt, it) } } }心率服务关键组件:
- Service UUID: 0000180D (Heart Rate)
- Characteristic UUID: 00002A37 (Heart Rate Measurement)
- Descriptor UUID: 00002902 (Client Characteristic Configuration)
3.2 启用数据通知
正确配置通知描述符是接收数据的前提:
fun enableNotification(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { gatt.setCharacteristicNotification(characteristic, true) val descriptor = characteristic.getDescriptor( UUID.fromString("00002902-0000-1000-8000-00805F9B34FB") ) descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE gatt.writeDescriptor(descriptor) }常见问题排查:
- 确保先调用
setCharacteristicNotification再写描述符 - 部分设备需要先读取特征值权限
- 写入描述符后应检查
onDescriptorWrite回调状态
4. 心率数据格式解析
根据BLE规范,心率测量值可能采用三种格式:
4.1 8位无符号整数格式
数据结构:
0x00: [Flags] 0x01: [Heart Rate Value (UINT8)]解析示例:
fun parseHeartRate8Bit(value: ByteArray): Int { if (value.size < 2) return -1 return value[1].toInt() and 0xFF }4.2 16位无符号整数格式
数据结构:
0x00: [Flags] 0x01-0x02: [Heart Rate Value (UINT16)]解析示例:
fun parseHeartRate16Bit(value: ByteArray): Int { if (value.size < 3) return -1 return ((value[2].toInt() and 0xFF) shl 8) or (value[1].toInt() and 0xFF) }4.3 24位RR间隔格式
复杂数据结构:
0x00: [Flags] 0x01: [Heart Rate Value] 0x02+: [RR-Interval] (每2字节一个间隔值)完整解析实现:
data class HeartRateData( val value: Int, val rrIntervals: List<Int>, val contactDetected: Boolean, val energyExpended: Int? ) fun parseHeartRate(value: ByteArray): HeartRateData { val flags = value[0].toInt() and 0xFF val format16Bit = (flags and 0x01) != 0 val contactDetected = (flags and 0x06) != 0 val hasEnergy = (flags and 0x08) != 0 val hasRR = (flags and 0x10) != 0 var offset = 1 val hrValue = if (format16Bit) { val v = ((value[offset+1].toInt() and 0xFF) shl 8) or (value[offset].toInt() and 0xFF) offset += 2 v } else { val v = value[offset].toInt() and 0xFF offset += 1 v } val energy = if (hasEnergy) { val e = ((value[offset+1].toInt() and 0xFF) shl 8) or (value[offset].toInt() and 0xFF) offset += 2 e } else null val rrIntervals = mutableListOf<Int>() if (hasRR) { while (offset + 1 < value.size) { val rr = ((value[offset+1].toInt() and 0xFF) shl 8) or (value[offset].toInt() and 0xFF) rrIntervals.add(rr) offset += 2 } } return HeartRateData(hrValue, rrIntervals, contactDetected, energy) }5. 连接管理与优化
5.1 连接参数协商
Android 8.0+支持通过BluetoothGatt.requestConnectionPriority()调整连接参数:
gatt.requestConnectionPriority( BluetoothGatt.CONNECTION_PRIORITY_HIGH )参数对比:
| 优先级 | 连接间隔 | 延迟 | 超时 |
|---|---|---|---|
| BALANCED | 30-50ms | 0ms | 20s |
| HIGH | 11.25-15ms | 0ms | 20s |
| LOW_POWER | 100-125ms | 2ms | 20s |
5.2 错误处理与重连
健壮的BLE连接需要处理以下异常场景:
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { when (newState) { BluetoothProfile.STATE_CONNECTED -> { gatt.discoverServices() } BluetoothProfile.STATE_DISCONNECTED -> { when (status) { BluetoothGatt.GATT_CONNECTION_CONGESTED -> { // 带宽不足,降低数据频率 } BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION -> { // 需要重新配对 } else -> { // 指数退避重连 scheduleReconnect() } } } } }5.3 资源释放
正确的连接终止流程:
fun disconnect() { gatt?.let { it.setCharacteristicNotification(characteristic, false) it.disconnect() Handler(Looper.getMainLooper()).postDelayed({ it.close() }, 300) // 确保断开完成 } }6. 实战:完整心率监测实现
结合上述技术点,实现完整的心率监测流程:
class HeartRateMonitor( private val context: Context, private val callback: (HeartRateData) -> Unit ) : BluetoothGattCallback() { private var gatt: BluetoothGatt? = null private val handler = Handler(Looper.getMainLooper()) fun startScan() { val scanner = BluetoothAdapter.getDefaultAdapter().bluetoothLeScanner val settings = ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) .build() scanner.startScan(null, settings, scanCallback) } private val scanCallback = object : ScanCallback() { override fun onScanResult(callbackType: Int, result: ScanResult) { result.device?.let { device -> if (device.name?.contains("HRM") == true) { gatt = device.connectGatt(context, false, this@HeartRateMonitor) } } } } override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { when (newState) { BluetoothProfile.STATE_CONNECTED -> { gatt.discoverServices() handler.postDelayed({ gatt.requestConnectionPriority( BluetoothGatt.CONNECTION_PRIORITY_HIGH ) }, 1000) } BluetoothProfile.STATE_DISCONNECTED -> { handler.postDelayed({ gatt.connect() }, 2000) } } } override fun onCharacteristicChanged( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic ) { if (characteristic.uuid == UUID.fromString("00002A37-0000-1000-8000-00805F9B34FB")) { callback(parseHeartRate(characteristic.value)) } } // 包含之前定义的parseHeartRate方法... }性能优化技巧:
- 使用
HandlerThread替代主线程Handler - 批量处理RR间隔数据降低UI更新频率
- 实现数据缓存避免丢失关键心率变化点
- 根据设备距离动态调整扫描间隔
