连接与断开:BluetoothDevice、connectGatt、BluetoothGattCallback、连接状态管理、重连机制
好,咱们进入正题。上一章我们搞定了扫描,拿到了设备对象——BluetoothDevice。接下来要做的,就是跟这个设备建立连接。
连接这块,说简单也简单,调一个 connectGatt 就完事了。但说复杂也复杂——连接失败怎么办?断连了怎么处理?要不要自动重连?这些坑,我当年都踩过。
1. BluetoothDevice:你扫描到的那个“人”
BluetoothDevice 是什么?说白了,它就是远端蓝牙设备的一个抽象。你通过扫描回调拿到它,然后拿着它去建立连接。
它有几个关键信息:
- 地址:
device.address,比如00:11:22:33:44:55 - 名称:
device.name,可能为 null - 绑定状态:
device.bondState
我个人习惯,在扫描到设备后,先把地址和名称存下来。万一后面连接出问题,还能根据地址重试。
BluetoothDevice 是系统管理的对象,同一个物理设备在不同时间扫描到,可能是同一个实例(系统会复用)。所以你可以放心用它做 key 来管理连接状态。
2. connectGatt:发起连接的入口
拿到 BluetoothDevice 后,调用它的 connectGatt 方法就能发起连接。这个方法有多个重载,最常用的是这个:
fun connectToDevice(device: BluetoothDevice, context: Context): BluetoothGatt? {
return device.connectGatt(
context,
false, // autoConnect
object : BluetoothGattCallback() {
// 回调处理
}
)
}
这里有个关键参数 autoConnect。我刚开始做 BLE 时,一直没搞懂它到底干嘛的。
- autoConnect = false:直接发起连接,如果连不上就报错。适合你主动去连一个已知在附近的设备。
- autoConnect = true:系统会在后台监听设备广播,等设备出现时自动连接。适合需要长期监听、设备可能不在身边的场景。
嗯,这里要注意:autoConnect = true 的连接速度很慢,因为系统要等广播。我一般在需要快速交互的场景下都用 false。
3. BluetoothGattCallback:连接后的“消息中心”
连接不是一锤子买卖。你发起连接后,系统会通过 BluetoothGattCallback 告诉你结果。这个回调接口里,你必须关注这几个方法:
| 回调方法 | 触发时机 | 说明 |
|---|---|---|
onConnectionStateChange |
连接状态变化时 | 最重要的回调,连接成功/断开都走这里 |
onServicesDiscovered |
服务发现完成 | 连接成功后,需要调用 discoverServices 触发 |
onCharacteristicRead |
读取特征值完成 | 读写操作的结果回调 |
onCharacteristicWrite |
写入特征值完成 | 同上 |
你想想看,整个 BLE 通信的生命周期,其实都围绕着这个回调展开。所以设计好回调的处理逻辑,是重中之重。
4. 连接状态管理:别让状态乱掉
我在项目中遇到过一个问题:用户快速点击连接/断开按钮,结果状态乱了——界面显示已连接,实际上已经断开了。
为什么会这样?因为 onConnectionStateChange 是异步回调,你点一下连接,系统还没回调,你又点了一下断开,这时候状态就打架了。
我的做法是:用一个状态机来管理连接状态。
enum class ConnectionState {
DISCONNECTED,
CONNECTING,
CONNECTED,
DISCONNECTING
}
class BleManager {
private var connectionState = ConnectionState.DISCONNECTED
private var gatt: BluetoothGatt? = null
fun connect(device: BluetoothDevice) {
if (connectionState != ConnectionState.DISCONNECTED) {
// 已经在连接中或已连接,忽略
return
}
connectionState = ConnectionState.CONNECTING
gatt = device.connectGatt(context, false, callback)
}
private val callback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
when (newState) {
BluetoothProfile.STATE_CONNECTED -> {
connectionState = ConnectionState.CONNECTED
// 通知 UI
}
BluetoothProfile.STATE_DISCONNECTED -> {
connectionState = ConnectionState.DISCONNECTED
// 清理资源
}
}
}
}
}
这样,不管用户怎么点,只要状态不是 DISCONNECTED,新的连接请求就会被忽略。状态不会乱。
onConnectionStateChange 中的 status 参数也很重要。如果 status != GATT_SUCCESS,说明连接过程出错了(比如超时、设备不在范围内)。这时候即使 newState == STATE_DISCONNECTED,你也应该做特殊处理,比如提示用户或触发重连。
5. 重连机制:让连接更健壮
BLE 连接其实挺脆弱的。设备走远了、信号被遮挡、甚至周围蓝牙干扰太多,都可能导致断连。
我曾经做过一个项目,设备在口袋里,手机在手上,走几步就断连了。用户反馈说「怎么老断?」,我只好加上重连机制。
重连的核心思路很简单:
- 检测到断连
- 判断是否需要重连(比如用户正在操作、设备在重连白名单里)
- 延迟一段时间后重新调用
connectGatt - 设置最大重连次数,避免无限重试
代码大概长这样:
class ReconnectManager(private val maxRetries: Int = 3) {
private var retryCount = 0
private var handler = Handler(Looper.getMainLooper())
fun onDisconnected(device: BluetoothDevice) {
if (retryCount < maxRetries) {
retryCount++
handler.postDelayed({
// 重新连接
device.connectGatt(context, false, callback)
}, 2000) // 延迟2秒
} else {
// 超过最大重试次数,放弃
retryCount = 0
// 通知 UI 连接失败
}
}
fun onConnected() {
retryCount = 0
handler.removeCallbacksAndMessages(null)
}
}
这里有几个细节:
- 延迟时间:我一般用 1-3 秒。太短了会频繁重试,耗电;太长了用户等不及。
- 重连次数:3 次差不多了。超过 3 次还连不上,说明设备真的不在附近,别白费力气。
- 清除回调:连接成功后,记得把 pending 的重连任务取消掉,不然会重复连接。
6. 断开连接:优雅地收尾
断开连接比连接简单,但也有一些讲究。
fun disconnect() {
gatt?.let {
it.disconnect() // 发起断开请求
it.close() // 释放资源
gatt = null
}
connectionState = ConnectionState.DISCONNECTED
}
注意顺序:先 disconnect,再 close。disconnect 是告诉系统我要断开,close 是释放 BluetoothGatt 占用的资源。如果先 close 再 disconnect,系统可能会报错。
BluetoothGatt 对象在用完后一定要 close,否则会泄漏。每个 connectGatt 调用都会创建一个新的 BluetoothGatt 实例,不 close 的话,内存会越占越多。
7. 整体流程梳理
说了这么多,咱们用一张图把整个连接断开流程串起来:
这张图把整个流程串起来了。从扫描到设备开始,调用 connectGatt,然后在回调里判断成功还是失败。成功了就去发现服务,失败了就触发重连。断开连接时记得 close 释放资源。
8. 避坑指南
最后,分享几个我踩过的坑:
- 不要在主线程做耗时操作:
onConnectionStateChange虽然跑在 Binder 线程,但如果你在里面做数据库操作或网络请求,还是可能卡住。我一般用 Handler 切到子线程处理。 - 注意 Android 版本差异:Android 10 以后,后台扫描和连接的限制更严了。如果你的 App 在后台,连接可能会失败。记得处理好
onConnectionStateChange中的status为 133 的情况(这是系统拒绝连接的标志)。 - 多设备连接时注意区分:如果你同时连接多个设备,每个设备都有自己的
BluetoothGatt和回调。别搞混了。我习惯用device.address作为 key 来管理。
好了,连接与断开这块就讲到这里。下一章我们会深入 BluetoothGatt 的细节,看看怎么发现服务、读写特征值。到时候见。
公众号:蓝海资料掘金营,微信deep3321