首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

【Android蓝牙-六】蓝牙数据通信机制详解:GATT与ATT服务的技术实现

  • 25-04-23 20:41
  • 4435
  • 5128
juejin.cn

一、蓝牙协议栈结构与数据流

你是否曾好奇,为什么同样是BLE通信,有些应用速度快、稳定性高,而有些却时常掉线、传输慢?这一切都与蓝牙协议栈的结构和数据流通路径密切相关。

在深入GATT通信机制前,先快速理解BLE协议栈中数据如何流转:

img 当你的数据从应用程序发出,它会被打包成特征值写入请求,通过GATT/ATT层处理,由L2CAP层分段封装,最终通过链路层和物理层发送出去。这个流程决定了通信的效率、可靠性和功耗表现。

二、从实际问题出发:为什么需要理解GATT?

场景1:传数据延时高

场景2:传数据失败率高

场景3:多设备连接
你的应用需要同时连接6个BLE设备采集数据,但总是有设备丢失连接。

这些典型问题,本质上都是GATT通信机制使用不当导致的。通过本文,你将了解如何:

  • 优化MTU提升数据吞吐量
  • 选择正确的读写特征方式
  • 合理设计服务和特征结构
  • 解决多设备并发连接问题

三、GATT通信架构:服务、特征与描述符

GATT基本概念:服务器与客户端

GATT(Generic Attribute Profile)是低功耗蓝牙(BLE)的核心通信框架,它基于简单的客户端-服务器架构:

GATT客户端是发起请求的一方,GATT服务端是响应请求、持有数据的一方。

具体角色定义:

角色职责举例
服务端(Server)持有一个或多个服务(Service),每个服务包含特征(Characteristic)和描述符(Descriptor),等待客户端访问其数据。BLE心率带、BLE温度传感器等
客户端(Client)通过读取、写入、订阅等方式访问服务端的特征值,实现数据交互。手机App、蓝牙主机

GATT的三层数据结构

为了高效组织 BLE 设备中的数据,GATT 使用了三层结构:服务(Service)、特征(Characteristic)和描述符(Descriptor) 。我们可以将它类比成一个图书馆的管理体系:

类比说明:

  • 服务(Service) :就像图书馆的一个部门(例如“医学区”、“文学区”)
  • 特征(Characteristic) :每个部门的具体书籍(例如“心脏病学入门”)
  • 描述符(Descriptor) :附在书上的标签或说明(出版信息、借阅规则)

image.png

实际案例:一个智能手环可能包含以下结构: image.png

这种层次结构使得BLE设备数据组织清晰、标准化,让不同厂商的设备可以互相理解并交互。

UUID:GATT世界的"身份证"

每个服务和特征都有一个UUID(通用唯一标识符)。UUID有两种形式:

  • 16位UUID:蓝牙SIG预定义的标准服务,如0x180D(心率服务)
  • 128位UUID:自定义服务,如"6E400001-B5A3-F393-E0A9-E50E24DCCA9E"

开发经验:尽量使用标准UUID。它不仅缩短了广播包长度(提高发现效率),还提升了与其他设备的互操作性。不确定是否有合适的标准UUID?查阅蓝牙官方GATT规范。

四、GATT通信流程与操作

连接与服务发现:打开通信之门

在任何GATT操作前,必须先建立连接并发现服务。这个过程实际上比大多数开发者想象的更复杂:

  1. 扫描设备:过滤目标设备并获取其广播数据
  2. 建立连接:通过MAC地址/UUID连接到设备
  3. 服务发现:获取设备提供的所有服务和特征

image.png

典型的Android服务发现代码:

