ATT 设备连接与断开管理
ATTConnect 示例工程中的 BLE 连接管理能力,以 BleManager 单例为核心,封装了从扫描、配对、GATT 连接、MTU 协商、服务发现到主动断开、超时兜底与重连的完整生命周期管理。
Purpose and Scope
本文档介绍 ATTConnect 模块中 ATT 设备连接与断开管理 的实现机制,涵盖:
BleManager单例的职责划分、初始化与销毁生命周期;connectBleDevice()连接流程(含 GATT over BR/EDR 的配对前置检查);disconnectBleDevice()断开流程与三种连接状态下的分支处理;- 基于主线程
Handler的超时/兜底消息机制(连接、通知、MTU、服务发现、断开、配对超时); - 多设备连接状态管理(
mBleDeviceMap、BleDevice状态机、正在通讯设备切换)。
本页不涉及以下内容(由同级目录页覆盖):BLE 扫描与设备发现细节、数据透传与发送队列(SendBleDataThread)、蓝牙配对工具(BluetoothUtil.createBond)的底层实现。本文档聚焦"连接建立与拆除"这一条主线。
Overview
ATT(Attribute Protocol)是 BLE 链路层之上用于读写属性的协议。在 Android 上,应用通过 BluetoothGatt#connectGatt() 发起 GATT 连接,随后经历 onConnectionStateChange(连接状态变化)、MTU 协商(requestMtu)、服务发现(discoverServices)、特征通知使能(CCCD 写入)等阶段,才进入可通讯状态。
BleManager 将上述零散且异步的系统回调收敛为一个可感知连接状态、可超时兜底、可重连的封装层,向上层暴露简单的方法调用(connectBleDevice / disconnectBleDevice)与事件回调(BleEventCallback)。其设计意图包括:
- 集中管理连接生命周期:所有 GATT 对象、设备状态、超时定时器都挂在
BleManager内部,避免上层 Activity 直接接触系统 API 的复杂性; - 异步兜底:Android BLE 回调可能丢失或长时间不返回,因此为每个关键阶段都设置了超时消息(连接 40s、服务发现、MTU、断开 6s、配对 30s),超时后主动执行降级或断开策略;
- 支持多设备与 GATT over BR/EDR:ATTConnect 演示了传统 BLE(LE transport)与双模设备(BR/EDR transport)两种连接方式,后者要求先完成系统配对。
Architecture
flowchart TD
subgraph sg_App["应用层 (ATTConnect)"]
Activity["MainActivity / 业务页面"]
CallbackImpl["BleEventCallback 实现"]
end
subgraph sg_BleManager["BleManager (单例)"]
BM["BleManager instance"]
Handler["主线程 Handler<br/>8 种超时/兜底消息"]
CallbackMgr["BleEventCallbackManager<br/>回调分发"]
DeviceMap["mBleDeviceMap<br/>Map<String, BleDevice>"]
GattCallback["mBluetoothGattCallback"]
end
subgraph sg_Model["模型与工具"]
BleDevice["BleDevice<br/>连接状态/MTU/Gatt"]
ScanDeviceInfo["ScanDeviceInfo"]
BluetoothUtil["BluetoothUtil<br/>createBond / deviceEquals"]
Config["Config<br/>UUID 与 MTU 常量"]
end
subgraph sg_System["Android 系统层"]
Adapter["BluetoothAdapter / LeScanner"]
Gatt["BluetoothGatt"]
SysReceiver["BroadcastReceiver<br/>蓝牙开关广播"]
end
Activity -->|"registerBleEventCallback"| BM
BM -->|"onBleConnection 等事件"| CallbackMgr
CallbackMgr -->|"分发"| CallbackImpl
BM --> Handler
Handler -->|"超时后强制断开/降级"| BM
BM --> DeviceMap
DeviceMap --> BleDevice
BM --> GattCallback
BM --> Config
BM -->|"配对前置检查"| BluetoothUtil
BM -->|"connectGatt / disconnect"| Gatt
BM -->|"startScan / stopScan"| Adapter
BM --> SysReceiver
架构说明:
- BleManager 是全局唯一的门面(double-checked locking 单例,见 BleManager.java),持有
BluetoothAdapter、BluetoothLeScanner、设备 Map 与回调管理器; - 主线程 Handler 承担所有超时兜底逻辑(
MSG_CONNECT_BLE_TIMEOUT等 8 类消息),保证定时回调与 UI 线程一致,避免多线程竞态; - BleEventCallbackManager 负责把内部状态变化(连接、发现、扫描状态、连接设备列表变化)广播给一个或多个上层回调;
- BleDevice 是每个远端设备的状态容器(连接状态、MTU、Gatt、发送线程),由
mBleDeviceMap按地址索引; - BluetoothUtil 提供配对(
createBond)、设备比较(deviceEquals)、系统已连接设备列表等系统级辅助能力,ATT/BR-EDR 连接前必须依赖它完成配对。
核心实现分析
BleManager 单例与初始化
BleManager 采用 volatile + 双重检查锁的单例模式,由 MyApplication 提供 Application 级 Context,因此整个进程生命周期内只有一个连接管理实例(BleManager.java):
private BleManager(Context context) {
if (null == context) {
throw new RuntimeException("Context can not be null.");
}
mContext = context;
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (Build.VERSION.SDK_INT >= LOLLIPOP && mBluetoothAdapter != null) {
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
}
registerReceiver();
}
public static BleManager getInstance() {
if (instance == null) {
synchronized (BleManager.class) {
if (instance == null) {
instance = new BleManager(MyApplication.Companion.getApplication());
JL_Log.w(TAG, "init", "instance : " + instance);
}
}
}
return instance;
}
Source: BleManager.java
设计意图:连接管理必须在整个 App 生命周期内唯一,任何页面(如设备列表页、通讯页)都能通过 getInstance() 拿到同一实例;registerReceiver() 在构造时注册蓝牙开关广播,蓝牙关闭时系统会强制断开 GATT,Manager 需据此同步内部状态。
核心常量(BleManager.java):
| 常量 | 值 | 含义 |
|---|---|---|
TRANSPORT_AUTO / TRANSPORT_BREDR / TRANSPORT_LE | 0 / 1 / 2 | 对应 BluetoothDevice.connectGatt 的 transport 参数 |
BLE_UUID_SERVICE / BLE_UUID_WRITE / BLE_UUID_NOTIFICATION | 来自 Config | 业务服务/写/通知特征 UUID |
BLE_UUID_NOTIFICATION_DESCRIPTOR | 00002902-0000-1000-8000-00805F9B34FB | CCCD(客户端特征配置描述符),用于使能通知 |
SEND_DATA_MAX_TIMEOUT | 8000ms | 单次写数据最大超时 |
SCAN_BLE_TIMEOUT | 12s | 扫描超时 |
CONNECT_BLE_TIMEOUT | 40s | 连接超时 |
CALLBACK_TIMEOUT | 6000ms | 断开回调兜底超时 |
RECONNECT_BLE_DELAY | 2000ms | 重连延迟 |
BOUND_TIMEOUT | 30s | ATT/BR-EDR 配对超时 |
连接流程:connectBleDevice
connectBleDevice() 有三个重载,最终收敛到三参数版本 connectBleDevice(BluetoothDevice, int transport, int mtu)(BleManager.java):
public boolean connectBleDevice(BluetoothDevice device, int transport, int mtu) {
if (!checkConnectEnv("connectBleDevice", device) || !isBluetoothEnable())
return false;
if (isConnectedDevice(device)) {
mCallbackManager.onBleConnection(device, BluetoothProfile.STATE_CONNECTED);
return true;
}
final BluetoothDevice connectingDevice = getConnectingDevice();
if (connectingDevice != null) {
JL_Log.e(TAG, "connectBleDevice", BluetoothUtil.formatString("Device(%s) is connecting, please wait.",
printDeviceInfo(connectingDevice)));
return isConnectingDevice(device);
}
if (isBleScanning()) {
stopLeScan();
}
//回调BLE连接中状态
final BleDevice bleDevice = addBleDevice(device);
bleDevice.setTransport(transport).setRequestMtu(mtu);
startConnectTimeout(device);
handleBleConnection(device, BluetoothProfile.STATE_CONNECTING);
if (bleDevice.isATTDevice()) { //连接Gatt over BR/EDR, 需要判断是否已配对
final int boundState = device.getBondState();
if (boundState != BluetoothDevice.BOND_BONDED) {
boolean ret = boundState == BluetoothDevice.BOND_BONDING; //正在配对中,等待配对结果
mHandler.removeMessages(MSG_BOUND_DEVICE_TIMEOUT);
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_BOUND_DEVICE_TIMEOUT, device), BOUND_TIMEOUT);
if (!ret) {
//未配对,进行配对流程
ret = BluetoothUtil.createBond(mContext, device);
...
if (!ret) {
mHandler.removeMessages(MSG_BOUND_DEVICE_TIMEOUT);
handleBleConnection(device, BluetoothProfile.STATE_DISCONNECTED);
}
}
return ret;
}
}
BluetoothGatt gatt = null;
try {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
gatt = device.connectGatt(mContext, false, mBluetoothGattCallback,
transport, 1, mHandler);
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) {
gatt = device.connectGatt(mContext, false, mBluetoothGattCallback,
transport, 1);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
gatt = device.connectGatt(mContext, false, mBluetoothGattCallback, transport);
} else {
gatt = device.connectGatt(mContext, false, mBluetoothGattCallback);
}
} catch (Exception e) {
e.printStackTrace();
}
boolean ret = gatt != null;
bleDevice.setGatt(gatt);
...
if (!ret) {
handleBleConnection(device, BluetoothProfile.STATE_DISCONNECTED);
}
return ret;
}
Source: BleManager.java
连接流程的关键设计点:
- 幂等保护:若设备已在连接列表中且状态为
STATE_CONNECTED,直接补发一次STATE_CONNECTED回调并返回true,避免重复建连;若已有其他设备处于 connecting,则拒绝新连接并返回isConnectingDevice(device)的结果——同一时间只允许一个设备处于连接中。 - 扫描互斥:发起连接前若正在扫描则先
stopLeScan(),减少扫描与连接的射频冲突。 - 状态先行:
addBleDevice()创建BleDevice并登记到mBleDeviceMap,随后立即回调STATE_CONNECTING,让 UI 第一时间进入"连接中"展示。 - ATT/BR-EDR 配对前置:
BleDevice.isATTDevice()判定为双模 ATT 设备时,连接前必须完成系统配对。未配对则调用BluetoothUtil.createBond()发起配对,并启动 30s 的MSG_BOUND_DEVICE_TIMEOUT兜底;配对失败直接回调STATE_DISCONNECTED。 - 版本化 connectGatt:按 SDK 版本选择不同的
connectGatt重载——Android 8.1+ 使用带transport, phy=1, Handler的 6 参数版本(phy 固定为 1,即PHY_LE_1M);Android 8.0 使用 5 参数版本;Android 6.0+ 使用带 transport 的版本;旧版本退回无 transport 版本。autoConnect=false(第二个参数),即发起主动连接而非后台监听。 - 失败兜底:
connectGatt抛异常或返回 null 时回调STATE_DISCONNECTED,保证状态机不悬挂。
默认参数重载(BleManager.java):
public boolean connectBleDevice(BluetoothDevice device, int transport) {
return connectBleDevice(device, transport, Config.REQUEST_BLE_MTU);
}
public boolean connectBleDevice(BluetoothDevice device) {
return connectBleDevice(device, TRANSPORT_LE);
}
Source: BleManager.java
即上层最常见的 connectBleDevice(device) 等价于"LE transport + 默认请求 MTU";请求 MTU 的值来自 Config.REQUEST_BLE_MTU,传统 BLE 连接成功后 Manager 会执行 MTU 协商(Android 建议范围 20~509)。
断开流程:disconnectBleDevice
disconnectBleDevice() 按 BleDevice 当前连接状态分三条路径处理(BleManager.java):
public void disconnectBleDevice(BluetoothDevice device) {
if (!checkConnectEnv("disconnectBleDevice", device)) return;
BleDevice bleDevice = getBleDevice(device);
...
final BluetoothGatt gatt = bleDevice.getGatt();
if (!isBluetoothEnable()) {
bleDevice.setConnection(BluetoothProfile.STATE_DISCONNECTED);
}
switch (bleDevice.getConnection()) {
case BluetoothProfile.STATE_CONNECTED: {
if (null != gatt) {
mHandler.removeMessages(MSG_DISCONNECT_BLE_TIMEOUT);
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_DISCONNECT_BLE_TIMEOUT, device), CALLBACK_TIMEOUT);
gatt.disconnect();
}
break;
}
case BluetoothProfile.STATE_CONNECTING: {
if (null != gatt) {
gatt.disconnect();
closeGatt(gatt);
}
handleBleConnection(device, BluetoothProfile.STATE_DISCONNECTED);
break;
}
default:
removeConnectedBle(device);
mCallbackManager.onBleConnection(device, BluetoothProfile.STATE_DISCONNECTED);
break;
}
}
Source: BleManager.java
三条路径的设计意图:
- STATE_CONNECTED(已连接):调用
gatt.disconnect()走系统正常断开流程,期望onConnectionStateChange(STATE_DISCONNECTED)回调驱动后续清理;同时启动 6s 的MSG_DISCONNECT_BLE_TIMEOUT兜底——若系统回调迟迟不来(某些厂商 ROM 的已知问题),由 Handler 直接closeGatt()并强制回调STATE_DISCONNECTED(见 BleManager.java)。 - STATE_CONNECTING(连接中):此时没有可靠的系统回调,直接
disconnect + close并同步回调STATE_DISCONNECTED,让 UI 立即恢复。 - 其他状态(含已断开/未知):仅从 Map 中移除并补发断开回调,保证事件对称。
- 另外,如果蓝牙本身已关闭(
isBluetoothEnable() == false),先把BleDevice状态强制置为STATE_DISCONNECTED,因为系统不会再有回调。
超时兜底消息机制
整个连接生命周期由主线程 Handler 驱动超时控制,共 8 种消息(BleManager.java):
| 消息 | 触发时机 | 超时值 | 兜底动作 |
|---|---|---|---|
MSG_SCAN_BLE_TIMEOUT (0x1010) | startLeScan | 12s | stopLeScan() |
MSG_CONNECT_BLE_TIMEOUT (0x1011) | connectBleDevice | 40s | disconnectBleDevice(device) |
MSG_SCAN_HID_DEVICE (0x1012) | 周期任务 | 1s 循环 | 同步系统已配对的 BLE 设备 |
MSG_NOTIFY_BLE_TIMEOUT (0x1013) | 使能通知 | 6s | disconnectBleDevice(device) |
MSG_CHANGE_BLE_MTU_TIMEOUT (0x1014) | MTU 协商 | — | MTU 降级为 20,继续按已连接处理 |
MSG_BLE_DISCOVER_SERVICES_CALLBACK_TIMEOUT (0x1015) | 服务发现 | — | 已有服务则补发 onServicesDiscovered,否则置 needReconnect=true 并断开 |
MSG_DISCONNECT_BLE_TIMEOUT (0x1016) | disconnectBleDevice | 6s | closeGatt() + 强制回调 STATE_DISCONNECTED |
MSG_BOUND_DEVICE_TIMEOUT (0x1017) | ATT 设备配对 | 30s | 回调 STATE_DISCONNECTED |
例如 MTU 协商超时时(BleManager.java):
case MSG_CHANGE_BLE_MTU_TIMEOUT: {
if (!(msg.obj instanceof BluetoothDevice)) return false;
BluetoothDevice device = (BluetoothDevice) msg.obj;
BleDevice bleDevice = getBleDevice(device);
...
if (null == bleDevice) return false;
bleDevice.setMtu(20);
handleBleConnection(device, BluetoothProfile.STATE_CONNECTED);
break;
}
Source: BleManager.java
设计意图:Android 的 GATT 回调是异步且不保证可靠的,超时兜底把"等系统回调"变成"自己掌握进度"。MTU 协商失败不视为连接失败——降级到 20 字节默认 MTU 继续通讯,这是对低质量外设的宽容处理;而服务发现超时若没有任何服务缓存,则标记 needReconnect 并断开,为后续自动重连(RECONNECT_BLE_DELAY = 2000ms)创造条件。
Core Flow
连接建立序列
sequenceDiagram
participant App as 上层业务
participant BM as BleManager
participant H as 主线程 Handler
participant U as BluetoothUtil
participant G as BluetoothGatt
participant CB as mBluetoothGattCallback
App->>BM: connectBleDevice(device, transport, mtu)
BM->>BM: 幂等检查(已连接/连接中/扫描互斥)
BM->>BM: addBleDevice + 回调 STATE_CONNECTING
BM->>H: 发送 MSG_CONNECT_BLE_TIMEOUT (40s)
alt ATT 设备且未配对
BM->>U: createBond(device)
U-->>BM: 配对结果
BM->>H: MSG_BOUND_DEVICE_TIMEOUT (30s)
end
BM->>G: connectGatt(context, false, CB, transport, phy=1, handler)
G-->>CB: onConnectionStateChange(CONNECTED)
CB->>BM: handleBleConnection(device, STATE_CONNECTED)
BM->>H: 移除连接超时消息
BM->>G: requestMtu(Config.REQUEST_BLE_MTU)
G-->>CB: onMtuChanged(mtu)
CB->>BM: 更新 BleDevice.mtu
BM->>G: discoverServices()
G-->>CB: onServicesDiscovered
CB->>BM: 使能通知(写 CCCD 00002902)
BM-->>App: 回调 STATE_CONNECTED / 服务就绪
断开序列
sequenceDiagram
participant App as 上层业务
participant BM as BleManager
participant H as 主线程 Handler
participant G as BluetoothGatt
participant CB as mBluetoothGattCallback
App->>BM: disconnectBleDevice(device)
alt 已连接
BM->>H: MSG_DISCONNECT_BLE_TIMEOUT (6s 兜底)
BM->>G: gatt.disconnect()
G-->>CB: onConnectionStateChange(DISCONNECTED)
CB->>BM: handleBleConnection(device, STATE_DISCONNECTED)
else 连接中
BM->>G: disconnect + closeGatt
BM-->>App: 回调 STATE_DISCONNECTED
else 其他状态
BM->>BM: removeConnectedBle(device)
BM-->>App: 回调 STATE_DISCONNECTED
end
H-->>BM: MSG_DISCONNECT_BLE_TIMEOUT 到达(若系统回调未返回)
BM->>G: closeGatt + 强制回调 STATE_DISCONNECTED
连接状态与多设备管理
状态机
BleDevice 内部以 BluetoothProfile.STATE_* 常量表达连接状态,BleManager 通过 handleBleConnection() 驱动状态迁移(未展示其完整实现,但其在连接超时、MTU 超时、断开兜底等消息中被反复调用):
stateDiagram-v2
[*] --> IDLE: 扫描发现/系统同步
IDLE --> CONNECTING: connectBleDevice
CONNECTING --> CONNECTED: onConnectionStateChange(CONNECTED) / MTU 协商完成
CONNECTING --> IDLE: 连接失败 / 配对失败 / 连接超时(40s)
CONNECTED --> CONNECTED: 服务发现超时(needReconnect=true)
CONNECTED --> IDLE: disconnectBleDevice / 通知超时(6s) / 蓝牙关闭
IDLE --> CONNECTING: 自动重连 (延迟2s)
状态迁移的约束:同一时刻最多一个设备处于 CONNECTING(getConnectingDevice() 遍历 Map 查找 bleDevice.isConnecting());mUsingDevice(正在通讯设备)在 removeConnectedBle() 中被维护——当连接列表清空时置 null,否则切换到按连接时间倒序排序后的第一个设备(BleManager.java):
private BleDevice removeConnectedBle(String address) {
if (!BluetoothAdapter.checkBluetoothAddress(address)) return null;
BleDevice bleDevice = mBleDeviceMap.remove(address);
...
if (null == bleDevice) return null;
bleDevice.stopSendDataThread();
final List<BluetoothDevice> connectedDeviceList = getConnectedDeviceList();
...
if (connectedDeviceList.isEmpty()) {
setConnectedBtDevice(null);
} else if (BluetoothUtil.deviceEquals(bleDevice.getDevice(), getConnectedBtDevice())) {
setConnectedBtDevice(connectedDeviceList.get(0));
}
return bleDevice;
}
Source: BleManager.java
设备数据模型
classDiagram
class BleManager {
-mBleDeviceMap Map~String, BleDevice~
-mUsingDevice BluetoothDevice
-mDiscoveredBleDevices List~BluetoothDevice~
-mCallbackManager BleEventCallbackManager
-mBluetoothGattCallback BluetoothGattCallback
+connectBleDevice(device, transport, mtu) boolean
+disconnectBleDevice(device) void
+getConnectedDeviceList() List~BluetoothDevice~
+isConnectedDevice(address) boolean
}
class BleDevice {
-device BluetoothDevice
-gatt BluetoothGatt
-connection int
-mtu int
-requestMtu int
-transport int
-needReconnect boolean
-sendDataThread SendBleDataThread
+isATTDevice() boolean
+isConnecting() boolean
+setConnection(state) void
}
class BleEventCallbackManager {
+registerBleEventCallback(cb) void
+unregisterBleEventCallback(cb) void
+onBleConnection(device, state) void
+onDiscoveryState(scanning) void
+release() void
}
class IBleOp {
<<interface>>
+getBleMtu() int
+writeDataByBle(gatt, serviceUUID, characteristicUUID, data) boolean
}
BleManager "1" *-- "many" BleDevice : 管理
BleManager "1" *-- "1" BleEventCallbackManager : 持有
BleEventCallbackManager --> BleEventCallback : 分发
BleManager ..|> IBleOp : 实现(如适用)
IBleOp 是数据通讯操作的抽象接口(IBleOp.java):
public interface IBleOp {
int getBleMtu();
boolean writeDataByBle(BluetoothGatt gatt, UUID serviceUUID, UUID characteristicUUID, byte[] data);
}
Source: IBleOp.java
Usage Examples
1. 注册连接事件回调
上层页面通过 registerBleEventCallback 订阅连接事件,在 onBleConnection 中根据 BluetoothProfile.STATE_* 刷新 UI(BleManager.java):
public void registerBleEventCallback(BleEventCallback callback) {
mCallbackManager.registerBleEventCallback(callback);
}
public void unregisterBleEventCallback(BleEventCallback callback) {
mCallbackManager.unregisterBleEventCallback(callback);
}
Source: BleManager.java
回调在页面 onDestroy 时必须注销,避免泄漏;BleEventCallbackManager.release() 会在 BleManager.destroy() 时清理全部回调。
2. 发起连接
传统 BLE 设备(LE transport)与 ATT/双模设备(BR/EDR transport)使用同一入口,区别只在 transport 参数(BleManager.java):
// 传统 BLE:默认 LE transport + Config.REQUEST_BLE_MTU
bleManager.connectBleDevice(device);
// 显式指定 transport 与 MTU(如双模 ATT 设备走 BR/EDR)
bleManager.connectBleDevice(device, BleManager.TRANSPORT_BREDR, 247);
Source: BleManager.java
注意:使用 TRANSPORT_BREDR 时设备必须是双模设备且已配对(connectBleDevice 内部会自动触发 createBond);BR/EDR 通道不支持 MTU 调整。
3. 主动断开与状态查询
// 主动断开:已连接状态走 gatt.disconnect() + 6s 兜底;连接中状态立即 close
bleManager.disconnectBleDevice(device);
// 状态查询:判断设备是否在已连接列表中
boolean connected = bleManager.isConnectedDevice(device.getAddress());
// 获取按连接时间倒序的已连接设备列表
List<BluetoothDevice> devices = bleManager.getConnectedDeviceList();
Source: BleManager.java
4. 资源释放
BleManager.destroy() 在 App 退出时调用:注销广播、停止连接超时、断开并清空所有设备、停止扫描、释放回调管理器与 Handler 消息(BleManager.java):
public void destroy() {
JL_Log.w(TAG, "destroy", "instance : " + instance);
unregisterReceiver();
stopConnectTimeout();
clearConnectedBleDevices();
if (isBleScanning()) stopLeScan();
isBleScanning(false);
mDiscoveredBleDevices.clear();
mCallbackManager.release();
mHandler.removeCallbacksAndMessages(null);
instance = null;
}
Source: BleManager.java
Configuration Options
连接管理相关的可配置项集中在 Config(com.jieli.bt.att.data.constant.Config)与 BleManager 内部常量中:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Config.getBLE_SERVICE_UUID() | UUID | 业务定义 | BLE 服务 UUID,连接后服务发现的筛选基准 |
Config.getBLE_WRITE_UUID() | UUID | 业务定义 | 写特征 UUID(BLE_UUID_WRITE) |
Config.getBLE_NOTIFY_UUID() | UUID | 业务定义 | 通知特征 UUID(BLE_UUID_NOTIFICATION) |
Config.REQUEST_BLE_MTU | int | 业务定义 | 连接成功后请求的 MTU,范围 [20, 509] |
BleManager.TRANSPORT_AUTO/BREDR/LE | int | 0/1/2 | connectGatt 的传输层选择 |
CONNECT_BLE_TIMEOUT | int | 40s | 连接阶段最大等待时间,超时自动断开 |
CALLBACK_TIMEOUT | int | 6s | gatt.disconnect() 后等待系统回调的兜底时间 |
BOUND_TIMEOUT | int | 30s | ATT 设备配对等待上限 |
SCAN_BLE_TIMEOUT | int | 12s | 扫描自动停止时间 |
RECONNECT_BLE_DELAY | int | 2s | 服务发现失败后的自动重连延迟 |
BLE_UUID_NOTIFICATION_DESCRIPTOR | UUID | 00002902-...-00805F9B34FB | CCCD 描述符 UUID,通知使能写入目标 |
运行时开关(无 UI 配置项,由代码路径控制):
- 是否启用自动重连:由
BleDevice.needReconnect标记,服务发现超时且无服务缓存时置位; - 是否允许并发连接:不允许——
connectBleDevice在已有 connecting 设备时拒绝新连接; - 蓝牙权限:
checkScanEnv要求扫描权限 + 定位权限;checkConnectEnv要求连接权限(BleManager.java)。
API Reference
以下为 BleManager 中与连接/断开管理直接相关的公开 API(签名以源码为准)。
getInstance(): BleManager
返回全局单例。首次调用时以 MyApplication 的 Context 初始化,注册蓝牙开关广播。
Throws: RuntimeException — 传入 null Context 时(仅构造路径)。
connectBleDevice(BluetoothDevice device, int transport, int mtu): boolean
发起 GATT 连接的主入口(含配对前置、超时启动、STATE_CONNECTING 回调)。
参数:
device(BluetoothDevice): 目标设备,由扫描或系统同步获得transport(int):TRANSPORT_AUTO/TRANSPORT_BREDR/TRANSPORT_LEmtu(int): 请求的 MTU 值,范围 [20, 509](BR/EDR 不支持)
返回: true 表示连接流程已启动(或设备已连接);false 表示权限缺失、设备为空、蓝牙关闭、connectGatt 失败。
重载: connectBleDevice(device, transport) 使用 Config.REQUEST_BLE_MTU;connectBleDevice(device) 等价于 LE transport。
disconnectBleDevice(BluetoothDevice device): void
按状态分支执行断开:已连接 → gatt.disconnect() + 6s 兜底;连接中 → 立即 close 并回调断开;其他 → 移除并补发回调。
isConnectedDevice(BluetoothDevice device) / isConnectedDevice(String address): boolean
判断设备是否处于已连接列表(遍历 getConnectedDeviceList() 按地址比对)。
getConnectedDeviceList(): List<BluetoothDevice>
返回按连接时间倒序的已连接设备列表;内部过滤 connection != STATE_CONNECTED 或无 Gatt 的条目。
getConnectedBleDeviceList(Context context): List<BluetoothDevice>(静态)
通过 BluetoothManager.getConnectedDevices(BluetoothProfile.GATT) 查询系统级已连接设备,需要连接权限。
getConnectedBtDevice(): BluetoothDevice
返回当前正在通讯的设备 mUsingDevice(唯一活动通讯对象)。
getConnectedBtGatt(BluetoothDevice device): BluetoothGatt
从 mBleDeviceMap 取出对应 BleDevice 并返回其 Gatt;未登记返回 null。
getBleMtu(BluetoothDevice device): int
返回设备当前协商后的 MTU(未连接返回 0)。
isConnecting(): boolean / isConnectingDevice(BluetoothDevice device): boolean
是否存在处于连接中的设备;以及指定设备是否正是连接中的那一个(BluetoothUtil.deviceEquals 比较)。
registerBleEventCallback(BleEventCallback callback) / unregisterBleEventCallback(...)
注册/注销连接事件回调;destroy() 时由 BleEventCallbackManager.release() 统一清理。
startLeScan(long timeout): boolean / stopLeScan(): boolean
扫描控制。连接管理相关行为:connectBleDevice 在扫描中会自动停止扫描;扫描超时消息 MSG_SCAN_BLE_TIMEOUT(12s 默认)自动停止。
writeDataByBleAsync(BluetoothDevice device, UUID serviceUUID, UUID characteristicUUID, byte[] data, OnWriteDataCallback callback): void
异步写数据入口(数据发送队列属于"数据透传"页范围,此处仅列出连接层暴露的入口)。
destroy(): void
释放全部连接资源并将单例置空。
Failure Modes, Edge Cases & Concurrency
故障模式与降级策略
| 故障场景 | 检测方式 | 系统行为 | 设计意图 |
|---|---|---|---|
| 连接 40s 无回调 | MSG_CONNECT_BLE_TIMEOUT | 自动 disconnectBleDevice | 系统回调丢失时不让 UI 永久停留在"连接中" |
connectGatt 返回 null / 抛异常 | 返回值检查 + try-catch | 回调 STATE_DISCONNECTED | 资源异常立即暴露,避免悬挂 |
| 断开后系统回调 6s 未达 | MSG_DISCONNECT_BLE_TIMEOUT | closeGatt() + 强制回调断开 | 部分 ROM 的 onConnectionStateChange 缺失问题 |
| MTU 协商超时/失败 | MSG_CHANGE_BLE_MTU_TIMEOUT | MTU 降级为 20,仍按已连接处理 | 宽容处理,不因 MTU 失败中断连接 |
| 服务发现超时且无服务 | MSG_BLE_DISCOVER_SERVICES_CALLBACK_TIMEOUT | needReconnect=true + 断开(2s 后重连) | 有缓存服务可补发回调;无缓存则重连恢复 |
| ATT 设备未配对 | getBondState() 检查 | createBond() + 30s 配对超时 | BR/EDR 连接必须前置配对;正在配对时等待结果 |
| 配对失败 | createBond 返回 false | 移除超时消息 + 回调断开 | 失败路径即时收敛 |
| 蓝牙被用户关闭 | 蓝牙开关广播 + isBluetoothEnable() | 强制置 STATE_DISCONNECTED,断开路径不再依赖系统回调 | 系统在蓝牙关闭时会断开所有 GATT,必须同步内部状态 |
| 通知使能超时 | MSG_NOTIFY_BLE_TIMEOUT | 断开设备 | 无法收数据时连接无意义,宁可重连 |
| 重复连接同一已连接设备 | isConnectedDevice | 补发 STATE_CONNECTED 回调并返回 true | 幂等,避免重复建连导致多 Gatt 实例 |
边界情况
- 空设备:
connectBleDevice/disconnectBleDevice均先经checkConnectEnv拒绝 null 设备(BleManager.java); - 非法地址:
getConnectedBLEDevice/removeConnectedBle使用BluetoothAdapter.checkBluetoothAddress校验地址格式; - 已连接列表为空:
removeConnectedBle将mUsingDevice置 null;否则切换到排序后第一个设备,保证"正在通讯设备"指针始终有效; - 扫描与连接互斥:连接前停止扫描;扫描中调用
startLeScan会 flush 并重置超时,防止重复扫描器; - 多设备连接:同一时刻仅允许一个 connecting 设备;但已连接设备允许多个(Map 按地址存储),
mUsingDevice指向当前活动通讯设备。
并发与一致性
- 单例字段(
instance、mUsingDevice、isBleScanning)使用volatile保证可见性; - 所有超时消息与回调分发都发生在主线程
Handler(Looper.getMainLooper())上,onConnectionStateChange等 GATT 回调本质也在主线程派发(Android 默认),因此状态读写天然串行化,无需额外加锁; mBleDeviceMap/mDiscoveredBleDevices为普通HashMap/ArrayList,仅在主线程路径访问;clearConnectedBleDevices()通过克隆 Map 迭代,避免disconnectBleDevice中removeConnectedBle修改集合导致ConcurrentModificationException;destroy()与业务页面生命周期交错时,instance=null后新页面调用getInstance()会重建实例——旧 Handler 消息已通过removeCallbacksAndMessages(null)清空,避免悬挂消息操作已销毁状态。
Performance & Operational Considerations
- 扫描参数:
SCAN_MODE_LOW_LATENCY+MATCH_MODE_AGGRESSIVE+MATCH_NUM_MAX_ADVERTISEMENT,Android 8.0+ 使用PHY_LE_ALL_SUPPORTED,以最快速度发现设备,代价是功耗较高,因此默认 12s 自动停止(BleManager.java); - 超时聚合:
startConnectTimeout用hasMessages防止重复投递 40s 连接超时,避免 Handler 队列堆积; - 资源回收:
disconnectBleDevice已连接路径只disconnect()不close(),把 Gatt 关闭交给系统回调后的清理逻辑;连接中路径才立即closeGatt()——这平衡了"复用连接"与"及时释放"; - 日志:全程使用
JL_Log输出设备地址、状态与关键决策点,线上定位连接问题时以BleManager的 TAG 日志为主线排查; - 权限前置:扫描需要定位权限,连接需要
BLUETOOTH_CONNECT权限,所有入口先做权限检查再执行系统调用,避免系统抛SecurityException。
Extension Points
BleEventCallback/IBleEventCallback:连接状态、扫描状态、设备列表变化的统一事件出口。新增业务(如弹窗提示断连、自动重连策略)通过实现该接口并注册即可,无需改动BleManager内部逻辑;IBleOp:数据读写能力的抽象接口(getBleMtu/writeDataByBle),便于替换底层 BLE 实现或注入测试替身;- transport 参数:
connectBleDevice的 transport 入参天然支持未来新增的双模/经典设备接入方式; BleDevice.isATTDevice():设备类型判定点,扩展新设备类型(如 HID over GATT)时在此扩展类型路由逻辑;Config常量:服务/写/通知 UUID 与请求 MTU 集中在Config,适配不同固件协议时只需调整配置,无需修改 Manager。
Related Links
- BleManager.java — 本文核心:连接/断开/超时/多设备管理
- IBleOp.java — 数据读写抽象接口
- BleEventCallbackManager.java — 回调注册与分发
- BleDevice.java — 设备状态容器(连接状态/MTU/发送线程)
- BluetoothUtil.java — 配对、设备比较、系统设备同步工具
- 同级目录:
4.2-device-connection下的 BLE 扫描与发现页、数据透传页(SendBleDataThread)分别覆盖扫描与通讯细节