史上最强Android 的低功耗蓝牙BLE开发实践

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zgkxzx/article/details/78195351

前言

随着可穿戴设备的越来越火爆,可移动终端设备上的应用也随之热门起来,现在主流的ios和android系统中,对于BLE蓝牙的开发有些差别,ios由于其蓝牙控制的稳定性,开发起来相对稳定简单,而android上面对蓝牙开发可就没那么简单稳定了,在api19后换上新的Bluedroid协议栈后开始支持BLE4.0低功耗蓝牙设备通信,系统对蓝牙支持也差强人意,加上android系统开放性,国内各手机厂商对系统所谓的优化定制后,手机系统对蓝牙的支持就不太稳定了,大部分情况都要对不同手机进行适配处理,导致android开发难度大大增加…

1.BLE蓝牙的特点

  • 近距离通信,典型距离是 10 米以内(BLE4.0)
  • 速度最高可达 24 Mbps
  • 低功耗 (峰值电流15mA,休眠电流 1uA)
  • 低成本
  • 低延迟连接 (3ms)
  • 高可弹性 (多种模式)
  • 高安全性 (128bit AES加密)

2.BLE 名称解释

2.1 GATT/ATT

GATT,全称叫做通用属性配置文件,它是建立在前面说的 ATT 的基础上,对 ATT 进行进一步的逻辑封装,定义数据的交互方式和含义。这是我们做 BLE 开发的时候直接接触的概念。GATT 按照层级定义了三个概念:服务(Service)、特征(Characteristic)和描述(Descriptor)。他们的包含关系如右边这个图所表示的:一个 Service 包含若干个 Characteristic,一个 Characteristic 可以包含若干 Descriptor。而 Characteristic 定义了数值和操作。Characteristic 的操作这几种权限:读、写、通知等权限。我们说的 BLE 通信,其实就是对 Characteristic 的读写或者订阅通知。还有最外面一层,Profile配置文件,把若干个相关的 Service 组合在一起,就成为了一个 Profile,Profile 就是定义了一个实际的应用场景。

2.2 UUID

Service、Characteristic 还有 Descriptor 都是使用 UUID 唯一标示的。具体的表现形式,我们在后面会讲到。我们先来说说 UUID 是什么? UUID 是全局唯一标识,它是 128bit 的值,为了便于识别和阅读,一般标示程如下的形式,8-4-4-12 的16进制标示。

关于 UUID 有一些规范,为了避免冲突,一般都不会自己手动去定义。例如 Android 中提供了 UUID.randomUUID() 来生成一个随机的 UUID。我们也看到,UUID 有点太长了,在低功耗蓝牙中这种数据长度非常受限的情况下,使用起来肯定不方便,所以蓝牙又使用了所谓的 16 bit 或者 32 bit 的 UUID。其实本质上并没有什么 16bit 或者 32 bit UUID,蓝牙 SIG 定义了一个基础的UUID(Bluetooth Base UUID),形式如下。除了 XXXX 那几位意外,其他都是固定,所以说,其实 16 bit UUID 是对应了一个 128 bit 的 UUID。这样一来,UUID 就大幅减少了,例如 16 bit uuid 只有有限的 65536 个,所以 16 bit UUID 并不能随便使用。SIG 已经预先定义了一些 UUID,如果你想添加一些自己的 16 bit 的 UUID,可以花钱买。

2.3 GAP

GAP是通用访问控制配置文件 ,由名字可以看出,它定义了 BLE 整个通信过程中的流程,例如广播、扫描、连接等流程。还定义了参与通信的设备角色,以及他们各自的职能,例如广播数据的 Broadcaster,接收广播的 Observer,还有被连接的“外设” Peripheral 和发起连接的“中心设备” Central。可以看到,参与交互的设备角色都不是对等。详细的交互流程,我们放到后面讲。

3.BLE 蓝牙栈结构

3.1 Arch

这里写图片描述
作为 Android 开发者,我们不必理解 BLE 的协议栈每个细节,这里大概介绍一下协议架构。我们都知道,协议一般都是分层设计的。BLE 协议栈也不例外。我们来看一下这个图。整个协议栈大致分为三部分,从下到上分别为,控制器(Controller)→主机(Host)→应用(Applications)。

控制器:它是协议栈的底层的实现,直接与硬件相关,一般直接集成在 SoC 中,由芯片厂商实现,包括物理层和链路层。 主机:这是协议栈的上层实现,是硬件的抽象,与具体的硬件和厂家无关。 应用层:就是使用 Host 层提供的 API,开发的应用。

3.2 Controller

这里写图片描述
首先是物理层。蓝牙是工作在 2.4GHz 附近,这是工业、科学、医疗 ISM 频段。可以看到它和 WiFi 工作在同一个频段。蓝牙把频段切分为 40 个通道,3 个广播通道,37 个数据通道,按照一定规律跳频通信(高斯频移键控 GFSK)。