typescript
代码解读
复制代码
private void discoverServices() { Log.d(TAG, "开始服务发现..."); if (bluetoothGatt != null) { boolean discovering = bluetoothGatt.discoverServices(); Log.d(TAG, "服务发现启动状态: " + discovering); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { Log.d(TAG, "服务发现完成!"); // 解析服务和特征 for (BluetoothGattService service : gatt.getServices()) { Log.d(TAG, "发现服务: " + service.getUuid()); for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) { Log.d(TAG, " 特征: " + characteristic.getUuid() + " 属性: " + characteristic.getProperties()); } } } else { Log.w(TAG, "服务发现失败,状态码: " + status); // 添加重试逻辑 } }

GATT 特征值读写:BLE 数据交换的核心机制

在 GATT 协议中,特征值的读取与写入是客户端与服务端交互的主要方式。理解不同读写机制的适用场景和性能特点,是 BLE 开发的关键。

读取操作:三种方式对比

BLE 提供了三种读取数据的方式,选择合适的方式将显著影响延迟与功耗:

读取方式适用场景优点缺点
直接读取 (Read)低频访问、静态数据简单直观每次都需请求,延迟高
通知 (Notification)高频变化数据,如传感器值主动推送、低延迟、低功耗不保证可靠送达(无确认机制)
指示 (Indication)安全关键数据,如健康指标等有确认机制,确保送达吞吐量低,通信效率有限

在心率等连续监测场景中,使用 通知 替代轮询读取,可显著降低功耗,延长电池续航。

启用通知的标准流程(代码实战)

要正确启用 Notification,必须完成 两步配置,其中写入 CCCD(客户端配置描述符)是最容易被忽略的一步:

java
代码解读
复制代码
public boolean enableNotifications(BluetoothGattCharacteristic characteristic) { BluetoothGatt gatt = getGatt(); if (gatt == null || characteristic == null) return false; // 第一步:启用本地通知能力 if (!gatt.setCharacteristicNotification(characteristic, true)) { Log.e(TAG, "启用本地通知失败"); return false; } // 第二步:写入 CCCD,告知服务端启用通知(或指示) BluetoothGattDescriptor cccd = characteristic.getDescriptor( UUID.fromString("00002902-0000-1000-8000-00805f9b34fb") ); if (cccd == null) { Log.e(TAG, "未找到 CCCD 描述符"); return false; } // 设置为通知(Notification)或指示(Indication) byte[] value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE; // 若需指示 → 使用 ENABLE_INDICATION_VALUE cccd.setValue(value); return gatt.writeDescriptor(cccd); }

常见错误:只调用 setCharacteristicNotification() 而未写入 CCCD,通知将永远不会生效!

2. 写入操作 - 性能与可靠性的权衡

BLE提供两种写入模式:

image.png

实战选择指南:

  • 发送控制命令、配置数据→带响应写入
  • 流式传输、实时数据→无响应写入

无响应写入配合应用层确认机制,可大幅提升传输效率:

scss
代码解读
复制代码
private final Queue[]> dataQueue = new LinkedList<>(); // 数据队列 private boolean isWriting = false; public void sendDataWithHighThroughput(byte[] data) { // 分包 List[]> packets = splitDataIntoPackets(data); // 添加到队列 dataQueue.addAll(packets); // 如果没有活跃写入,开始传输 if (!isWriting) { processQueue(); } } private void processQueue() { if (dataQueue.isEmpty()) { isWriting = false; return; } isWriting = true; byte[] packet = dataQueue.poll(); // 设置无响应写入模式 writeCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); writeCharacteristic.setValue(packet); // 无需等待回调,立即处理下一包 if (bluetoothGatt.writeCharacteristic(writeCharacteristic)) { // 短暂延迟以避免堵塞BLE堆栈 new Handler().postDelayed(this::processQueue, 5); } else { Log.e(TAG, "写入失败,重试..."); dataQueue.add(packet); // 放回队列 new Handler().postDelayed(this::processQueue, 100); } }

五、MTU优化:"为什么我一次只能传20字节?"

几乎每个BLE开发者都曾困惑:无论如何尝试,一次最多只能传输20字节数据。这是因为BLE默认MTU(Maximum Transmission Unit)为23字节,其中3字节用于ATT协议头,剩余20字节才是你的有效负载。

MTU协商:突破20字节限制

BLE 4.2以上版本支持协商更大的MTU,最高可达512字节,实际上这个取决于你的主从设备的实际支持情况,协商流程如下:

image.png

java
代码解读
复制代码
// 请求更大的MTU if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (bluetoothGatt.requestMtu(512)) { Log.d(TAG, "MTU请求已发送"); } else { Log.e(TAG, "MTU请求发送失败"); } } // MTU协商回调 @Override public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { Log.d(TAG, "MTU已更改为: " + mtu); // 实际可用数据长度 = mtu - 3 int maxDataLength = mtu - 3; Log.d(TAG, "最大数据负载: " + maxDataLength + " 字节"); } else { Log.e(TAG, "MTU更改失败: " + status); } }

关键事实:现代Android手机通常支持约512字节MTU,最终结果取决于你的主从设备的实际支持。总是在连接后协商并记录实际MTU,根据它调整传输策略。

MTU对性能的实际影响

在一个固件更新项目中,MTU对传输时间的影响(100KB文件):

  • MTU=23: 约180秒
  • MTU=247: 约45秒
  • MTU=512: 约25秒

提升 MTU 带来的两个隐患

尽管提高 MTU 看似“免费提速”,但以下两个问题不容忽视:

  1. 兼容性与堆栈负载

    • 部分 BLE 芯片或旧系统版本对大 MTU 支持不佳。
    • 高 MTU 会占用更多缓冲区资源,可能导致连接不稳定或崩溃。
  2. 出错代价上升

    • 数据包越大,传输出错时的重传代价越高。
    • 某些底层 BLE 控制器缺乏细粒度的错误恢复能力。

开发建议小结

  • 优先使用 requestMtu() 动态协商机制,避免硬编码 MTU。
  • 接收到的 MTU 回调才是真正生效值,发送端必须根据返回值切包。
  • 对于敏感数据流(如语音、图像),应结合分包与校验机制处理异常。

六、实战案例:心率监测应用的GATT设计与优化

现在,让我们将所学应用到实际案例中。假设我们开发一款心率监测应用,需要:

  1. 实时接收心率数据(1Hz频率)
  2. 获取历史心率记录(每5分钟一条)
  3. 配置报警阈值(心率过高/过低时通知)

服务与特征设计

ini
代码解读
复制代码
心率服务 (UUID: 0x180D) ├── 心率测量特征 (UUID: 0x2A37) │ ├── 属性: 通知 │ └── 值: [心率数据,1字节格式标志 + 1字节心率值] │ ├── 历史记录特征 (自定义UUID) │ ├── 属性: 读取、长特征值 │ └── 值: [时间戳 + 心率值的数组] │ └── 报警阈值特征 (自定义UUID) ├── 属性: 读取、写入 └── 值: [最低心率 + 最高心率]

通信流程优化

  1. 连接与服务发现

    java
    代码解读
    复制代码
    // 建立连接后首先设置MTU bluetoothGatt.requestMtu(512); // 在onMtuChanged回调中进行服务发现 @Override public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { // MTU设置成功,开始服务发现 bluetoothGatt.discoverServices(); } }
  2. 实时心率数据

    scss
    代码解读
    复制代码
    // 启用心率通知 BluetoothGattCharacteristic heartRateChar = heartRateService .getCharacteristic(UUID.fromString("00002A37-0000-1000-8000-00805f9b34fb")); enableNotifications(heartRateChar); // 处理通知数据 @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { if (UUID.fromString("00002A37-0000-1000-8000-00805f9b34fb") .equals(characteristic.getUuid())) { byte[] data = characteristic.getValue(); // 第一个字节是格式标志 boolean format = (data[0] & 0x01) != 0; int heartRate; if (format) { // 心率值是16位整数 heartRate = ((data[1] & 0xff) | ((data[2] & 0xff) << 8)); } else { // 心率值是8位整数 heartRate = data[1] & 0xff; } updateUI(heartRate); } }
  3. 批量读取历史记录

    scss
    代码解读
    复制代码
    private void readHistoricalData() { // 准备长特征值读取 final List[]> fragments = new ArrayList<>(); // 读取第一块 bluetoothGatt.readCharacteristic(historyCharacteristic); // 处理读取回调 @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { if (historyCharacteristic.getUuid().equals(characteristic.getUuid())) { byte[] data = characteristic.getValue(); fragments.add(data); // 继续读取下一块(示例逻辑) if (needMoreData(data)) { bluetoothGatt.readCharacteristic(historyCharacteristic); } else { // 所有数据读取完毕,合并处理 processHistoricalData(fragments); } } } } }

性能优化与常见问题解决

  1. 电池优化:调整连接参数延长设备电池寿命

    java
    代码解读
    复制代码
    // 仅适用于Android 8.0+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { BluetoothGattService service = bluetoothGatt.getService(SERVICE_UUID); // 将连接间隔设为较长(省电模式) bluetoothGatt.setPreferredPhy( BluetoothDevice.PHY_LE_1M, // TX PHY BluetoothDevice.PHY_LE_1M, // RX PHY BluetoothDevice.PHY_OPTION_NO_PREFERRED // 编码选项 ); bluetoothGatt.requestConnectionPriority( BluetoothGatt.CONNECTION_PRIORITY_BALANCED); }
  2. 连接稳定性:实现断线重连机制

    scss
    代码解读
    复制代码
    @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_DISCONNECTED) { Log.w(TAG, "设备断开连接: " + status); // 清理状态 cleanupResources(); // 如果不是用户主动断开,尝试重连 if (!userDisconnectRequested) { Log.d(TAG, "尝试重新连接..."); // 延迟一段时间后重连(避免立即重连导致的问题) new Handler(Looper.getMainLooper()).postDelayed(() -> { reconnectToDevice(); }, 2000); } } }
  3. 数据一致性:心率计算检查

    arduino
    代码解读
    复制代码
    private boolean isValidHeartRate(int heartRate) { // 检查心率是否在合理范围内(通常人类心率30-220) if (heartRate < 30 || heartRate > 220) { Log.w(TAG, "收到可疑心率值: " + heartRate); return false; } // 检查与前一次测量的突变 if (lastHeartRate > 0 && Math.abs(heartRate - lastHeartRate) > 20) { // 心率突变超过20,可能是测量错误 suspiciousReadingCount++; if (suspiciousReadingCount < 3) { // 忽略偶然突变 return false; } } else { suspiciousReadingCount = 0; } lastHeartRate = heartRate; return true; }

七、BLE安全通信简述

GATT通信安全性是医疗、支付等应用的重要考量。BLE提供了加密和认证机制,但默认设置下通信可能不安全。

安全级别

BLE安全分四个级别:

  1. Security Mode 1, Level 1: 无安全(无配对、无加密)
  2. Security Mode 1, Level 2: 未认证配对加密
  3. Security Mode 1, Level 3: 认证配对加密
  4. Security Mode 1, Level 4: 认证LE Secure Connections配对加密(最安全)

在Android中实现安全GATT通信

java
代码解读
复制代码
// 设置绑定回调 private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { // 连接成功,请求提高安全级别(仅适用于已配对设备) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { gatt.setEncryptionLevel(BluetoothDevice.ENCRYPTION_LEVEL_FULL); } // 请求MTU和服务发现 gatt.requestMtu(512); } } }; // 为特征设置加密保护 // 注意:这需要在BLE设备固件中配置相应的权限标志

当GATT特征需要加密保护时,客户端尝试读写会触发自动配对过程,这在医疗和隐私数据保护场景下非常重要。

八、总结与最佳实践

通过深入理解GATT通信机制,我们可以设计出高效、可靠、节能的BLE应用。

最佳实践汇总

  1. 服务设计:尽量使用标准UUID,合理组织服务和特征结构

  2. 连接流程:连接后先设置MTU,再发现服务

  3. 数据通信选择:

    • 单次读取:静态配置数据
    • 通知/指示:实时变化数据
    • 无响应写入+队列:大数据吞吐
  4. MTU优化:总是协商最大MTU,但考虑平台差异

  5. 错误处理:实现可靠的重连机制,处理各种异常情况

性能提升关键点

  • 批量处理:多个小数据包合并发送,减少传输次数
  • 合理使用通知:不要过度使用通知机制
  • 异步操作:避免GATT操作阻塞UI线程
  • 连接参数调优:根据应用场景调整连接优先级

理解并掌握这些GATT通信机制,你的BLE应用将更加稳定、高效且省电,无论是在消规类产品还是车规或者工业级产品中都能脱颖而出。

注:本文转载自juejin.cn的万户猴的文章"https://juejin.cn/post/7496341504828817435"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

140
Android
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top