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 流程图

BLE 连接生命周期状态转换 扫描 (Scan) 连接 (Connect) 服务发现 数据交互 断开 / 重连 实线箭头:正常流程向前推进;虚线箭头:重连失败后回退到扫描,或重连成功跳过扫描。 Hilt作用域应映射为:单例(扫描)→ 限界(连接期间)→ 自定义(数据交互),详见后续章节。

各阶段实现要点

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
连接BluetoothGattadb 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对象。