在 Host 层和 Controller 之间有一个接口层,简称为 HCI。主机和控制器之间就是通过 HCI 命令和事件交互的。HCI 这一层是协议栈中是可选的,例如在一些简单小型的设备上可能就没有,但是所有的 Android 设备上肯定是有。这是蓝牙上层应用和芯片的交互的必经之路。后面我们会讲到,这一层的 log,能够很好的帮助我们分析和调试问题。

3.3 Host

这里写图片描述
在 Host 部分,协议结构要复杂一些,有逻辑链路控制和适配层,安全管理模块等等。我们重点来看属性协议,简称为 ATT,它是 BLE 通信的基础。ATT 把数据封装,向外暴露为“属性”,提供“属性”的为服务端,获取“属性”的为客户端。ATT 是专门为低功耗蓝牙设计的,结构非常简单,数据长度很短。

3.Android BLE

3.1 Android Ble 开发

这里写图片描述
从 Android 4.3 Jelly Bean,也就是 API 18 才开始支持低功耗蓝牙。这时支持 BLE 的 Central 模式,也就是我们在上面 GAP 中说的,Android 设备只能作为中心设备去连接其他设备。从 Android 5.0 开始才支持外设模式。

Android SDK 中 BLE 相关的 API 都在 android.bluetooth.* 下面,同时在 Android 5.0 也引入了一些也需要用到 android.bluetooth.le* 下面的 API。

例如,在前面介绍的 GATT 定义的一些概念,都有对应的类,例如 BluetoothGatt/BluetoothGattService/BluetoothGattCharacteristic 等。有了前面的介绍,我们就能很容易的理解这些类的作用。

另外,要在 APP 中使用蓝牙功能,需要在 Manifest 中申请蓝牙相关的权限。在 Android 6.0 及以上平台中,还需要申请定位权限。为什么会这样?因为 BLE 确实有定位的能力,我们后面会讲到。

3.2 Android Ble 底层架构

这里写图片描述
Android 上蓝牙实现的架构。我们来看右边这个图,这是 Android 上一个非常经典的层级结构。最下面硬件部分,可以使用各厂家的具体实现,通过硬件抽象层(HAL)接口统一连接到 Android AOSP 中,通过 JNI 提供 Java 访问接口。蓝牙服务运行在 com.android.bluetooth 进程中,最后通过 Binder 机制向客户端,也就是 APP 提供相关的 API。

关于蓝牙协议栈,这里多说一点。Android 4.2 以前用的是老牌协议栈实现 BlueZ,4.2 开始换成了由 Google 和 Broadcom(博通)联合开发的 BlueDroid,专门在 Android 平台使用。BlueDroid 作为全新的实现,功能不是完善,我们可以看到在 4.3 以后才支持 BLE,在 5.0 以后才支持外设模式,到目前为止,功能其实还是不是很完善。也有一些 Bug。
这里写图片描述
介绍一下 Android 中 BLE 操作的过程,APP 发起一个 BLE 操作,然后理解返回,操作结果通过回调上报。操作被封装为一个消息,然后放到协议栈的消息队列中,有一个独立的线程获取消息进行处理,这里非常类似于我们熟知的 Looper 和 Handler 机制。

因为是使用消息机制,回调的时候必须知道通知哪个客户端?客户端发起请求之前,首先要向协议栈注册客户端,注册成功以后,返回一个 clientIf,这是一个整型,是客户端在协议栈的一个句柄,客户端的后续操作,都只需要带上这个 clientIf 句柄即可。

在操作完成的时候,一般都有一个显式的停止操作,用来释放前面的申请的 clientIf 和资源。如果不能正确的释放,不仅会造成内存泄漏,而且可能会导致后续所有的 BLE 操作都是不能做了。因为这个 clientIf 是有限,在现在蓝牙协议栈中只有 32 个,而且是Android 上所有 APP 共用的。当这些资源用完以后,只有通过杀掉对应的 APP 或者重启蓝牙才能恢复。

3.3 Ble 蓝牙广播

这里写图片描述
我们所说的广播数据其实包含两部分:Advertising Data(广播数据) 和 Scan Response Data(扫描响应数据)。通常情况下,广播的一方,按照一定的间隔,往空中广播 Advertising Data,当某个监听设备监听到这个广播数据时候,会通过发送 Scan Response Request,请求广播方发送扫描响应数据数据。这两部分数据的长度都是固定的 31 字节。在 Android 中,系统会把这两个数据拼接在一起,返回一个 62 字节的数组。

广播数据包的结构如这个图所示(动画)。广播包中是包含一个一个的小 AD structure,每个 AD structure 是一个完整的数据,它的结构是:第一个字节表示长度 n,后面紧接 n 个字节的数据。数据部分第一个字节表示数据类型,也就是后面的数据含义,后面 n - 1 个字节表示真实数据。例如 0x08 是设备的名字,后面的数据就是设备名字的 UTF-8 编码。

这些广播数据可以自己手动去解析,在 Android 5.0 也提供 ScanRecord 帮你解析,直接可以通过这个类获得有意义的数据。

