Android BLE 开发实战:连接心率带并解析 3 种数据格式

📅 2026/7/9 8:34:17
Android BLE 开发实战:连接心率带并解析 3 种数据格式
Android BLE 开发实战连接心率带并解析 3 种数据格式在健康监测和运动追踪领域低功耗蓝牙BLE技术已成为连接智能穿戴设备的主流方案。本文将深入探讨如何通过Android平台与心率带建立稳定连接并完整解析心率监测器传输的三种标准数据格式8位、16位和24位。不同于传统蓝牙开发BLE协议栈的交互需要开发者更精细地控制连接生命周期和数据解析逻辑。1. 环境准备与权限配置开发BLE应用前需在AndroidManifest.xml中声明必要权限。从Android 12开始蓝牙权限体系进行了重大调整!-- Android 12 新权限体系 -- uses-permission android:nameandroid.permission.BLUETOOTH_SCAN android:usesPermissionFlagsneverForLocation / uses-permission android:nameandroid.permission.BLUETOOTH_CONNECT / !-- 兼容旧版本的权限声明 -- uses-permission android:nameandroid.permission.BLUETOOTH android:maxSdkVersion30 / uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION android:maxSdkVersion30 /关键注意事项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 )连接参数说明参数类型推荐值作用autoConnectBooleanfalse设为true会导致连接延迟增加transportIntTRANSPORT_LE明确指定使用BLE传输phyIntPHY_LE_1MAndroid 8.0可指定物理层注意连接操作应在非UI线程执行避免阻塞主线程导致ANR3. 心率服务发现与数据解析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: ListInt, 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[offset1].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[offset1].toInt() and 0xFF) shl 8) or (value[offset].toInt() and 0xFF) offset 2 e } else null val rrIntervals mutableListOfInt() if (hasRR) { while (offset 1 value.size) { val rr ((value[offset1].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 )参数对比优先级连接间隔延迟超时BALANCED30-50ms0ms20sHIGH11.25-15ms0ms20sLOW_POWER100-125ms2ms20s5.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, thisHeartRateMonitor) } } } } 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更新频率实现数据缓存避免丢失关键心率变化点根据设备距离动态调整扫描间隔