You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:15:15