广播中可以有哪些数据类型呢?设备连接属性,标识设备支持的 BLE 模式,这个是必须的。设备名字,设备包含的关键 GATT service,或者 Service data,厂商自定义数据等等。

这里我们也可以看到,广播数据只有最多62字节,所以广播数据空间每个直接都非常珍贵。一般都把哪些数据放到广播中呢?原则上是广播中要放一些能够表达设备身份的数据,还有一些需要暴露的必要数据。因为空间的限制,所以基于广播做不了太复杂的应用,到了 Bluetooth 5.0,据说要大幅扩展广播数据的容量,扩展到 512 字节,这时想象空间就比较大了。

另外,再顺带提一下,无线通信中基本都有信号强度的概念 — 也就是 RSSI,RSSI 单位是 dB,通过 RSSI 能够大致推测出距离的远近。但是这个在 Android 设备上非常不靠谱,RSSI 的值波动很大,跟环境和手机的角度关系很大。
这里写图片描述
我们来直观看一下扫描到的结果是什么样。例如我们使用一个 APP 扫描,扫到一个设备,这些内容都是从广播数据中解析出来的,例如设备名字,设备类型,GATT 服务数据,产商自定义数据等。原始数据是这样的(动画),这里是一个 62 字节的16进制标示,下面这个表格中,每一行就是一个 AD Structure。

3.4 android 蓝牙广播扫描

我们来看一下 Android 作为接收者怎么接收广播数据,扫描设备。代码其实很简单,首先创建一个 LeScanCallback,用来接收收到广播以后,回调上报数据。然后会用 BluetoothAdapter 的 startLeScan 来开始扫描,需要停止扫描的时候,使用 stopLeScan 来停止。

我么来详细看一下这个回调函数,onLeScan,有 BluetoothDevice 这个参数,代表扫描到的设备,关键是设备的的 MAC 地址信息。然后就是 RSSI,表示扫描到的设备的信号强度,接下来 scanRecord 就是我们前面介绍的广播数据,这个数据的长度是62字节。值得提的一点是,BLE 所有回调函数都不是在主线程中的。

这里有几点需要注意,这里在不需要扫描以后,一定要 stopLeScan,而且 start 和 stop 中传入的 LeScanCallback 一定要是同一个,因为 LeScanCallback 就是我们客户端的标识。否者就会出现我们前面说的 clientIf 不释放的问题。在 Android 开发中,我们经常会使用匿名内部类来做参数,在这里就千万不要这么做。

在 Android 5.0 中,提供了全新的扫描 API — BluetoothLeScanner,它提供了对扫描更加精细的控制。

除了这种方法,还可以使用经典蓝牙扫描的方式,BluetoothAdapter 的 startDiscovery(),然后通过 BroadcastReceiver 来接收收到的广播。如果只是做 BLE 的开发,不建议使用这个方法,这是一个非常重的操作,灵活性非常差。
这里写图片描述
接下来我们来看一下扫描的工作流程。首先 APP 发起扫描请求,通过蓝牙的 Service 发送请求给蓝牙芯片。蓝牙芯片开始扫描,扫描到了设备,就通过回调上报。我们知道,扫描真正执行实在 BT 芯片中,只要 APP 发送了请求下去以后,Android 系统就可以休眠了,也就是我们常说的 AP (Application Processor),等扫描到了设备以后,底层 BP (Baseband Processor)就会唤醒上层 AP,执行回调通知到 APP,(动画)就像我们图中红色框标出的这样。这里有一个问题,随着我们周围的 BLE 设备逐渐增多,频繁扫描到设备,系统就会被频繁的唤醒,甚至睡眠不下去,从而导致耗电严重。

为了避免这种问题,耗电的问题。我们需要尽可能少的使用扫描。即使需要扫描,我们也希望尽可能少的上报扫描到的设备。这里就可以使用 Android 5.0 上提供的新接口,设置 ScanFilter,通过一定的规则过滤,只有扫描到了符合我们的规则的设备才上报,或者通过设置延迟上报,从而减少唤醒系统的次数。

这里总结一下扫描中一些建议。1、首先,尽可能使用新的 API,功能更强大;2、尽可能少地扫描,因为毕竟扫描是一个比较重的操作,耗电,也会减慢 BLE 连接速度;3、扫描的时候,尽量设置 ScanFilter,只扫描那些你感兴趣的设备,而不是全盘扫描;4、正确使用 API,特别是合理停止扫描,防止资源泄漏。

3.3 Ble 蓝牙连接

这里写图片描述
BLE 连接的建立是通过 GAP 来协商和创建连接。Central 设备发起连接,外设接收连接请求,并协商连接参数。

前面我们介绍了 GATT,GATT 核心内容就是 Service、Characteristic 以及 Descriptor。每个 BLE 外设,根据自己的功能,向外暴露 Service 等。其实最重要的获取 Service 中的 Characteristic,Characteristic 可以被读、写、还有变化的时候有通知,这样就实现了双向的通信。

未完待续。。。

猜你喜欢

转载自blog.csdn.net/zgkxzx/article/details/78195351