BLE连接生命周期映射:从扫描到连接、服务发现、数据交互、断开重连
BLE(Bluetooth Low Energy)设备的完整连接生命周期是一条有严格状态转换的链条。理解这条链条是设计稳定、可维护的蓝牙应用的前提,更是为后续使用Hilt精准管理各阶段组件作用域边界的基础。本章将逐阶段剖析生命周期,并展示对应的编程实践。
核心观点:每个生命周期阶段都对应一个可独立注入的Hilt组件或限定符。本章先掌握“阶段是什么”,后续章节再绑定至Hilt作用域。
生命周期阶段总览
| 阶段编号 | 阶段名称 | 关键行为 | 典型持续时间 |
|---|---|---|---|
| 1 | 扫描 | 发现周边可连接设备 | 几秒~几十秒 |
| 2 | 连接 | 建立GATT链路,获得BluetoothGatt | 0.5~5秒 |
| 3 | 服务发现 | 获取远程设备的所有Service和Characteristic | 0.2~2秒 |
| 4 | 数据交互 | 读写特征值、订阅通知 | 持续存在 |
| 5 | 断开与重连 | 主动/被动断开,触发自动或手动重连 | 可重复 |
注意:Android系统蓝牙API是异步的,每个阶段通常通过回调(Callback)与主线程交互。生命周期正确映射能有效避免回调混乱导致的资源泄漏或空指针。
状态转换 SVG 流程图
各阶段实现要点
1. 扫描(Scan)
使用 BluetoothLeScanner 的 startScan 方法。推荐使用 ScanFilter 过滤特定服务或设备名称,避免过多无关设备。
// Hilt注入扫描器
@Inject lateinit var bleScanner: BluetoothLeScanner
val scanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
// 筛选目标设备,例如服务UUID匹配
if (result.scanRecord?.serviceUuids?.contains(MY_SERVICE_UUID) == true) {
stopScan()
connectToDevice(result.device)
}
}
}
bleScanner.startScan(listOf(filter), scanSettings, scanCallback)
2. 连接(Connect)
通过 BluetoothDevice.connectGatt() 建立GATT连接。注意使用 BluetoothGattCallback 处理状态变化。
val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices() // 连接成功立即开始服务发现
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
handleDisconnect(gatt)
}
}
}
val gatt = device.connectGatt(context, false, gattCallback)
常见错误:连接时传入的
autoConnect 参数(false 代表主动连接;true 代表后台绑定,应谨慎使用)。
3. 服务发现(Service Discovery)
在 onConnectionStateChange 成功后调用 gatt.discoverServices(),然后在 onServicesDiscovered 回调中遍历 Service 和 Characteristic。
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
val service = gatt.getService(SERVICE_UUID) ?: return
val characteristic = service.getCharacteristic(CHAR_UUID) ?: return
// 保存引用,供后续数据交互
targetCharacteristic = characteristic
// 准备接收通知(如果需要)
gatt.setCharacteristicNotification(characteristic, true)
// 可选:读取特征值
gatt.readCharacteristic(characteristic)
}
}
4. 数据交互(Data Exchange)
- 读:
gatt.readCharacteristic(characteristic),结果在onCharacteristicRead回调。 - 写:
gatt.writeCharacteristic(characteristic),注意写入类型(WRITE_TYPE_DEFAULT或WRITE_TYPE_NO_RESPONSE)。 - 通知/指示:开启
setCharacteristicNotification并写入 CCCD(客户端特征配置描述符)。
fun writeData(data: ByteArray) {
characteristic.value = data
gatt.writeCharacteristic(characteristic)
}
override fun onCharacteristicWrite(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// 写入成功
}
}
override fun onCharacteristicChanged(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, value: ByteArray) {
// 收到通知数据
}
5. 断开与重连(Disconnect & Reconnect)
主动断开调用 gatt.disconnect() 和 gatt.close();被动断开(如信号丢失)需在 onConnectionStateChange 中实现重连策略。
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
when (newState) {
BluetoothProfile.STATE_DISCONNECTED -> {
if (shouldReconnect) {
// 延迟2秒后重新连接
handler.postDelayed({ gatt.connect() }, 2000)
} else {
gatt.close()
}
}
}
}
优化建议:重连时避免无限循环,可设定最大重试次数(如3次),失败后转为重新扫描。使用指数退避策略减少频繁请求。
调试与日志
| 阶段 | 关键日志标签 | 调试手段 |
|---|---|---|
| 扫描 | BluetoothLeScanner | 查看ScanResult内容,确认rssi、uuid |
| 连接 | BluetoothGatt | adb shell dumpsys bluetooth_manager 检查连接状态码 |
| 服务发现 | onServicesDiscovered | 列出所有service uuid,核对是否存在期望特征 |
| 数据交互 | onCharacteristic* | 使用nRF Connect嗅探实际收发的数据 |
| 断开 | onConnectionStateChange | 记录 status 值(133=超时,62=连接参数不良) |
常见错误及规避
- GATT 133 错误:连接超时。检查设备是否在有效范围,或调低扫描间隔。
- 服务发现返回空:设备未广播primary service,或连接后立即调用discoverServices但底层链路未稳定。可延迟200ms再发现。
- 写特征值无响应:未检查写入类型匹配。短包用
WRITE_TYPE_NO_RESPONSE,长包需拆分。 - 重连死循环:未判断
status是否为GATT_SUCCESS。若status非0,应放弃重连。
性能与稳定性优化
- 批量操作调度:读写请求使用队列串行执行,避免同时多个请求导致回调错乱。
- 连接参数协商:连接成功后通过
requestConnectionPriority提升数据吞吐。 - 保持Context存活:使用
Application级别Context,防止Activity销毁导致蓝牙回调丢失。 - 电量控制:交互完毕及时关闭通知、断开连接,释放BluetoothGatt对象。