参考:https://blog.csdn.net/qq_36075612/article/details/127739150?spm=1001.2014.3001.5502
参考: https://blog.csdn.net/qq_36075612/article/details/122772966?spm=1001.2014.3001.5502
传统蓝牙是在之前的 1.0.1.2 , 2.0+EDR,2.1+EDR,3.0+EDR 等基础上发展和完善起来
的。
传统蓝牙可以用与数据量比较大的传输,如语音,音乐,较高数据量传输等,低功耗蓝牙这样应用于实时性要求比较高,但是数据速率比较低的产品,如遥控类的,如鼠标,键盘,遥控鼠标 (Air Mouse),传感设备的数据发送,如心跳带,血压计,温度传感器等。传统蓝牙有 3 个功率级别, Class1,Class2,Class3, 分别支持 100m,10m,1m 的传输距离,而低功耗蓝牙无功率级别,一般发送功率在 7dBm ,一般在空旷距离,达到 20m 应该是没有问题的。
由于苹果对经典蓝牙数据传输接口有限制(需要过 MFI 认证),加上功耗偏大,因此在
目前移动互联应用中慢慢地被淘汰 。
低功耗蓝牙是 Nokia 的 Wibree 标准上发展起来的。
蓝牙低能耗(BLE)技术是低成本、短距离、可互操作的鲁棒性无线技术,工作在免许可的2.4GHz ISM 射频频段。它从一开始就设计为超低功耗 (ULP)无线技术。它利用许多智能手段最大限度地降低功耗。
蓝牙低功耗架构共有两种芯片构成:单模芯片和双模芯片。
蓝牙技术联盟 (Bluetooth Special Interest Group)是一家贸易协会,由电信、计算机、汽车制造、工业自动化和网络行业的领先厂商组成。该小组致力于推动蓝牙无线技术的发展,为短距离连接移动设备制定低成本的无线规范,并将其推向市场。
Bluetooth 的一个很重要特性,就是所有的 Bluetooth 产品都必须实现全部的Bluetooth 规范。为了更容易的保持 Bluetooth 设备之间的兼容, Bluetooth 规范中定义了Profile 。 Profile 定义了设备如何实现一种连接或者应用,你可以把 Profile 理解为连接层或者应用层的协议规范 。
蓝牙组织规定了一些标准的 profile ,例如 HID OVER GATT ,防丢器 ,心率计等。每个 profile 中会包含多个 service ,每个 service 代表从机的一种能力。
service 可以理解为一个服务 。在 BLE 从机中,通过有多个服务,例如电量信息服务、系统信息服务等,每个 service 中又包含多个 characteristic 特征值 。每个具体的characteristic 特征值才是 BLE 通信的主题。比如当前的电量是 80%,所以会通过电量的 characteristic 特征值存在从机的 profile 里,这样主机就可以通过这个 characteristic 来读取 80% 这个数据。
Characteristic 特征值 。 BLE 主从机的通信均是通过 characteristic 来实现,可以理解为一个标签,一个属性,通过这个标签可以获取或者写入想要的内容。
Decriptor 描述符 。描述符就是描述 Characteristic 的,描述符有读写属性,描述符也可以被读写。
UUID,统一识别码,我们刚才提到的 service 和 characteristic,都需要一个唯一的UUID 来标识。
SIG 定义 UUID 共用了一个 基本 UUID : 0x0000xxxx-0000-1000-8000-00805F9B34FB ,
总共 128 位。为了进一步简化基本 UUID ,每一个 SIG 定义的属性有一个唯一的 16 位 UUID ,以代替上面的基本 UUID 的‘x ’部分。使用 16 位的 UUID 便于记忆和操作,例如 SIG 定义了“ Device Information ”的 16 位 UUID 为 0x180A 。
在所有的 Profile 中,有四种是基本的 Profile ,这些 Profile 会被其它的 Profile 使用,它们包括 GAP/SDAP/SPP/GOEP Profile 。
GAP Profile: Generic Access Profile,该 Profile 保证不同的 Bluetooth 产品可以互相发现对方并建立连接。 GAP 规定的是一些一般性的运行任务。因此,它具有强制性,并作为所有其它蓝牙应用规范的基础 。
SDAP Profile: Service Discovery Application Profile,通过该 Profile,一个 Bluetooth 设备可以找到其它 Bluetooth 设备提供的服务,以及查询相关的信息。
全称 Serial Port Profile ,定义了如何在两台 BT 设备之间建立虚拟串口并进行连接。
例如,在两台电脑或者 Labtop 之间就可以建立这种连接。
GOEP Profile: Generic Object Exchange Profile,通用对象交换。这个 Profile的名字有些费解,它定义的是数据的传输,包括同步,文件传输,或者推送其它的数据。可以理解为与内容无关的传输层协议,可以被任何应用用来传输自己定义的数据对象。
A2DP Profile 全名是 Advenced Audio Distribution Profile 蓝牙音频传输模型协定。
DUN Profile 全称 Dial-up Networking (DUN) Profile,实现一台蓝牙设备通过另外一个带无线功能的蓝牙设备共享上网。
AVRCP(Audio/Video Remote Control Profile ),也就是音频 / 视频远程控制配置文件。
HID 全称 Human Interface Device Profile, 即人机接口设备 Profile。
现在低功耗蓝牙(BLE )连接都是建立在 GATT (Generic Attribute Profile) 协议之上。 GATT 是一个在蓝牙连接之上的发送和接收很短的数据段的通用规范,这些很短的数据
段被称为属性(Attribute )。
GAP 使你的设备被其他设备可见,并决定了你的设备是否可以或者怎样与合同设备进行交互。例如 Beacon 设备就只是向外广播,不支持连接,小米手环就等设备就可以与中心设备连接。
GAP 给设备定义了若干角色,其中主要的两个是:外围设备( Peripheral )和中心设备
(Central )。
外围设备:这一般就是非常小或者简单的低功耗设备,用来提供数据,并连接到一个更
加相对强大的中心设备。例如小米手环。
中心设备:中心设备相对比较强大,用来连接其他外围设备。例如手机等。
在 GAP 中 外围设备 通过两种方式向外广播数据: Advertising Data Payload ( 广播数据 )和 Scan Response Data Payload ( 扫描回复 ),每种数据最长可以包含 31 byte 。
这里广播数据是必需的,因为外设必需不停的向外广播,让中心设备知道它的存在。扫描回
复是可选的,中心设备可以向外设请求扫描回复,这里包含一些设备额外的信息,例如设备
的名字。
GAP 的广播工作流程如下图所示。
从图中我们可以清晰看出广播数据和扫描回复数据是怎么工作的。外围设备会设定一个广播间隔,每个广播间隔中,它会重新发送自己的广播数据。广播间隔越长,越省电,同时也不太容易扫描到。
大部分情况下,外设通过广播自己来让中心设备发现自己,并建立 GATT 连接,从而进行更多的数据交换。也有些情况是不需要连接的,只要外设广播自己的数据即可。用这种方式主要目的是让外围设备,把自己的信息发送给多个中心设备。因为基于 GATT 连接的方式 的,只能是一个外设连接一个中心设备。使用广播这种方式最典型的应用就是苹果的iBeacon 。广播工作模式下的网络拓扑图如下:
GATT 的全名是 Generic Attribute Profile (普通属性协议),它定义两个 BLE 设备通过叫做 Service 和 Characteristic 的东西进行通信。 GATT 就是使用了 ATT(Attribute Protocol )协议, ATT 协议把 Service, Characteristic 对应的数据保存在一个查找表中,查表使用 16 bit ID 作为每一项的索引。
一旦两个设备建立起了连接,GATT 就开始起作用了,这也意味着,你必需完成前面的 GAP
协议。这里需要说明的是 GATT 连接,必需先经过 GAP 协议 。
实际上,我们在 Android 开发中,可以直接使用设备的 MAC 地址发起连接,可以不经过扫描的步骤。这并不意味不需要经过GAP,实际上在芯片级别已经给你做好了,蓝牙芯片发起连接,总是先扫描设备,扫描到了才会发起连接。
GATT 连接需要特别注意的是: GATT 连接是独占的 。 也就是一个 BLE 外设同时只能被
一个中心设备连接。一旦外设被连接,它就会马上停止广播,这样它就对其他设备不可见了。当设备断开,它又开始广播。
中心设备和外设需要双向通信的话,唯一的方式就是建立 GATT 连接。
一个外设只能连接一个中心设备,而一个中心设备可以连接多个外设。一旦建立起了连接,通信就是双向的了,对比前面的 GAP 广播的网络拓扑, GAP 通信是单向的。如果你要让两个设备外设能通信,就只能通过中心设备中转。
GATT 通信的双方是 C/S 关系。外设作为 GATT 服务端(Server),它维持了 ATT 的查找表以及 service 和 characteristic 的定义。中心设备是 GATT客户端(Client),它向 Server 发起请求。需要注意的是,所有的通信事件,都是由客户端(也叫主设备,Master)发起,并且接收服务端(也叫从设备,Slave)的响应。
一旦连接建立,外设将会给中心设备建议一个连接间隔(Connection Interval),这样中心设备就会在每个连接间隔尝试去重新连接, 检查是否有新的数据 。但是这个连接间隔只是一个建议 ,你的中心设备可能并不会严格按照这个间隔来执行,例如你的中心设备正在忙于连接其他的外设,或者中心设备资源太忙。
下图展示一个外设(GATT 服务端)和中心设备(GATT 客户端)之间的数据交换流程,可以看到的是, 每次都是主设备发起请求 :
GATT 事务是建立在嵌套的 Profiles, Services 和 Characteristics 之上的的,如下图所示:
并不是实际存在于 BLE 外设上的,它只是一个被 Bluetooth SIG 或者外设设计者预先定义的 Service 的集合。例如心率 Profile(Heart Rate Profile) 就是结合了 Heart Rate Service 和 Device Information Service。所有官方通过 GATT Profile 的列表可以从这里找到。
是把数据分成一个个的独立逻辑项,它包含一个或者多个 Characteristic。每个 Service 有一个 UUID 唯一标识。 UUID 有 16 bit 的,或者 128 bit 的。6 bit 的 UUID 是官方通过认证的,需要花钱购买,128 bit 是自定义的,这个就可以自己随便设置。
官方通过了一些标准 Service,完整列表在这里(链接失效了)。以 心率 Heart Rate Service为 例 , 可 以 看 到 它 的 官 方 通 过16 bit UUID 是 0x180D , 包 含 3 个Characteristic:Heart Rate Measurement(心率测量), Body Sensor Location(车身传感器位置) 和 Heart Rate Control Point(心率控制点),并且定义了只有第一个是必须的,其他是可选实现的。
在 GATT 事务中的最低界别的是 Characteristic,Characteristic 是最小的逻辑数据单元,当然它可能包含一个组关联的数据,例如加速度计的 X/Y/Z 三轴值。
与 Service 类似,每个 Characteristic 用 16 bit 或者 128 bit 的 UUID唯一标识。你可以免费
使用 Bluetooth SIG 官方定义的标准 Characteristic,使用官方定义的,可以确保 BLE 的软件和硬件能相互理解。当然,你可以自定义 Characteristic,这样的话,就只有你自己的软件和外设能够相互理解。
举个例子,心率 Heart Rate Measurement Characteristic,这是上面提到的 Heart Rate Service 必需实现的 Characteristic,它的 UUID 是 0x2A37。
它的数据结构是,开始 8 bit 定义心率数据格式(是 UINT8 还是 UINT16?),接下来就是对应格式的实际心率数据。
实际上,和 BLE 外设打交道,主要是通过 Characteristic。你可以从 Characteristic 读取数据,也可以往 Characteristic 写数据。这样就实现了双向的通信。
所以你可以自己实现一个类似串口(UART)的 Sevice,这个 Service 中包含两个Characteristic,一个被配置只读的通道(RX),另一个配置为只写的通道(TX)。
Bluetooth SIG 官方文档,如果想深入了解,可以精读。
蓝牙核心协议文档
Bluetooth Developer Portal
官方通过的 BLE Profile
官方通过的 BLE Service
官方通过的 BLE Characteristic
给大家推荐一本书《低功耗蓝牙权威指南》
教程源码地址: https://github.com/HX-IoT/
https://blog.csdn.net/WHMTBYY/article/details/125089358
https://blog.csdn.net/WHMTBYY/article/details/125861715
声明权限
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
初始化及配置
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BleManager.getInstance().init(getApplication());
BleManager.getInstance()
.enableLog(true)
.setReConnectCount(1, 5000)
.setOperateTimeout(5000);
}
在使用之前,需要事先调用初始化init(Application app)方法。此外,可以进行一些自定义的配置,比如是否显示框架内部日志,重连次数和重连时间间隔,以及操作超时时间。
扫描外围设备
APP作为中心设备,想要与外围硬件设备建立蓝牙通信的前提是首先得到设备对象,途径是扫描。在调用扫描方法之前,你首先应该先处理下面的准备工作。
BleManager.getInstance().isSupportBle();
BleManager.getInstance().isBlueEnable();
BleManager.getInstance().enableBluetooth();
方法2:通过startActivityForResult引导界面引导用户打开蓝牙。
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, 0x01);
需要注意的是,第一种方法是异步的,打开蓝牙需要一段时间,调用此方法后,蓝牙不会立刻就处于开启状态。如果使用此方法后紧接者就需要进行扫描,建议维护一个阻塞线程,内部每隔一段时间查询蓝牙是否处于开启状态,外部显示等待UI引导用户等待,直至开启成功。使用第二种方法,会通过系统弹出框的形式引导用户开启,最终通过onActivityResult的形式回调通知是否开启成功。
6.0及以上机型动态获取位置权限。
蓝牙打开之后,进行扫描之前,需要判断下当前设备是否是6.0及以上,如果是,需要动态获取之前在Manifest中声明的位置权限。
配置扫描规则
扫描规则可以配置1个或多个,也可以不配置使用默认(扫描10秒)。扫描的时候,会根据配置的过滤选项,对扫描到的设备进行过滤,结果返回过滤后的设备。扫描时间配置为小于等于0,会实现无限扫描,直至调用BleManger.getInstance().cancelScan()来中止扫描。
BleScanRuleConfig scanRuleConfig = new BleScanRuleConfig.Builder()
.setServiceUuids(serviceUuids) // 只扫描指定的服务的设备,可选
.setDeviceName(true, names) // 只扫描指定广播名的设备,可选
.setDeviceMac(mac) // 只扫描指定mac的设备,可选
.setAutoConnect(isAutoConnect) // 连接时的autoConnect参数,可选,默认false
.setScanTimeOut(10000) // 扫描超时时间,可选,默认10秒
.build();
BleManager.getInstance().initScanRule(scanRuleConfig);
以上准备工作完成后,就可以开始进行扫描。
BleManager.getInstance().scan(new BleScanCallback() {
@Override
public void onScanStarted(boolean success) {
}
@Override
public void onLeScan(BleDevice bleDevice) {
}
@Override
public void onScanning(BleDevice bleDevice) {
}
@Override
public void onScanFinished(List<BleDevice> scanResultList) {
}
});
设备信息
扫描得到的BLE外围设备,会以BleDevice对象的形式,作为后续操作的最小单元对象。它本身含有这些信息:
后续进行设备连接、断开、判断设备状态,读写操作等时候,都会用到这个对象。可以把它理解为外围蓝牙设备的载体,所有对外围蓝牙设备的操作,都通过这个对象来传导。
连接、断连、监控连接状态
拿到设备对象之后,可以进行连接操作。
BleManager.getInstance().connect(bleDevice, new BleGattCallback() {
@Override
public void onStartConnect() {
}
@Override
public void onConnectFail(BleException exception) {
}
@Override
public void onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status) {
}
@Override
public void onDisConnected(boolean isActiveDisConnected, BleDevice bleDevice, BluetoothGatt gatt, int status) {
}
});
GATT协议
BLE连接都是建立在 GATT (Generic Attribute Profile) 协议之上。GATT 是一个在蓝牙连接之上的发送和接收很短的数据段的通用规范,这些很短的数据段被称为属性(Attribute)。它定义两个 BLE 设备通过Service 和 Characteristic 进行通信。GATT 就是使用了 ATT(Attribute Protocol)协议,ATT 协议把 Service, Characteristic以及对应的数据保存在一个查找表中,次查找表使用 16 bit ID 作为每一项的索引。
关于GATT这部分内容会在下面重点讲解。总之,中心设备和外设需要双向通信的话,唯一的方式就是建立 GATT 连接。当连接成功之后,外围设备与中心设备之间就建立起了GATT连接。
上面讲到的connect(BleDevice bleDevice, BleGattCallback bleGattCallback)方法其实是有返回值的,这个返回值就是BluetoothGatt。当然还有其他方式可以获取BluetoothGatt对象,连接成功后,调用:
BluetoothGatt gatt = BleManager.getInstance().getBluetoothGatt(BleDevice bleDevice);
通过BluetoothGatt对象作为连接桥梁,中心设备可以获取外围设备的很多信息,以及双向通信。
首先,就可以获取这个蓝牙设备所拥有的Service和Characteristic。每一个属性都可以被定义作不同的用途,通过它们来进行协议通信。下面的方法,就是通过BluetoothGatt,查找出所有的Service和Characteristic的UUID:
List<BluetoothGattService> serviceList = bluetoothGatt.getServices();
for (BluetoothGattService service : serviceList) {
UUID uuid_service = service.getUuid();
List<BluetoothGattCharacteristic> characteristicList= service.getCharacteristics();
for(BluetoothGattCharacteristic characteristic : characteristicList) {
UUID uuid_chara = characteristic.getUuid();
}
}
协议通信
APP与设备建立了连接,并且知道了Service和Characteristic(需要与硬件协议沟通确认)之后,我们就可以通过BLE协议进行通信了。通信的桥梁,主要就是是通过 标准的或者自定义的Characteristic,中文我们称之为“特征”。我们可以从 Characteristic 读数据和写数据。这样就实现了双向的通信。站在APP作为中心设备的角度,常用于数据交互的通信方式主要有3种:接收通知、写、读,此外还有设置最大传输单元,获取实时信号强度等通信操作。
BleManager.getInstance().notify(
bleDevice,
uuid_service,
uuid_characteristic_notify,
new BleNotifyCallback() {
@Override
public void onNotifySuccess() {
// 打开通知操作成功
}
@Override
public void onNotifyFailure(BleException exception) {
// 打开通知操作失败
}
@Override
public void onCharacteristicChanged(byte[] data) {
// 打开通知后,设备发过来的数据将在这里出现
}
});
关闭notify
BleManager.getInstance().stopNotify(uuid_service, uuid_characteristic_notify);
打开indicate
BleManager.getInstance().indicate(
bleDevice,
uuid_service,
uuid_characteristic_indicate,
new BleIndicateCallback() {
@Override
public void onIndicateSuccess() {
// 打开通知操作成功
}
@Override
public void onIndicateFailure(BleException exception) {
// 打开通知操作失败
}
@Override
public void onCharacteristicChanged(byte[] data) {
// 打开通知后,设备发过来的数据将在这里出现
}
});
关闭indicate
BleManager.getInstance().stopIndicate(uuid_service, uuid_characteristic_indicate);
这里的通知操作用到了两个关键的参数,uuid_service和uuid_characteristic_notify(或uuid_characteristic_indicate),就是上面提到的Service和Characteristic,此处以字符串的形式体现,不区分大小写。
BleManager.getInstance().read(
bleDevice,
uuid_service,
uuid_characteristic_read,
new BleReadCallback() {
@Override
public void onReadSuccess(byte[] data) {
// 读特征值数据成功
}
@Override
public void onReadFailure(BleException exception) {
// 读特征值数据失败
}
});
BleManager.getInstance().write(
bleDevice,
uuid_service,
uuid_characteristic_write,
data,
new BleWriteCallback() {
@Override
public void onWriteSuccess(int current, int total, byte[] justWrite) {
// 发送数据到设备成功(分包发送的情况下,可以通过方法中返回的参数可以查看发送进度)
}
@Override
public void onWriteFailure(BleException exception) {
// 发送数据到设备失败
}
});
进行BLE数据相互发送的时候,一次最多能发送20个字节。如果需要发送的数据超过20个字节,有两种方法,一种是主动尝试拓宽MTU,另一种是采用分包传输的方式。框架中的write方法,当遇到数据超过20字节的情况时,默认是进行分包发送的。
BleManager.getInstance().setMtu(bleDevice, mtu, new BleMtuChangedCallback() {
@Override
public void onSetMTUFailure(BleException exception) {
// 设置MTU失败
}
@Override
public void onMtuChanged(int mtu) {
// 设置MTU成功,并获得当前设备传输支持的MTU值
}
});
BleManager.getInstance().readRssi(
bleDevice,
new BleRssiCallback() {
@Override
public void onRssiFailure(BleException exception) {
// 读取设备的信号强度失败
}
@Override
public void onRssiSuccess(int rssi) {
// 读取设备的信号强度成功
}
});
在BLE设备通信过程中,有几点经验分享给大家:
在分解FastBle源码之前,我首先介绍一下BLE通信一些理论知识。
蓝牙简介
蓝牙是一种近距离无线通信技术。它的特性就是近距离通信,典型距离是 10 米以内,传输速度最高可达 24 Mbps,支持多连接,安全性高,非常适合用智能设备上。
蓝牙技术的版本演进
Android上BLE功能的逐步演进
在Android开发过程中,版本的碎片化一直是需要考虑的问题,再加上厂商定制及蓝牙本身也和Android一样一直在发展过程中,所以对于每一个版本支持什么功能,是我们需要知道的。
中心模式和外设模式是什么意思?
蓝牙的广播和扫描
以下内容部分参考自BLE Introduction 。
关于这部分内容,需要引入一个概念,GAP(Generic Access Profile),它用来控制设备连接和广播。GAP 使你的设备被其他设备可见,并决定了你的设备是否可以或者怎样与设备进行交互。例如 Beacon 设备就只是向外发送广播,不支持连接;小米手环就可以与中心设备建立连接。
在 GAP 中蓝牙设备可以向外广播数据包,广播包分为两部分: Advertising Data Payload(广播数据)和 Scan Response Data Payload(扫描回复),每种数据最长可以包含 31 byte。这里广播数据是必需的,因为外设必需不停的向外广播,让中心设备知道它的存在。扫描回复是可选的,中心设备可以向外设请求扫描回复,这里包含一些设备额外的信息,例如设备的名字。在 Android 中,系统会把这两个数据拼接在一起,返回一个 62 字节的数组。这些广播数据可以自己手动去解析,在 Android 5.0 也提供 ScanRecord 帮你解析,直接可以通过这个类获得有意义的数据。广播中可以有哪些数据类型呢?设备连接属性,标识设备支持的 BLE 模式,这个是必须的。设备名字,设备包含的关键 GATT service,或者 Service data,厂商自定义数据等等。
外围设备会设定一个广播间隔,每个广播间隔中,它会重新发送自己的广播数据。广播间隔越长,越省电,同时也不太容易扫描到。
刚刚讲到,GAP决定了你的设备怎样与其他设备进行交互。答案是有2种方式:
基于非连接的,这种应用就是依赖 BLE 的广播,也叫作 Beacon。这里有两个角色,发送广播的一方叫做 Broadcaster,监听广播的一方叫 Observer。
GATT 通信的双方是 C/S 关系。外设作为 GATT 服务端(Server),它维持了 ATT 的查找表以及 service 和 characteristic 的定义。中心设备是 GATT 客户端(Client),它向 Server 发起请求。需要注意的是,所有的通信事件,都是由客户端发起,并且接收服务端的响应。
BLE通信基础
BLE通信的基础有两个重要的概念,ATT和GATT。
ATT
全称 attribute protocol,中文名“属性协议”。它是 BLE 通信的基础。ATT 把数据封装,向外暴露为“属性”,提供“属性”的为服务端,获取“属性”的为客户端。ATT 是专门为低功耗蓝牙设计的,结构非常简单,数据长度很短。
GATT
全称 Generic Attribute Profile, 中文名“通用属性配置文件”。它是在ATT 的基础上,对 ATT 进行的进一步逻辑封装,定义数据的交互方式和含义。GATT是我们做 BLE 开发的时候直接接触的概念。
GATT 层级
GATT按照层级定义了4个概念:配置文件(Profile)、服务(Service)、特征(Characteristic)和描述(Descriptor)。他们的关系是这样的:Profile 就是定义了一个实际的应用场景,一个 Profile包含若干个 Service,一个 Service 包含若干个 Characteristic,一个 Characteristic 可以包含若干 Descriptor。
Profile
Profile 并不是实际存在于 BLE 外设上的,它只是一个被 Bluetooth SIG 或者外设设计者预先定义的 Service 的集合。例如心率Profile(Heart Rate Profile)就是结合了 Heart Rate Service 和 Device Information Service。所有官方通过 GATT Profile 的列表可以从这里找到。
Service
Service 是把数据分成一个个的独立逻辑项,它包含一个或者多个 Characteristic。每个 Service 有一个 UUID 唯一标识。 UUID 有 16 bit 的,或者 128 bit 的。16 bit 的 UUID 是官方通过认证的,需要花钱购买,128 bit 是自定义的,这个就可以自己随便设置。官方通过了一些标准 Service,完整列表在这里。以 Heart Rate Service为例,可以看到它的官方通过 16 bit UUID 是 0x180D,包含 3 个 Characteristic:Heart Rate Measurement, Body Sensor Location 和 Heart Rate Control Point,并且定义了只有第一个是必须的,它是可选实现的。
Characteristic
需要重点提一下Characteristic, 它定义了数值和操作,包含一个Characteristic声明、Characteristic属性、值、值的描述(Optional)。通常我们讲的 BLE 通信,其实就是对 Characteristic 的读写或者订阅通知。比如在实际操作过程中,我对某一个Characteristic进行读,就是获取这个Characteristic的value。
UUID
Service、Characteristic 和 Descriptor 都是使用 UUID 唯一标示的。
UUID 是全局唯一标识,它是 128bit 的值,为了便于识别和阅读,一般以 “8位-4位-4位-4位-12位”的16进制标示,比如“12345678-abcd-1000-8000-123456000000”。
但是,128bit的UUID 太长,考虑到在低功耗蓝牙中,数据长度非常受限的情况,蓝牙又使用了所谓的 16 bit 或者 32 bit 的 UUID,形式如下:“0000XXXX-0000-1000-8000-00805F9B34FB”。除了 “XXXX” 那几位以外,其他都是固定,所以说,其实 16 bit UUID 是对应了一个 128 bit 的 UUID。这样一来,UUID 就大幅减少了,例如 16 bit UUID只有有限的 65536(16的四次方) 个。与此同时,因为数量有限,所以 16 bit UUID 并不能随便使用。蓝牙技术联盟已经预先定义了一些 UUID,我们可以直接使用,比如“00001011-0000-1000-8000-00805F9B34FB”就一个是常见于BLE设备中的UUID。当然也可以花钱定制自定义的UUID。
通过上面BLE的基础理论,我们可以分析到,BLE通信实际上就是先由客户端发起与服务端的连接,再通过服务端的找到其Characteristic进行两者间的数据交互。
在FastBle源码中,首先看BleManager中的connect()方法:
public BluetoothGatt connect(BleDevice bleDevice, BleGattCallback bleGattCallback) {
if (bleGattCallback == null) {
throw new IllegalArgumentException("BleGattCallback can not be Null!");
}
if (!isBlueEnable()) {
BleLog.e("Bluetooth not enable!");
bleGattCallback.onConnectFail(new OtherException("Bluetooth not enable!"));
return null;
}
if (Looper.myLooper() == null || Looper.myLooper() != Looper.getMainLooper()) {
BleLog.w("Be careful: currentThread is not MainThread!");
}
if (bleDevice == null || bleDevice.getDevice() == null) {
bleGattCallback.onConnectFail(new OtherException("Not Found Device Exception Occurred!"));
} else {
BleBluetooth bleBluetooth = new BleBluetooth(bleDevice);
boolean autoConnect = bleScanRuleConfig.isAutoConnect();
return bleBluetooth.connect(bleDevice, autoConnect, bleGattCallback);
}
return null;
}
这个方法将扫描到的外围设备对象传入,通过一些必要的条件判断之后,调用bleBluetooth.connect()进行连接。我们去看一下BleBluetooth这个类:
public BleBluetooth(BleDevice bleDevice) {
this.bleDevice = bleDevice;
}
上面的BleBluetooth的构造方法是传入一个蓝牙设备对象。由此可见,一个BleBluetooth可能代表你的Android与这一个外围设备整个交互过程,从开始连接,到中间数据交互,一直到断开连接的整个过程。在多连接情况下,有多少外围设备,设备池中就维护着多少个BleBluetooth对象。
MultipleBluetoothController就是控制多设备连接的。它里面有增加和移除设备的方法,如下图的addBleBluetooth和removeBleBluetooth,传入的参数就是BleBluetooth对象,验证了上面的说法。
public synchronized void addBleBluetooth(BleBluetooth bleBluetooth) {
if (bleBluetooth == null) {
return;
}
if (!bleLruHashMap.containsKey(bleBluetooth.getDeviceKey())) {
bleLruHashMap.put(bleBluetooth.getDeviceKey(), bleBluetooth);
}
}
public synchronized void removeBleBluetooth(BleBluetooth bleBluetooth) {
if (bleBluetooth == null) {
return;
}
if (bleLruHashMap.containsKey(bleBluetooth.getDeviceKey())) {
bleLruHashMap.remove(bleBluetooth.getDeviceKey());
}
}
回到BleBlutooth的connect方法:
public synchronized BluetoothGatt connect(BleDevice bleDevice,
boolean autoConnect,
BleGattCallback callback) {
addConnectGattCallback(callback);
isMainThread = Looper.myLooper() != null && Looper.myLooper() == Looper.getMainLooper();
BluetoothGatt gatt;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
gatt = bleDevice.getDevice().connectGatt(BleManager.getInstance().getContext(),
autoConnect, coreGattCallback, TRANSPORT_LE);
} else {
gatt = bleDevice.getDevice().connectGatt(BleManager.getInstance().getContext(),
autoConnect, coreGattCallback);
}
if (gatt != null) {
if (bleGattCallback != null)
bleGattCallback.onStartConnect();
connectState = BleConnectState.CONNECT_CONNECTING;
}
return gatt;
}
可见,最终也是调用了原生API中的BluetoothDevice的connectGatt()方法。在蓝牙原理分析中讲到,连接过程中要创建一个BluetoothGattCallback,用来作为回调,这个类非常重要,所有的 GATT 操作的回调都在这里。而此处的coreGattCallback应该就扮演着这个角色,它是BluetoothGattCallback的实现类对象,对操作回调结果做了封装和分发。
private BluetoothGattCallback coreGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
if (newState == BluetoothGatt.STATE_CONNECTED) {
gatt.discoverServices();
} else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
closeBluetoothGatt();
BleManager.getInstance().getMultipleBluetoothController().removeBleBluetooth(BleBluetooth.this);
if (connectState == BleConnectState.CONNECT_CONNECTING) {
connectState = BleConnectState.CONNECT_FAILURE;
if (isMainThread) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_CONNECT_FAIL;
message.obj = new BleConnectStateParameter(bleGattCallback, gatt, status);
handler.sendMessage(message);
} else {
if (bleGattCallback != null)
bleGattCallback.onConnectFail(new ConnectException(gatt, status));
}
} else if (connectState == BleConnectState.CONNECT_CONNECTED) {
connectState = BleConnectState.CONNECT_DISCONNECT;
if (isMainThread) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_DISCONNECTED;
BleConnectStateParameter para = new BleConnectStateParameter(bleGattCallback, gatt, status);
para.setAcitive(isActiveDisconnect);
para.setBleDevice(getDevice());
message.obj = para;
handler.sendMessage(message);
} else {
if (bleGattCallback != null)
bleGattCallback.onDisConnected(isActiveDisconnect, bleDevice, gatt, status);
}
}
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
BleLog.i("BluetoothGattCallback:onServicesDiscovered "
+ '\n' + "status: " + status
+ '\n' + "currentThread: " + Thread.currentThread().getId());
if (status == BluetoothGatt.GATT_SUCCESS) {
bluetoothGatt = gatt;
connectState = BleConnectState.CONNECT_CONNECTED;
isActiveDisconnect = false;
BleManager.getInstance().getMultipleBluetoothController().addBleBluetooth(BleBluetooth.this);
if (isMainThread) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_CONNECT_SUCCESS;
BleConnectStateParameter para = new BleConnectStateParameter(bleGattCallback, gatt, status);
para.setBleDevice(getDevice());
message.obj = para;
handler.sendMessage(message);
} else {
if (bleGattCallback != null)
bleGattCallback.onConnectSuccess(getDevice(), gatt, status);
}
} else {
closeBluetoothGatt();
connectState = BleConnectState.CONNECT_FAILURE;
if (isMainThread) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_CONNECT_FAIL;
message.obj = new BleConnectStateParameter(bleGattCallback, gatt, status);
handler.sendMessage(message);
} else {
if (bleGattCallback != null)
bleGattCallback.onConnectFail(new ConnectException(gatt, status));
}
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
Iterator iterator = bleNotifyCallbackHashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
Object callback = entry.getValue();
if (callback instanceof BleNotifyCallback) {
BleNotifyCallback bleNotifyCallback = (BleNotifyCallback) callback;
if (characteristic.getUuid().toString().equalsIgnoreCase(bleNotifyCallback.getKey())) {
Handler handler = bleNotifyCallback.getHandler();
if (handler != null) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_CHA_NOTIFY_DATA_CHANGE;
message.obj = bleNotifyCallback;
Bundle bundle = new Bundle();
bundle.putByteArray(BleMsg.KEY_NOTIFY_BUNDLE_VALUE, characteristic.getValue());
message.setData(bundle);
handler.sendMessage(message);
}
}
}
}
iterator = bleIndicateCallbackHashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
Object callback = entry.getValue();
if (callback instanceof BleIndicateCallback) {
BleIndicateCallback bleIndicateCallback = (BleIndicateCallback) callback;
if (characteristic.getUuid().toString().equalsIgnoreCase(bleIndicateCallback.getKey())) {
Handler handler = bleIndicateCallback.getHandler();
if (handler != null) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_CHA_INDICATE_DATA_CHANGE;
message.obj = bleIndicateCallback;
Bundle bundle = new Bundle();
bundle.putByteArray(BleMsg.KEY_INDICATE_BUNDLE_VALUE, characteristic.getValue());
message.setData(bundle);
handler.sendMessage(message);
}
}
}
}
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
Iterator iterator = bleNotifyCallbackHashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
Object callback = entry.getValue();
if (callback instanceof BleNotifyCallback) {
BleNotifyCallback bleNotifyCallback = (BleNotifyCallback) callback;
if (descriptor.getCharacteristic().getUuid().toString().equalsIgnoreCase(bleNotifyCallback.getKey())) {
Handler handler = bleNotifyCallback.getHandler();
if (handler != null) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_CHA_NOTIFY_RESULT;
message.obj = bleNotifyCallback;
Bundle bundle = new Bundle();
bundle.putInt(BleMsg.KEY_NOTIFY_BUNDLE_STATUS, status);
message.setData(bundle);
handler.sendMessage(message);
}
}
}
}
iterator = bleIndicateCallbackHashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
Object callback = entry.getValue();
if (callback instanceof BleIndicateCallback) {
BleIndicateCallback bleIndicateCallback = (BleIndicateCallback) callback;
if (descriptor.getCharacteristic().getUuid().toString().equalsIgnoreCase(bleIndicateCallback.getKey())) {
Handler handler = bleIndicateCallback.getHandler();
if (handler != null) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_CHA_INDICATE_RESULT;
message.obj = bleIndicateCallback;
Bundle bundle = new Bundle();
bundle.putInt(BleMsg.KEY_INDICATE_BUNDLE_STATUS, status);
message.setData(bundle);
handler.sendMessage(message);
}
}
}
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
Iterator iterator = bleWriteCallbackHashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
Object callback = entry.getValue();
if (callback instanceof BleWriteCallback) {
BleWriteCallback bleWriteCallback = (BleWriteCallback) callback;
if (characteristic.getUuid().toString().equalsIgnoreCase(bleWriteCallback.getKey())) {
Handler handler = bleWriteCallback.getHandler();
if (handler != null) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_CHA_WRITE_RESULT;
message.obj = bleWriteCallback;
Bundle bundle = new Bundle();
bundle.putInt(BleMsg.KEY_WRITE_BUNDLE_STATUS, status);
bundle.putByteArray(BleMsg.KEY_WRITE_BUNDLE_VALUE, characteristic.getValue());
message.setData(bundle);
handler.sendMessage(message);
}
}
}
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
Iterator iterator = bleReadCallbackHashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
Object callback = entry.getValue();
if (callback instanceof BleReadCallback) {
BleReadCallback bleReadCallback = (BleReadCallback) callback;
if (characteristic.getUuid().toString().equalsIgnoreCase(bleReadCallback.getKey())) {
Handler handler = bleReadCallback.getHandler();
if (handler != null) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_CHA_READ_RESULT;
message.obj = bleReadCallback;
Bundle bundle = new Bundle();
bundle.putInt(BleMsg.KEY_READ_BUNDLE_STATUS, status);
bundle.putByteArray(BleMsg.KEY_READ_BUNDLE_VALUE, characteristic.getValue());
message.setData(bundle);
handler.sendMessage(message);
}
}
}
}
}
};
在收到连接状态、读、写、通知等操作的结果回调之后,通过消息队列机制,交由相应的Handler去处理。那处理消息的Handler在哪里?举例其中的write操作Handler handler = bleWriteCallback.getHandler();,handler对象被包含在了这个write操作的callback中。
public abstract class BleBaseCallback {
private String key;
private Handler handler;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Handler getHandler() {
return handler;
}
public void setHandler(Handler handler) {
this.handler = handler;
}
}
所有的操作的callback都继承自这个BleBaseCallback抽象类,它有两个成员变量。一个key,标识着这个callback归属于哪一个Characteristic的操作;另一个handler,用于传递底层发来的操作结果,最终将结果交由callback去抛给调用者,完成一次接口回调。
private void handleCharacteristicWriteCallback(BleWriteCallback bleWriteCallback,
String uuid_write) {
if (bleWriteCallback != null) {
writeMsgInit();
bleWriteCallback.setKey(uuid_write);
bleWriteCallback.setHandler(mHandler);
mBleBluetooth.addWriteCallback(uuid_write, bleWriteCallback);
mHandler.sendMessageDelayed(
mHandler.obtainMessage(BleMsg.MSG_CHA_WRITE_START, bleWriteCallback),
BleManager.getInstance().getOperateTimeout());
}
}
上面这段源码解释了这个机制,每一次write操作之后,都会对传入的callback进行唯一性标记,再通过handler用来传递操作结果,同时将这个callback加入这个设备的BleBlutooth对象的callback池中管理。
这样就形成了APP维持一个设备连接池,一个设备连接池管理多个设备管理者,一个设备管理者管理多个不同类别的callback集合,一个callback集合中含有多个同类的不同特征的callback。
运行结果: