Android 低功耗蓝牙(BLE)如何处理包大小(MTU)限制
一、概述
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;
}