前言
经过一段时间的蓝牙开发接触,想总结下现实Android ble模块开发的流程以及遇到的问题。
1.初识BLE
Android 4.3(API Level 18)开始引入Bluetooth Low Energy(BLE,低功耗蓝牙)的核心功能并提供了相应的 API, 应用程序通过这些 API 扫描蓝牙设备、查询 services、读写设备的 characteristics(属性特征)等操作。
Android BLE 使用的蓝牙协议是 GATT 协议,有关该协议的详细内容可以参见蓝牙官方文档连接1。以下我引用一张官网的图来大概说明 Android 开发中我们需要了解的一些 Bluetooth Low Energy 的专业术语。
2.GATT,Service,Characteristic 基本概念
首先在开发ble前,需要搞明白上面三个概念,对于接下来的具体开发有很大的帮助。
2.1 GATT
如图所示,GATT(Generic Attribute Profile),描述了一种使用ATT的服务框架
该框架定义了服务(Server)和服务属性(characteristic)的过程(Procedure)及格式
Procedure定义了characteristic的发现、读、写、通知(Notifing)、指示(Indicating)
及配置characteristic的广播
GATT中最上层是Profile,Profile由一个或多个服务(Service)组成 服务是由Characteristics组成,或是其他服务的引用(Reference) Characteristic包含一个值(Value),可能包含该Value的相关信息
2.2 Service
一个低功耗蓝牙设备可以定义许多 Service, Service 可以理解为一个功能的集合。设备中每一个不同的 Service 都有一个 128 bit 的 UUID 作为这个 Service 的独立标志。蓝牙核心规范制定了两种不同的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
2.3 Characteristic
在 Service 下面,又包括了许多的独立数据项,我们把这些独立的数据项称作 Characteristic。同样的,每一个 Characteristic 也有一个唯一的 UUID 作为标识符。在 Android 开发中,建立蓝牙连接后,我们说的通过蓝牙发送数据给外围设备就是往这些 Characteristic 中的 Value 字段写入数据;外围设备发送数据给手机就是监听这些 Charateristic 中的 Value 字段有没有变化,如果发生了变化,手机的 BLE API 就会收到一个监听的回调。
3.android BLE开发
接下来进入本章的主题,ble的具体开发流程,在具体开发前,我们分如下几步去了解
3.1 android BLE 开发流程图
和很多开发遇到的问题一样,首先需要一个整体流程的大体了解,如下所示:
3.2 android BLE API简介
通过上图的一些流程,首先我们需要了解下如的使用
3.2.1 BluetoothAdapter
BluetoothAdapter 拥有基本的蓝牙操作,例如开启蓝牙扫描,使用已知的 MAC 地址 (BluetoothAdapter getRemoteDevice)实例化一个 BluetoothDevice 用于连接蓝牙设备的操作等等。
3.2.2 BluetoothDevice
代表一个远程蓝牙设备。这个类可以让你连接所代表的蓝牙设备或者获取一些有关它的信息,例如它的名字,地址和绑定状态等等。
3.2.3 BluetoothGatt
这个类提供了 Bluetooth GATT 的基本功能。例如重新连接蓝牙设备,发现蓝牙设备的 Service 等等。
3.2.4 BluetoothGattService
这一个类通过 BluetoothGatt getService 获得,如果当前服务不可见那么将返回一个 null。这一个类对应上面说过的 Service。我们可以通过这个类的 getCharacteristic(UUID uuid) 进一步获取 Characteristic 实现蓝牙数据的双向传输。
3.2.5 BluetoothGattCharacteristic
这个类对应上面提到的 Characteristic。通过这个类定义需要往外围设备写入的数据和读取外围设备发送过来的数据。
4.showTime-具体实操代码
接下来就根据上面的流程图,来具体来实操下;
4.1 step-0: 权限申请与检查
在Android5.0之前,是默认申请GPS硬件功能的。而在Android 5.0 之后,需要在manifest 中申明GPS硬件模块功能的使用。
在 Android 6.0 及以上,还需要打开位置权限。如果应用没有位置权限,蓝牙扫描功能不能使用(其它蓝牙操作例如连接蓝牙设备和写入数据不受影响)。
4.2 step-1:连接蓝牙前的初始化工作
根据流程图可知:
1.获取bluetoothAdapter对象
2.检查蓝牙是否开启
4.3 step-2:扫描蓝牙设备
扫描获取周围的蓝牙的方法有两种
开启蓝牙后,我们可以获取很多设备信息,mac,uuid等,方法2与方法1的区别,在于添加特定拦截器,对特定UUID Service的蓝牙设备拦截,我开发中,经常需要针对特定蓝牙产品做连接。
在 LeScanCallback 回调的方法中,第一个参数是代表蓝牙设备的类,可以通过这个类建立蓝牙连接获取关于这一个设备的一系列详细的参数,例如名字,MAC 地址等等;第二个参数是蓝牙的信号强弱指标,通过蓝牙的信号指标,我们可以大概计算出蓝牙设备离手机的距离。计算公式为:d = 10^((abs(RSSI) - A) / (10 * n));第三个参数是蓝牙广播出来的广告数据。
当执行上面的代码之后,一旦发现蓝牙设备,LeScanCallback 就会被回调,直到 stopLeScan 被调用。出现在回调中的设备会重复出现,所以如果我们需要通过 BluetoothDevice 获取外围设备的地址手动过滤掉已经发现的外围设备。
注意:使用扫描的时候,由于比较耗能,所以当连接到自己的设备后,进行扫描方法关闭;
4.4 step-3:连接蓝牙设备
连接蓝牙设备可以通过 BluetoothDevice.ConnectGatt 方法连接,也可以通过 BluetoothGatt.connect 方法进行重新连接
参数说明:第二个参数:设置为true的时候,如果设备断开连接,会不断的去请求再次连接,否则设置为false的情况下,只是进行一次连接尝试。
第三个参数:连接后进行的一系列操作的回调,例如连接和断开连接的回调,发现服务的回调,成功写入数据,成功读取数据的回调等等。
当调用蓝牙的连接方法之后,蓝牙会异步执行蓝牙连接的操作,如果连接成功会回调 BluetoothGattCalbackl onConnectionStateChange 方法。这个方法运行的线程是一个 Binder 线程,所以不建议直接在这个线程处理耗时的任务,因为这可能导致蓝牙相关的线程被阻塞。
在onConnectionStateChange方法中:
参数1:GATT服务的连接类。
参数2:是否成功执行了连接操作,只有当改参数为BluetoothGatt.GATT_SUCCESS的情况下,参数三的状态才是有效的。
参数3:代表当前设备的连接状态,如果 newState == BluetoothProfile.STATE_CONNECTED 说明设备已经连接,可以进行下一步的操作了(发现蓝牙服务,也就是 Service)。当蓝牙设备断开连接时,这一个方法也会被回调,其中的 newState == BluetoothProfile.STATE_DISCONNECTED。
4.5 step-4:ble模块通讯
读取数据:
当我们发现服务之后就可以通过 BluetoothGatt getService 获取 BluetoothGattService,接着通过 BluetoothGattService getCharactristic 获取 BluetoothGattCharactristic。
通过 BluetoothGattCharactristic readCharacteristic 方法可以通知系统去读取特定的数据。如果系统读取到了蓝牙设备发送过来的数据就会调用 BluetoothGattCallback onCharacteristicRead 方法。通过 BluetoothGattCharacteristic getValue 可以读取到蓝牙设备的数据
数据写入:
和读取数据一样,在执行写入数据前需要获取到 BluetoothGattCharactristic。接着执行一下步骤:
1.调用 BluetoothGattCharactristic setValue 传入需要写入的数据(蓝牙最多单次1支持 20 个字节数据的传输,如果需要传输的数据大于这一个字节则需要分包传输)。
2.调用 BluetoothGattCharactristic writeCharacteristic 方法通知系统异步往设备写入数据。
3.系统回调 BluetoothGattCallback onCharacteristicWrite 方法通知数据已经完成写入。此时,我们需要执行 BluetoothGattCharactristic getValue 方法检查一下写入的数据是否我们需要发送的数据,如果不是按照项目的需要判断是否需要重发。
以下是示例代码:
向蓝牙设备注册监听实现实时读取蓝牙设备的数据
BLE app通常需要获取设备中characteristic 变化的通知。下面的代码演示了怎么为一个Characteristic 设置一个监听。
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
值得注意的是,除了通过 BluetoothGatt setCharacteristicNotification 开启 Android 端接收通知的开关,还需要往 Characteristic 的 Descriptor 属性写入开启通知的数据开关使得当硬件的数据改变时,主动往手机发送数据。
4.6 step-5:断开服务
当我们连接蓝牙设备完成一系列的蓝牙操作之后就可以断开蓝牙设备的连接了。通过 BluetoothGatt disconnect 可以断开正在连接的蓝牙设备。
BluetoothGatt disconnect 断开蓝牙的连接,紧接着在BluetoothGattCallback onConnectionStateChange 执行 BluetoothGatt close 方法释放资源。
4.7 step-5:ble开发注意事项(截取网络)
1 蓝牙的写入操作( 包括 Descriptor 的写入操作), 读取操作必须序列化进行. 写入数据和读取数据是不能同时进行的, 如果调用了写入数据的方法, 马上调用又调用写入数据或者读取数据的方法,第二次调用的方法会立即返回 false, 代表当前无法进行操作. 详情可以参考 蓝牙读写操作返回 false,为什么多次读写只有一次回调?*
2 Android 连接外围设备的数量有限,当不需要连接蓝牙设备的时候,必须调用 BluetoothGatt close 方法释放资源。详细的参考可以看这里 Android BLE 蓝牙开发的各种坑*
3 蓝牙 API 连接蓝牙设备的超时时间大概在 20s 左右,具体时间看系统实现。有时候某些设备进行蓝牙连接的时间会很长,大概十多秒。如果自己手动设置了连接超时时间(例如通过 Handler postDelay 设置了 5s 后没有进入 BluetoothGattCallback onConnectionStateChange 就执行 BluetoothGatt close 操作强制释放断开连接释放资源)在某些设备上可能会导致接下来几次的连接尝试都会在 BluetoothGattCallback onConnectionStateChange 返回 state == 133。另外可以参考这篇吐槽 Android 中 BLE 连接出现“BluetoothGatt status 133”的解决方法*
4 所有的蓝牙操作使用 Handler 固定在一条线程操作,这样能省去很多因为线程不同步导致的麻烦
6.参考
https://www.jianshu.com/p/3a372af38103
https://blog.csdn.net/s13383754499/article/details/78436023
http://blog.chinaunix.net/uid-21411227-id-5750680.html
GATT协议:https://www.cnblogs.com/smart-mutouren/p/5937990.html