Android BLE 开发实战:连接 GATT 服务器与 20 字节数据分包传输
Android BLE 开发实战:连接 GATT 服务器与 20 字节数据分包传输
在智能穿戴设备和物联网终端普及的今天,低功耗蓝牙(BLE)已成为移动开发者必须掌握的核心技术之一。与经典蓝牙相比,BLE 在保持稳定通信的同时显著降低了能耗,这使得它成为心率监测器、智能门锁等需要长时间运行的设备的理想选择。本文将深入探讨 Android 平台上 BLE 开发的关键环节,特别是如何高效连接 GATT 服务器并处理 20 字节 MTU 限制下的数据传输挑战。
1. BLE 开发基础与环境准备
1.1 权限声明与蓝牙适配器
在 AndroidManifest.xml 中声明以下权限是 BLE 开发的第一步:
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <!-- Android 6.0+ 需要位置权限进行设备扫描 --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>对于 Android 12 及以上版本,权限系统更加细化:
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"/> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>获取 BluetoothAdapter 的实例:
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); if (bluetoothAdapter == null) { // 设备不支持蓝牙 return; }1.2 设备扫描与过滤
使用 BluetoothLeScanner 进行设备扫描时,可以通过 ScanFilter 提高效率:
BluetoothLeScanner scanner = bluetoothAdapter.getBluetoothLeScanner(); List<ScanFilter> filters = new ArrayList<>(); filters.add(new ScanFilter.Builder() .setServiceUuid(ParcelUuid.fromString(SERVICE_UUID)) .build()); ScanSettings settings = new ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) .build(); scanner.startScan(filters, settings, scanCallback);关键参数对比:
| 扫描模式 | 功耗 | 发现速度 | 适用场景 |
|---|---|---|---|
| SCAN_MODE_LOW_POWER | 低 | 慢 | 后台持续扫描 |
| SCAN_MODE_BALANCED | 中 | 中 | 一般应用 |
| SCAN_MODE_LOW_LATENCY | 高 | 快 | 需要快速响应的场景 |
2. GATT 连接与服务发现
2.1 建立 GATT 连接
发现目标设备后,通过 BluetoothDevice 对象建立连接:
BluetoothGatt gatt = device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE);连接参数说明:
autoConnect设为 false 可加快连接速度TRANSPORT_LE确保使用低功耗传输模式
2.2 服务发现流程
连接成功后,在 BluetoothGattCallback 中处理服务发现:
private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { gatt.discoverServices(); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { List<BluetoothGattService> services = gatt.getServices(); // 处理发现的服务 } } };典型服务发现时间:
- 简单设备:100-300ms
- 复杂设备(多服务):500ms-1s
3. 数据传输与 MTU 处理
3.1 20 字节限制的挑战
BLE 协议默认 MTU 为 23 字节,扣除 ATT 头部的 3 字节后,实际有效载荷仅剩 20 字节。这意味着:
- 单次传输数据量有限
- 大文件传输需要分包机制
- 需要处理数据重组和流量控制
3.2 MTU 协商优化
Android 5.0+ 支持 MTU 协商,可提升单次传输量:
gatt.requestMtu(247); // 请求最大支持的MTU在回调中处理结果:
@Override public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { Log.d(TAG, "MTU changed to: " + mtu); } }MTU 大小对比:
| MTU 大小 | 有效载荷 | 适用场景 |
|---|---|---|
| 23 (默认) | 20 字节 | 兼容所有设备 |
| 158 | 155 字节 | 平衡兼容性与效率 |
| 247 (最大) | 244 字节 | 高性能设备 |
3.3 数据分包与重组
当传输数据超过 MTU 限制时,需要实现分包机制。以下是发送端的示例:
public void sendLargeData(BluetoothGattCharacteristic characteristic, byte[] data) { int chunkSize = 20; // 默认MTU限制 int offset = 0; while (offset < data.length) { int remaining = data.length - offset; int length = Math.min(remaining, chunkSize); byte[] chunk = Arrays.copyOfRange(data, offset, offset + length); characteristic.setValue(chunk); gatt.writeCharacteristic(characteristic); offset += length; // 添加适当延迟避免堵塞 SystemClock.sleep(10); } }接收端需要实现数据重组逻辑:
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { byte[] chunk = characteristic.getValue(); outputStream.write(chunk, 0, chunk.length); // 根据协议判断是否接收完成 if (isPacketComplete(outputStream.toByteArray())) { byte[] completeData = outputStream.toByteArray(); processCompleteData(completeData); outputStream.reset(); } }4. 性能优化与异常处理
4.1 连接参数优化
BLE 的连接间隔(Connection Interval)直接影响功耗和吞吐量:
// Android 8.0+ 支持连接参数更新 gatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);连接参数对比:
| 优先级 | 连接间隔 | 延迟 | 功耗 |
|---|---|---|---|
| HIGH | 7.5-15ms | 低 | 高 |
| BALANCED | 30-50ms | 中 | 中 |
| LOW | 100-125ms | 高 | 低 |
4.2 常见问题排查
连接失败处理:
- 检查蓝牙是否已开启
- 验证权限是否已授予
- 确认设备是否在范围内
- 尝试重启蓝牙适配器
数据传输稳定性优化:
- 添加重试机制
- 实现数据校验(如 CRC)
- 使用通知(Notification)代替读取
- 避免在主线程执行蓝牙操作
4.3 资源释放
正确释放资源避免内存泄漏:
@Override protected void onDestroy() { if (gatt != null) { gatt.disconnect(); gatt.close(); } if (scanner != null) { scanner.stopScan(scanCallback); } super.onDestroy(); }5. 实战:健康设备数据采集案例
以心率监测器为例,演示完整的数据采集流程:
5.1 服务与特征值定义
// 心率服务UUID private static final UUID HEART_RATE_SERVICE = UUID.fromString("0000180D-0000-1000-8000-00805f9b34fb"); // 心率测量特征值UUID private static final UUID HEART_RATE_MEASUREMENT = UUID.fromString("00002A37-0000-1000-8000-00805f9b34fb");5.2 启用通知
BluetoothGattService hrService = gatt.getService(HEART_RATE_SERVICE); BluetoothGattCharacteristic hrChar = hrService .getCharacteristic(HEART_RATE_MEASUREMENT); // 启用通知 gatt.setCharacteristicNotification(hrChar, true); // 写入描述符启用通知 BluetoothGattDescriptor descriptor = hrChar .getDescriptor(CLIENT_CHARACTERISTIC_CONFIG); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); gatt.writeDescriptor(descriptor);5.3 数据处理
@Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { if (HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) { byte[] data = characteristic.getValue(); int flags = data[0]; int heartRate; if ((flags & 0x01) != 0) { // 16位心率值 heartRate = (data[1] & 0xFF) + ((data[2] & 0xFF) << 8); } else { // 8位心率值 heartRate = data[1] & 0xFF; } updateHeartRateDisplay(heartRate); } }在实际项目中遇到的最棘手问题往往是不同厂商对蓝牙协议实现的差异。某次开发中,我们发现华为设备在连接某些BLE设备时会出现间歇性断开,最终通过调整连接间隔和超时设置解决了这个问题。这种经验告诉我们,蓝牙开发不能仅停留在API调用层面,还需要深入理解协议细节和设备特性。