Android BLE 官网地址
一、BLE简单介绍
蓝牙BLE是在Android4.3系统及以上引入的,但是仅作为中央设备,直到5.0以后才可以既作为中央设备又可以作为周边设备。
BLE : Bluetooth Low Energy
,即蓝牙低功耗,它是一种技术,从蓝牙4.0开始支持。
低功耗蓝牙芯片有两种模式:单模和双模。
- 单模:只能执行低功耗协议栈,也就是只支持BLE。
- 双模:即支持传统蓝牙又支持BLE的使用。
BLE较传统蓝牙具有传输速度更快,覆盖范围更广,安全性更高,延迟更短,耗电低等优点。
二、BLE基本概念
2.1、GATT概述(深入了解见2.3)
Gatt
(Generic Attribute Profile)即通用属性协议,用于在BLE链路上发送和接收的数据块。目前所有的BLE应用都是基于GATT的。一个设备可以实现多个配置文件。- BLE交互的桥梁是
Characteristic、Desciptor、Service
。 Characteristic
:可以理解为一个数据类型,它包括一个value和0至多个对此characteristic的描述(Descriptor
);Descriptor
:对Characterisctic
的描述,如范围、单位等;Service
:是Characteristic
的集合,它可以包含多个Characteristic
。
2.2、GATT定义了用于BLE设备传输数据的标准数据结构:
- 服务(
Service
) - 特征(
Characteristic
) - 描述符(
Descriptor
)。
2.3、通用属性配置文件(GATT)及其服务,特性与属性介绍
-
角色
- 除了GAP定义了角色之外,BLE还定义了另外2种角色:
GATT
服务器和GATT
客户端,它们完全独立于GAP的角色。提供数据的设备称为GATT,访问GATT服务器而获得数据的设备称为GATT客户端。 注意:一个设备可以同时作为服务器和客户端。
- 除了GAP定义了角色之外,BLE还定义了另外2种角色:
-
GATT层
- GATT 层是传输真正数据所在的层。
- 一个GATT服务器通过一个称为属性表的表格组织数据,这些数据就是用于真正发送的数据。
-
属性
- 一个属性包含句柄、UUID(类型)、值,句柄是属性在GATT表中的索引,在一个设备中每一个属性的句柄都是唯一的。UUID包含属性表中数据类型的信息,它是理解属性表中的值的每一个字节的意义的关键信息。在一个GATT表中可能有许多属性,这些属性能可能有相同的UUID。
-
特性
- 一个特性至少包含2个属性:一个属性用于声明,一个属性用于存放特性的值。
- 所有通过GATT服务传输的数据必须映射成一系列的特性,可以把特性中的这些数据看成是一个个捆绑起来的数据,每个特性就是一个自我包容而独立的数据点。 例如,如果几块数据总是一起变化,那么我们可以把它们集中在一个特性里。
-
描述符
- 任何在特性中的属性不是定义为属性值就是为描述符。描述符是一个额外的属性以提供更多特性的信息,它提供一个人类可识别的特性描述的实例。 然而,有一个特别的描述符值得特别地提起:客户端特性配置描述符(Client Characteristic Configuration Descriptor,CCCD),这个描述符是给任何支持通知或指示功能的特性额外增加的。
- 在CCCD中写入"1"使能通知功能,写入"2"使能指示功能,写入"0"同时禁止通知和指示功能。
-
服务
- 一个服务包含一个或多个特性,这些特性是逻辑上相关的集合体。
- GATT服务一般包含几块具有相关的功能,比如特定传感器的读取和设置,人机接口的输入输出。组织具有相关的特性到服务中既实用又有效,因为它使得逻辑上和用户数据上的边界变得更加清晰,同时它也有助于不同应用程序间代码的重用。GATT基于蓝牙技术联盟(SIG)官方而设计,SIG建议根据它们的规范设计自己的profile。
-
profile(数据配置文件)
- 一个
profile
文件可以包含一个或者多个服务,一个profile
文件包含需要的服务的信息或者为对等设备如何交互的配置文件的选项信息。设备的GAP和GATT的角色都可能在数据的交换过程中改变,因此,这个文件应该包含广播的种类、所使用的连接间隔、所需的安全等级等信息。 - 需要注意的是 :一个
profile
中的属性表不能包含另一个属性表。
- 一个
-
标准的定制服务和特性
-
技术联盟(SIG)已经定义一些profile、服务、特性和根据协议栈的GATT层定义的属性。但是,协议栈中只实现了一部分应用的BLE服务,那就意味着,只要协议栈支持 GATT ,就可能为一个应用建立一个它需要的 profile 和服务。
-
既然在一个应用中可以支持profile和服务,那么就可以在这个应用中建立一个定制的服务。
-
-
UUID
-
GATT层
中定义的所有属性都有一个UUID值,UUID是全球唯一的128位的号码,它用来识别不同的特性。 -
蓝牙技术联盟 UUID
- 蓝牙核心规范制定了两种不同的UUID,一种是基本的UUID,一种是代替基本UUID的16位UUID。
- 所有的蓝牙技术联盟定义UUID共用了一个基本的UUID: 0x0000xxxx-0000-1000-8000-00805F9B34FB
- 为了进一步简化基本UUID ,每一个蓝牙技术联盟定义的属性有 一个唯一的16位UUID,以代替上面的基本UUID的'x'部分。例如,心率测量特性使用 0X2A37 作为它的16位UUID,因此它完整的128位UUID为:0x00002A37-0000-1000-8000-00805F9B34FB
- 虽然技术联盟使用相同的基本UUID,但是16位的UUID足够唯一地识别蓝牙技术联盟所定义的各种属性。
- 蓝牙技术联盟所用的基本UUID不能用于任何定制的属性、服务和特性。对于定制的属性,必须使用另外完整的128位UUID。
-
供应商特定的UUID
- SoftDevice 根据蓝牙技术联盟定义UUID类似的方式定义UUID:先增加一个特定的基本UUID,再定义一个16位的UUID(类似于一个别名),再加载在基本UUID之上。这种采用为所有的定制属性定义一个共用的基本UUID的方式使得应用变为更加简单,至少在同一服务中更是如此。
- 使用软件nRFgo Studio非常容易产生一个新的基本UUID:例如,在LED BUTTON示例中,采用0x0000xxxx-1212-EFDE-1523-785FEABCD123作为基本UUID。
-
蓝牙核心规范没有任何规则或是建议如何对加入基本UUID的16位UUID进行分配,因此你可以按照你的意图来任意分配。例如,在LED BUTTON示例中,0x1523作为服务的UUID,0x1524作为LED特性的UUID,0x1525作为按键状态特性的UUID。
-
空中操作和性质
-
大部分的空中操作事件都是采用句柄 来进行的,因为句柄能够唯一识别各个属性。如何使用特性依据它的性质,特性的性质包括:
-
1)写
-
2)没有回应的写
-
3)读
-
4)通知
-
5)指示
-
-
写和没有回应的写
写和没有回应的写允许GATT客户端写入一个值到GATT服务器的一个特性中。它们之间不同的地方在于没有回应的写事件没有任何应用层上的确认或回应。
- 读
读性质表明一个GATT客户端可以读取在GATT服务器中特性的值。
- 通知和指示
通知和指示性质允许GATT服务器在其某个特性改变的时候对GATT客户端进行提醒,通知和指示之间不同之处在于指示有应用层上的确认,而通知没有。
三、BLE基本API
3.1、BluetoothAdapter 蓝牙适配器
本地设备蓝牙适配器,提供基本蓝牙功能的工具,例如开启蓝牙发现,查询配对设备,实例化蓝牙设备链接,监听连接请求,扫描设备等。
java
BluetoothAdapter.LeScanCallback scanCallback = new BluetoothAdapter.LeScanCallback() {
@Override public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
//device 远程蓝牙设备
//rssi 信号强度
//scanRecord 广播信息(service_uuid,mac,或者加密信息等)
}
};
3.2、BluetoothDevice 远程蓝牙设备
- 提供了远程蓝牙设备的基本信息,如名称,地址,类别,绑定状态等。
3.3、BluetoothGatt GATT客户端
- GATT协议的公共API,提供了GATT的基本功能,如实现蓝牙设备的通信。
3.4、BluetoothGattCallback GATT状态回调
java
/* 连接状态回调,包括连接到服务器 / 从服务器断开连接 */
onConnectionStateChange();
/* 远程设备发现新服务 */
onServicesDiscovered();
/* 特征相关操作的回调 */
onCharacteristicRead();
onCharacteristicWrite();
onCharacteristicChanged();
3.5、BluetoothGattService
- GATT服务,根据服务的 UUID,尝试获取服务实例。
3.6、BluetoothGattCharacteristic
- GATT特征,实际通信中的数据信息主体。
java
/* 获取对应UUID的特征 */
BluetoothGattService.getCharacteristic(uuid);
/* 获取服务的特征列表 */
BluetoothGattService.getCharacteristics();
kotlin
private fun initServiceAndChara() {
val bluetoothGattServices = mBluetoothGatt!!.services
for (bluetoothGattService in bluetoothGattServices) {
val characteristics = bluetoothGattService.characteristics
for (characteristic in characteristics) {
val charaProp = characteristic.properties
if (charaProp and BluetoothGattCharacteristic.PROPERTY_READ > 0) {
//读取数据
read_UUID_chara = characteristic.uuid
read_UUID_service = bluetoothGattService.uuid
Log.e("Ble_Operation","read_chara=$read_UUID_chara----read_service=$read_UUID_service")
}
if (charaProp and BluetoothGattCharacteristic.PROPERTY_WRITE > 0) {
//写
write_UUID_chara = characteristic.uuid
write_UUID_service = bluetoothGattService.uuid
Log.e("Ble_Operation","write_chara=$write_UUID_chara----write_service=$write_UUID_service")
}
if (charaProp and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE > 0) {
//写无响应
write_UUID_chara = characteristic.uuid
write_UUID_service = bluetoothGattService.uuid
Log.e("Ble_Operation","write_chara=$write_UUID_chara----write_service=$write_UUID_service")
}
if (charaProp and BluetoothGattCharacteristic.PROPERTY_NOTIFY > 0) {
//订阅回调可频繁调用,但不能确保百分百接收到回调
notify_UUID_chara = characteristic.uuid
notify_UUID_service = bluetoothGattService.uuid
Log.e("Ble_Operation","notify_chara=$notify_UUID_chara----notify_service=$notify_UUID_service")
}
if (charaProp and BluetoothGattCharacteristic.PROPERTY_INDICATE > 0) {
//不可频繁调用,但可百分百接收到回调
indicate_UUID_chara = characteristic.uuid
indicate_UUID_service = bluetoothGattService.uuid
Log.e("Ble_Operation","indicate_chara=$indicate_UUID_chara----indicate_service=$indicate_UUID_service")
}
}
}
}
- PROPERTY_READ //读取
- PROPERTY_WRITE //写
- PROPERTY_WRITE_NO_RESPONSE //无响应的写
- PROPERTY_NOTIFY //订阅回调可频繁调用,但不能确保百分百接收到回调
- PROPERTY_INDICATE //不可频繁调用,但可百分百接收到回调
3.7、BluetoothSocket
- 表示蓝牙套接字接口(与 TCP Socket 相似)。这是允许应用通过 InputStream 和 OutputStream 与其他蓝牙设备交换数据的节点。正是利用这个对象来完成蓝牙设备间的数据交换
3.8、BluetoothServerSocket
- 表示用于侦听传入请求的开发服务器套接字(类似于 TCP ServerSocket)要连接两台 Android 设备,其中一台设备必须使用此类开发的一个服务器套接字。当一台远程蓝牙设备向此设备发出连接请求时,BluetoothServerSocket 将会在接受连接后返回已连接的 BluethoothSocket。
3.9、BluetoothClass
- 描述蓝牙设备的一般特性和功能。这是一组只读属性,用于定义设备的主要和次要设备类及其服务。不过,它不能可靠地描述设备支持的所有蓝牙配置文件和服务,而是适合作为设备类型提示。
3.10、BluetoothProfile
- 表示蓝牙配置文件的接口。蓝牙配置文件是适用于设备间蓝牙通信的无线接口规范。免提配置文件便是一个示例。如需了解关于配置文件的详细讨论,参考下面配置文件的讲解
3.11、BluetoothHeadset
- 提供蓝牙耳机支持,以便与手机配合使用。其中包括蓝牙耳机和免提(1.5版)配置文件。
- BluetoothProfile 的实现类
3.12、BlutoothA2dp
- 定义高质量音频如何通过蓝牙连接和流式传输,从一台设备传输到另一台设备。"A2DP"代表高级音频分发配置文件。是 BluetoothProfile的实现类
3.13、BluetoothHealth
- 表示用于控制蓝牙服务的健康设备配置文件代理。BluetoothProfile 的实现类。
四、BLE的连接模式
对于BLE单设备来讲常见的蓝牙模块的工作模式有四种:
- 主设备模式
- 从设备模式
- 广播模式
- Mesh组网模式
主机模式工作流程图:
五、BLE的基本操作流程
- 第一步:扫描周边BLE设备
- 第二步:从扫描结果中获取符合条件的BLE设备辨别身份的条件:设备名称、设备地址(MAC)、广播数据。
- 第三步:连接匹配的BLE设备
- 第四步:连接成功后可以获取设备所包含的所有服务和特征,服务和特征是APP与设备进行交互的通道
- 第五步:对指定的特征进行读、写、通知等操作。常用的操作有:notify和wirte,前者是APP接收BLE发过来的数据,后者是APP向BLE设备发送数据。
- 第六步:APP与BLE设备断开连接。
六、BLE的设备开发注意点
- 1)只有在Android 4.3版本以上才可以使用BLE功能;在Android 6.0 以上使用扫描方法必须获取位置权限;某些型号手机还可能需要打开定位功能。
- 2)遇到写或者订单失败的情况,再次操作建议间隔一段时间
- 3)什么情况下分包:写限制20个字节,超过20个字节,需要分包发送
七、BLE开发过程的一些技巧
- 1)并不是每次都需要扫描设备再进行连接,例如:之前扫描过设备或者知道设备MAC,可将设备信息保存起来,需要的时候直接连接,省去扫描步骤加快流程。
- 2)连接成功之后,例如:已知需求的服务和特性UUID,可直接进行操作,无需再进行获取设备对应服务和特性。
- 3)蓝牙BLE的连接与系统挂钩,不与APP的生命周期挂钩的,因此APP的退出,设备回调丢失,但设备依然还是连接状态,可以再次对已经连接设备的MAC进行connect, 快速建立连接通知桥梁。
八、总结
- GATT 结构:服务 → 特征值 → 描述符,是 BLE 数据交互的基础。
- 低功耗特性:适合电池供电的 IoT 设备和间歇性数据传输场景。
- 开发重点:权限处理、连接管理、数据读写和通知机制。
掌握这些概念后,可以进一步学习 BLE 广播(Advertising)、数据加密(Secure Connection)等高级功能。