智能手表与 App 蓝牙低功耗(BLE)实战指南
DemoApplication — 智能手表与 App 蓝牙低功耗(BLE)实战指南
文档主题
智能手表与手机 App 之间的通信常采用蓝牙低功耗(BLE)。相比经典蓝牙,BLE 更省电、适合周期性小数据同步(心率、步数、通知、固件升级进度等),是穿戴设备的主流方案。大文件(如MP3 音频)若必须走 BLE,需按分包与断点续传设计,见下文第四章第 6 节。
本仓库当前为Jetpack Compose示例工程(minSdk 24/compileSdk 34),可作为在此基础上接入 BLE 的起点。
一、BLE 在手表场景中的常见用法
| 能力 | 说明 |
|---|---|
| GATT 客户端 | 手机 App 通常作为Central(中心设备),手表作为Peripheral(外设),App 连接后读写特征值(Characteristic)。 |
| 通知 / 指示 | 手表主动上报数据:使用Notify或Indicate(Indicate 带应用层确认)。 |
| 写入指令 | App 向手表下发控制或配置:对可写特征执行Write / Write Without Response。 |
| MTU 与分包 | 数据量大时需协商MTU,或在上层做分包与粘包协议。 |
| 配对与绑定 | 涉及敏感数据或防重放时,可能依赖系统配对;纯明文广播+连接则未必每次配对。 |
常见协议形态:厂商自定义GATT Service/Characteristic UUID,或基于标准服务(如 Heart Rate、Battery 等)再扩展私有特征。
二、开发环境与本项目运行
- Android Studio:建议 Hedgehog 及以上,自带 JDK 与 Android SDK。
- 设备:真机(推荐)用于 BLE;模拟器对蓝牙支持有限。
- 运行:用 Android Studio 打开工程根目录,同步 Gradle 后选择设备,运行
app模块。
当前入口:MainActivity使用 Compose;已实现BLE Demo(权限门闸、扫描列表、连接、服务枚举、requestMtu、尝试读标准电量、WatchProtocol本地分包演示),见ble与ui/BleDemoScreen包。
三、Android 端实战步骤(推荐顺序)
1. 声明权限(随 Android 版本组合使用)
- 大致规律:Android 12(API 31)起位置权限与蓝牙权限拆分;扫描场景常需精确位置或新蓝牙权限组合,具体以目标
targetSdk与官方文档为准。 - 清单中常见项(名称以你最终 targetSdk 为准核对官方表):
BLUETOOTH/BLUETOOTH_ADMIN(低版本)BLUETOOTH_SCAN、BLUETOOTH_CONNECT(API 31+)- 若扫描需满足「旧策略」:
ACCESS_FINE_LOCATION等 BLUETOOTH_ADVERTISE(若 App 需作为外设广播,一般手表 App 较少)
实战要点:在运行时请求危险权限;连接前检查BluetoothAdapter是否可用、蓝牙是否开启。
2. 扫描附近外设
- 使用
BluetoothLeScanner(startScan/stopScan)。 - 过滤:按厂商提供的Service UUID或设备名前缀过滤,减少列表噪音。
- 节流:避免长时间全量扫描耗电;找到目标后及时
stopScan。
3. 建立 GATT 连接
device.connectGatt(context, false, callback),在BluetoothGattCallback中处理:onConnectionStateChange:已连接 / 断开onServicesDiscovered:发现服务后枚举Service → Characteristic → DescriptoronCharacteristicRead/onCharacteristicWrite/onCharacteristicChanged(Notify 数据)
4. 打开 Notify / Indicate
- 对 CCCD(Client Characteristic Configuration Descriptor)写入
ENABLE_NOTIFICATION_VALUE或ENABLE_INDICATION_VALUE。 - 在
onDescriptorWrite中确认成功后再认为「订阅就绪」。
5. 读写业务数据
- 读:
readCharacteristic(注意队列:部分机型不宜连续无等待地堆叠操作)。 - 写:根据固件约定选择Write With Response或Without Response。
- 协议:与手表固件约定好字节序、命令字、长度、校验(CRC 等)及错误码。
6. 断开与释放
disconnect()后close(),避免泄漏;页面销毁或退后台策略要与产品一致(部分场景需保持连接)。
7. 前台服务(可选但常见)
- 长时间同步或 OTA 时,使用Foreground Service+ 类型合适的
foregroundServiceType,避免被系统强杀并符合后台限制。
四、大量数据(如走路步数)怎么传
BLE 单次 ATT 载荷受MTU限制,步数若按「每分钟/每小时一条」累积成几天历史,很容易超过单包长度,必须在应用层协议和产品形态上一起设计。
1. 能少传就少传:摘要优先
- 实时:连接期间只推「当前会话增量」或「今日累计」,字节很少。
- 历史:不要传原始传感器流;用按天/按小时聚合的结构(时间戳 + 步数 + 可选距离/卡路里),必要时再支持「拉取某一天明细」。
- 二进制紧凑:定长记录或小端整数数组,避免 JSON/XML 占满 MTU。
2. 必须传大包时:MTU + 分包 + 确认
- 协商 MTU:连接建立后调用
requestMtu()(具体上限因手机与手表协议栈而异,常见在约 185~247 字节有效 ATT 载荷量级,以实测为准)。 - 自定义分包帧:例如「命令字 + 总长度 + 分片序号/总分片数 + 载荷 + CRC」。手表按序发 Notify(或 App 读长数据),App 侧重组缓冲区;丢包则重传某片。
- 流控:每发 N 包等一层 ACK(或 Indicate),避免手表 RAM 与控制器队列溢出导致断连。
- Indicate vs Notify:大批量若要求可靠,可对关键片用Indicate(有确认),或仍用 Notify 但在应用层发 ACK 包。
3. 传输路径选择
- Notify 推流:手表主动推,适合「同步历史」会话;注意 Android 端串行化 GATT 操作(不要无脑并发写)。
- Read 分块:固件把历史放在「逻辑块」里,App 发「读第 k 块」命令,再
readCharacteristic或走私有「块特征」;便于断点续传。 - Prepare Write / Long Write:标准上适合较长写入;穿戴里更常见仍是厂商自定义分包,实现简单、两端一致即可。
4. 吞吐与体验
- 连接间隔:主要由系统与对端协商,App 能控制的有限;大批量同步时保持屏幕常亮、前台服务,减少被限速或杀后台。
- PHY:若双方支持2M PHY,在可接受距离内有利于提高速率(仍远低于经典蓝牙)。
- 失败重试:超时、CRC 错误、只收到部分分片时,从最后成功序号续传。
5. 与「经典蓝牙」的取舍
若手表同时支持经典蓝牙(SPP 等)且产品允许配对两套栈,大批量文件类同步可走经典通道;纯 BLE 手表则按上文分包与聚合设计即可。
6. MP3 / 音频文件传输
MP3 属于已压缩的二进制大文件(常见数 MB),与步数不同:不能靠「聚合摘要」缩小,只能整文件按字节搬运,技术本质与固件 OTA 分包相同,只是落盘路径与格式校验不同。
能力预期
- BLE 实际吞吐受连接间隔、PHY、对端实现影响,常见在每秒数十 KB 量级(以双端实测为准)。一首 5MB 的 MP3 纯 BLE 可能要数分钟甚至更久,且耗电、占连接,需有明确 UI(进度、取消、后台策略)。
- 不要再压一层「通用压缩」:MP3 本身已压缩,gzip 等收益很小,徒增 CPU。
协议层面(与上文「分包 + 流控」一致)
- 定义文件会话:
fileId(或路径 token)、总长度、分片大小(建议与协商 MTU 对齐,留出帧头/CRC 空间)、分片序号、载荷、CRC32/SHA256(整文件校验)。 - 断点续传:持久化「已确认收到的最大连续偏移」,重连后从该偏移继续,避免用户反复全量传。
- 方向:通常是手机 → 手表(下发铃声、离线播客片段);若手表回传录音 MP3,思路相同但注意手表侧存储空间与写闪存寿命。
- 存储与格式:固件约定写入路径(如「音乐分区」)、单文件上限;可选先写到临时文件再
rename做原子提交,避免半截文件被播放器打开。
产品形态上的更优解(优先评估)
- 手表带 WiFi / 配套手机热点:大文件走 HTTP(S) 或厂商私有 WiFi 通道,体验远好于纯 BLE。
- 经典蓝牙(A2DP/SPP 或厂商大通道):适合「传歌到表」类场景,若硬件支持应优先考虑。
- 只做短音频:闹钟、提示音可用几十~数百 KB 的短片段(甚至降级为低码率或专用提示音格式),显著缩短 BLE 传输时间。
Android 实现注意
- 读本地 MP3 用顺序流式读取(
FileInputStream/MappedByteBuffer分块),避免一次性readBytes()整文件进内存。 - 传输会话放在前台服务中执行,并处理系统杀进程、蓝牙断开后的恢复与重试。
五、与手表固件协作的检查清单
- UUID 表:主服务 UUID、各特征 UUID、属性(读/写/Notify)、字节布局文档。
- 连接参数:是否要求指定 PHY、连接间隔偏好(由固件与主机协商,但需知预期)。
- 安全:是否加密、是否必须配对、密钥与证书流程。
- OTA:分包大小、断点续传、校验与回滚策略。
- 调试工具:手机端可用nRF Connect等 App 对真实手表 GATT 做探查,与 Android 日志对照。
六、常见问题与排查
| 现象 | 可能原因 |
|---|---|
| 扫描不到设备 | 权限未授予、蓝牙关闭、手表未处于可发现模式、过滤条件过严 |
| 连接立刻断开 | UUID 不匹配、固件只允许单连接、RSSI 过弱、固件侧拒绝 |
| Notify 无数据 | CCCD 未写成功、订阅错特征、固件未推数据 |
| 写入无响应 | 特征不可写、需 Write Without Response、MTU/长度超限 |
调试时打开HCI snoop或使用厂商抓包工具,可快速区分是 App 层还是协议/固件层问题。
七、在本项目中落地的建议结构(Kotlin)
便于维护的拆分方式(示例思路,非强制目录名):
BlePermissions:统一请求与解释权限文案BleScanner:扫描生命周期与回调转换GattClient:封装BluetoothGatt、队列化读写、重连策略WatchProtocol:纯 Kotlin 的组包/解包,与 UI 解耦- UI 层(Compose):只观察状态(已连接 / 扫描列表 / 最新心率等),不直接操作 GATT 细节
依赖上可逐步引入Kotlin 协程+MutableStateFlow向界面层推送状态;若后续需要蓝牙相关 Jetpack API,再按官方文档添加对应依赖版本。
点击跳转代码链接
https://gitcode.com/qq_33495943/watchesBLEAPP
