当前位置: 首页 > news >正文

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_CONNECTBLUETOOTH_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线程执行,避免阻塞主线程导致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) }

常见问题排查:

  1. 确保先调用setCharacteristicNotification再写描述符
  2. 部分设备需要先读取特征值权限
  3. 写入描述符后应检查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 )

参数对比:

优先级连接间隔延迟超时
BALANCED30-50ms0ms20s
HIGH11.25-15ms0ms20s
LOW_POWER100-125ms2ms20s

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更新频率
  • 实现数据缓存避免丢失关键心率变化点
  • 根据设备距离动态调整扫描间隔
http://www.jsqmd.com/news/1153221/

相关文章:

  • 计算机毕业设计之临沂市供电公司工程项目决策管理系统
  • 都在用的 AI 建站神器 MotionSites.ai — 信息差揭秘
  • 暗黑破坏神2存档编辑器:轻松修改游戏数据的完整指南
  • AD7175-8与MKV46F128VLH16的高精度数据采集方案
  • Codex科研AI助手实战:从环境部署到全流程加速论文写作与数据分析
  • 基于 YOLO 与 ByteTrack 的实时视频目标计数系统
  • 5秒极速破解百度网盘提取码:开源神器baidupankey完全指南
  • 北京华恒智信为文旅行业优化定岗定编、提质人均人效
  • Next.js Middleware 与鉴权:生活应用的安全层不能只靠客户端判断
  • 涡轮叶尖压力测量:Kulite传感器解决方案
  • Unbelievable!DDR5的地址时钟竟然按差分50欧姆设计
  • 鸿蒙6G全域通感一体化体系架构 第一卷一阶第七篇 超密集小区干扰生克制衡原理
  • 如何让Jellyfin媒体库自动获取完美元数据:MetaTube插件完整指南
  • TLA2518 ADC与PIC18F96J65的高精度数据采集方案
  • AI Agent从入门到实战:收藏必备 | MCP——Agent 工具生态的连接协议
  • 游戏手柄键盘映射终极指南:用AntiMicroX让任何游戏支持手柄操作
  • 本地企业 外链 Google Business:提升自然流量的6个实操步骤
  • AMD MI355X部署GLM5.2大模型:性能基准测试与成本优化实践
  • 中教睿海靠谱吗?CCTV认证与退役军人网官方合作实力验证
  • python使用xpath Python用Xpath抓数据?三步搞定,比BeautifulSoup还爽
  • 北京华恒智信为文旅行业破解数字化人才断层、落地数字化转型
  • 浏览器中的音乐格式转换利器:解锁你的数字音乐收藏
  • 计算机毕业设计之基于SSM框架的在线学习资源分享平台
  • 想转行AI?大模型4大热门赛道深度解析,小白也能收藏入坑!
  • 如何用Pearcleaner彻底清理macOS:免费工具终极指南
  • (论文速读)FasterDiT:无需修改架构即可实现更快的扩散变形金刚培训
  • 陈,大小鼠疼痛分析系统 福尔马林疼痛分析系统 疼痛分析系统
  • 2026成都ERP系统定制开发选型指南:避坑、自检与评估标准
  • Apache Dubbo:企业级微服务的老牌 RPC 框架
  • LLMs are eroding my software engineering career and I don‘t know what to do:一名初级开发者的深度突围指南