Android双蓝牙应用需求:长UUID分拆写入与连接状态感知
嘿,针对你这个Android蓝牙服务端的需求,我刚好有过类似的实现经验,给你拆解成两个核心模块来解决:在线状态监测和长UUID的拆分接收,具体方案如下:
一、客户端在线状态监测
要让服务端实时知晓客户端的在线状态,我推荐用「连接回调+心跳兜底」的组合方案:
- 利用GATT连接状态回调:当客户端发起连接或断开时,服务端的
BluetoothGattServerCallback会触发onConnectionStateChange方法。你可以在这里维护一个全局的状态标记(比如isClientConnected),同时记录连接的客户端设备信息,方便后续业务逻辑处理。 - 心跳机制兜底:有时候蓝牙连接可能因为信号弱、系统回收等异常情况断开,但回调没及时触发。这时候可以让客户端定期(比如30秒)向服务端发送一个专门的「心跳特征值」,服务端记录最后一次收到心跳的时间,一旦超过超时时间(比如30秒)就标记客户端为离线。
二、长UUID拆分接收与重组
因为128位UUID无法一次性写入单个特征,拆分到两个write characteristic是合理的方案,具体实现要点:
- 定义两个专属特征:分别对应UUID的前64位(8字节)和后64位(8字节),都设置为
PROPERTY_WRITE和PERMISSION_WRITE权限。 - 缓冲区存储与重组触发:服务端维护两个字节数组缓冲区,分别存储收到的前半段和后半段数据。每次收到任意一段写入后,检查两个缓冲区是否都已填满,若已填满则合并字节数组并转换为UUID对象。
- 字节序一致性:务必和客户端约定好字节序(比如大端序),不然重组出来的UUID会完全错误。另外可以加简单的长度校验,确保每个特征收到的都是8字节数据,避免非法写入干扰。
三、核心代码示例
1. GATT服务与特征初始化
// 替换成你自己的UUID值 private static final UUID SERVICE_UUID = UUID.fromString("你的广播服务UUID"); private static final UUID CHAR_UUID_PART1 = UUID.fromString("用于存储UUID前半段的特征UUID"); private static final UUID CHAR_UUID_PART2 = UUID.fromString("用于存储UUID后半段的特征UUID"); private static final UUID CHAR_HEARTBEAT = UUID.fromString("心跳特征UUID"); private BluetoothGattServer mGattServer; private boolean isClientConnected = false; private byte[] uuidPart1 = null; private byte[] uuidPart2 = null; private long lastHeartbeatTime; private static final long HEARTBEAT_TIMEOUT = 30 * 1000; // 30秒超时 private Handler mHeartbeatHandler = new Handler(Looper.getMainLooper()); // 初始化GATT服务 private void initGattServer() { mGattServer = BluetoothManager.getDefaultAdapter().openGattServer(this, mGattServerCallback); if (mGattServer == null) { Log.e(TAG, "创建GATT服务器失败"); return; } // 创建主服务 BluetoothGattService service = new BluetoothGattService(SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY); // 添加UUID前半段特征 BluetoothGattCharacteristic charPart1 = new BluetoothGattCharacteristic( CHAR_UUID_PART1, BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_WRITE ); service.addCharacteristic(charPart1); // 添加UUID后半段特征 BluetoothGattCharacteristic charPart2 = new BluetoothGattCharacteristic( CHAR_UUID_PART2, BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_WRITE ); service.addCharacteristic(charPart2); // 添加心跳特征 BluetoothGattCharacteristic charHeartbeat = new BluetoothGattCharacteristic( CHAR_HEARTBEAT, BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_WRITE ); service.addCharacteristic(charHeartbeat); mGattServer.addService(service); }
2. GATT服务器回调实现
private BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() { @Override public void onConnectionStateChange(BluetoothDevice device, int status, int newState) { super.onConnectionStateChange(device, status, newState); if (newState == BluetoothProfile.STATE_CONNECTED) { isClientConnected = true; lastHeartbeatTime = System.currentTimeMillis(); // 重置UUID缓冲区 uuidPart1 = null; uuidPart2 = null; Log.d(TAG, "客户端[" + device.getName() + "]已连接"); // 启动心跳监测 mHeartbeatHandler.postDelayed(mHeartbeatRunnable, HEARTBEAT_TIMEOUT); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { isClientConnected = false; mHeartbeatHandler.removeCallbacks(mHeartbeatRunnable); Log.d(TAG, "客户端[" + device.getName() + "]已断开"); } } @Override public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value); // 响应客户端写入请求(必须返回,否则客户端可能会重试) mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null); UUID charUuid = characteristic.getUuid(); if (charUuid.equals(CHAR_UUID_PART1)) { if (value.length == 8) { // 校验长度 uuidPart1 = value.clone(); checkAndReconstructUuid(); } else { Log.w(TAG, "UUID前半段长度错误,预期8字节,实际收到" + value.length + "字节"); } } else if (charUuid.equals(CHAR_UUID_PART2)) { if (value.length == 8) { uuidPart2 = value.clone(); checkAndReconstructUuid(); } else { Log.w(TAG, "UUID后半段长度错误,预期8字节,实际收到" + value.length + "字节"); } } else if (charUuid.equals(CHAR_HEARTBEAT)) { lastHeartbeatTime = System.currentTimeMillis(); Log.d(TAG, "收到客户端心跳"); } } }; // 检查并重组完整UUID private void checkAndReconstructUuid() { if (uuidPart1 != null && uuidPart2 != null) { byte[] fullUuidBytes = new byte[16]; System.arraycopy(uuidPart1, 0, fullUuidBytes, 0, 8); System.arraycopy(uuidPart2, 0, fullUuidBytes, 8, 8); UUID fullUuid = bytesToUuid(fullUuidBytes); Log.d(TAG, "重组完成,完整UUID:" + fullUuid.toString()); // 这里处理完整UUID的业务逻辑 handleReceivedUuid(fullUuid); // 重置缓冲区 uuidPart1 = null; uuidPart2 = null; } } // 字节数组转UUID(大端序) private UUID bytesToUuid(byte[] bytes) { long msb = 0; long lsb = 0; for (int i = 0; i < 8; i++) { msb = (msb << 8) | (bytes[i] & 0xff); } for (int i = 8; i < 16; i++) { lsb = (lsb << 8) | (bytes[i] & 0xff); } return new UUID(msb, lsb); } // 心跳监测任务 private Runnable mHeartbeatRunnable = new Runnable() { @Override public void run() { if (isClientConnected) { if (System.currentTimeMillis() - lastHeartbeatTime > HEARTBEAT_TIMEOUT) { isClientConnected = false; Log.d(TAG, "客户端心跳超时,标记为离线"); } else { mHeartbeatHandler.postDelayed(this, HEARTBEAT_TIMEOUT); } } } }; // 处理收到的完整UUID private void handleReceivedUuid(UUID uuid) { // 这里写你的业务逻辑,比如存储、校验、触发其他操作等 }
额外提示
- 记得在AndroidManifest中添加蓝牙权限:
BLUETOOTH、BLUETOOTH_ADMIN、ACCESS_FINE_LOCATION(Android 12及以上需要BLUETOOTH_SCAN、BLUETOOTH_ADVERTISE、BLUETOOTH_CONNECT权限)。 - 服务端的GATT服务器要在合适的生命周期(比如Activity的onCreate)初始化,onDestroy时关闭:
mGattServer.close()。
内容的提问来源于stack exchange,提问作者Drake
相关产品推荐
相关产品推荐

