学而实习之 不亦乐乎

Android 低功耗蓝牙(BLE)如何处理包大小(MTU)限制

2024-03-31 17:07:40

一、概述

Bluetooth low energy 在给 remote 设备发送数据时,一帧数据大小是有一定限制的,主要是 MTU 的大小,因此发送数据较大时,可以先获取 MTU 的大小,再根据大小做适当处理。实际发送数据的大小需要减去 3 字节的帧控制字段。

1、最大传输单元(Maximum Transmission Unit, MTU)

MTU是指在一个协议数据单元中(Protocol Data Unit, PDU) 有效的最大传输Byte。

2、蓝牙MTU默认23个Byte

默认 MTU 为23个Byte,ATT 的 Opcode 占1个Byte、ATT 的 Handle 占2个Byte、GATT占20个Byte。即:
23Byte(ATT)=1Byte(Opcode)+2Byte(Handler)+20Byte(BATT)。

3、蓝牙最大MTU

不同的蓝牙版本最大 MTU 不同,例如:蓝牙4.2的最大MTU=247Byte(不一定正确,也有说是257Byte、也有说是241Byte),蓝牙5.0的最大MTU=512,有效的最大MTU还需要减去协议Byte、Opcode和Handler。蓝牙4.2的有效的最大MTU是244Byte(不一定正确)。
蓝牙4.2:1Byte(Opcode)+2Byte(Handler)+244Byte(BATT)=247Byte(不一定正确);

二、自定义 MTU 

不是说蓝牙协议中MTU定义了多少,移动设备就能用多少,不同的移动设备的蓝牙支持的最大有效的MTU不同,例如:有些移动可能仅仅支持默认的MTU=23Byte,也可能仅仅 MTU=185(例如苹果)。

在实际开发过程中,需要根据实际需求去选择相应的移动设备,然后根据实际的需要去修改MTU,自定义的 MTU 不能大于最大的 MTU 值。此外,自定义的MTU不是随意设置的,自定义的 MTU 值不同蓝牙的性能可能不同,例如:自定义的 MTU=185 的蓝牙性能,可能会比自定义的 MTU=153 的性能好。最好根据实际情况多测几个数据。

在Android中修改MTU很简单只需要调用 BluetoothGatt.requestMtu(int MTU)方法即可。requestMtu(intMTU)必须在发现蓝牙服务并建立蓝牙服务连接之后才能调用,否则MTU会默认为20Byte。如果调用成功会自定回调 BluetoothGattCallback.onMtuChanged(BluetoothGatt gatt, int mtu, int status)方法。

// 重写MTU
private boolean requestMtu() {
    if (bleGatt != null && Build.VERSION.SDK_INT >=Build.VERSION_CODES.LOLLIPOP) {
        // 25 *6 +3 =153
        return bleGatt.requestMtu(this.bleMtu);
    }
    return false;
}

@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
    super.onMtuChanged(gatt, mtu, status);
    Log.d("onMtuChanged"," onMtuChanged");
    if (BluetoothGatt.GATT_SUCCESS == status) {
        Log.d("BleService", "onMtuChanged success MTU = " + mtu);
    }else {
        Log.d("BleService", "onMtuChanged fail ");
    }
}

三、分包发送

若无法自定义 MTU,那么每次只能发送20字节,这就需要做分包处理,并要注意分包发送需要间隔一两百毫秒。可以参照如下的思路分包:
 

protected Queue<DataInfo> splitPacketFor20Byte(byte[] data) {
    Queue<DataInfo> dataInfoQueue = new LinkedList<>();
    if (data != null) {
        int index = 0;
        do {
            byte[] surplusData = new byte[data.length - index];
            byte[] currentData;
            System.arraycopy(data, index, surplusData, 0, data.length - index);
            if (surplusData.length <= 20) {
                currentData = new byte[surplusData.length];
                System.arraycopy(surplusData, 0, currentData, 0, surplusData.length);
                index += surplusData.length;
            } else {
                currentData = new byte[20];
                System.arraycopy(data, index, currentData, 0, 20);
                index += 20;
            }
            DataInfo dataInfo = new DataInfo();
            dataInfo.setData(currentData);
            dataInfo.setDataType(DataInfo.DataType.SEND);
            dataInfoQueue.offer(dataInfo);
        } while (index < data.length);
    }
    return dataInfoQueue;
}