Android蓝牙通信过程详解

蓝牙无线技术是一种全球通用的短距离无线技术,具有耗电量低、成本低、安全性、稳定性、易用性等优点,尤其在物联网设备上的占有率非常高,因此我们有必要对蓝牙做深入的了解。本文从蓝牙和Android手机的一次通信过程为引,讲解Android蓝牙通信上的一些问题和原理,以及调用方法。如有描述不当的地方还请指出。

例子

假设Android手机A要给蓝牙设备B发送一条“hello”的消息,然后B会给A回一条“world”。我们应该怎么做呢?

过程

过程可以大致分为三步:

寻找设备

连接

通信

寻找设备

A要怎么找到B呢,一般是由设备B按照一定的周期广播数据包,然后A和B指定好协议,看广播数据里有没有协议约定的数据,有的话则说明找到了B。

广播的数据包分为四种

ADV_IND

ADV_DIRECT_IND

ADV_NONCONN_IND

ADV_SCAN_IND

一般发送的是ADV_IND包(可参考蓝牙协议分析(5)_BLE广播通信相关的技术分析。

Android设备通过系统提供的API开始接受广播数据,

然后在callback里获取广播数据

ScanResult就是当前接收到的广播里数据,在5.0以及5.0以上的api,会帮我们解析广播的数据,但是5.0以下的话,只是会把整个广播数据传回来。下面大致讲解下广播协议数据。

广播协议分析:

官方文档:

https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile

广播的数据,按 len 1字节,TYPE1字节, data (len -1)字节的顺序组依次组织,key的含义在上面的表格中已经给出。 下面举个例子,

假设下面是设备B的广播数据

解析数据

另外,设备的广播频率是可以自己定义的,从几ms到几百ms不等,广播的频率决定了手机发现设备的快慢,

有个软件叫"nRF Connect",可以很方便的观看蓝牙设备的广播速度。

软件截图:

上图的Adv. Interval就是广播的间隔,上文讲到的所有东西都可以在这个软件上观察到。

连接过程

蓝牙分为5种工作状态,

准备(standby):就绪状态,准备转变为其他状态

广播(advertising):向外发送数据的状态

监听扫描(Scanning): 扫描状态的时候,在接受到ADV_IND包是,会发送SCAN_REQ包,可以获得更多的信息。

发起连接(Initiating):发起连接状态,在ADV_IND或者ADV_DIRECT_IND之后,会发送CONNECT_REQ包,从而建立连接。

已连接(Connected):根据连接时约定的参数,发送CONNECT_EVENT,保持连接不断开。

具体工作流程如下:

可被连接的设备(Advertiser),按照一定的周期广播ADV_IND或者ADV_DIRECT_IND包(可参考“蓝牙协议分析(5)_BLE广播通信相关的技术分析”)。

主动连接的设备(Initiator),在收到广播包之后,会回应一个CONNECT_REQ请求,该请求携带了可决定后续“通信时序”的参数,例如双方在哪一个时间点、哪一个Physical Channel收发数据,等等。

Initiator在发出CONNECT_REQ数据包之后,自动转变为Connection状态,成为Master角色(注意:这是“自动”的,不需要等待另一方的回应)。同样,Advertiser在收到CONNECT_REQ请求之后,也自动转变为Connection状态,成为Slave角色。

此后,双方按照CONNECT_REQ参数所给出的约定,定时到切换到某一个Physical Channel上,按照Master->Slave然后Slave->Master的顺序,收发数据,直至连接断开。

在Android手机中,连接的代码非常简单,只有一个api可以调用,如下:

之后在bluetoothGattCallback的onConnectionChange的回调里,就可以知道连接的结果。

通信

说道通信,就要讲到跳频。跳频的原理是

蓝牙这边在通信的时候,会约定一个随机的频率(也不是完全随机,有一个范围)。双方在这个频率上通信,这样通信更稳定。所以Ble在扫描和连接两个步骤中,可能耗时比较长,但是开始通信之后,速度和稳定性都会加快不少。

而跳频的参数,是在连接命令里发过去的。

蓝牙设备,都会注册service和characteristic,并且用uuid标识。

service 可以理解为一个服务,在BLE从机中有多个服务,电量信息,系统服务信息等,每一个service中包含了多个characteristic特征值,每一个具体的characteristic特征值才是BLE通信的主题。

characteristic特征值:BLE主机从机通信均是通过characteristic进行,可以将其理解为一个标签,通过该标签可以读取或写入相关信息。

大家可以理解为service就是java里的class,characteristic是class里的public方法。所以我们应该先找到class,再调用其方法。

再往下想,写和读肯定是两个方法,所以也会是两个characteristic。

service和characteristic的uuid都是通信之前双方约定好的。android端在上文说的discoverService之后,查看有没有对应的uuid,如果有,就可以发起通信了。

setCharacteristicNotification 成功后,就可以在onCharacteristicChanged方法收到回调了。

蓝牙通信的过程,就是往writeCharacteristic里写数据,然后监听readCharacteristic的返回。这两个操作保持有序进行,就可以通信了。

过程总结

针对上文说的例子, 画个图来总结一下

蓝牙采坑总结

scan过程

据测试,Android5.0以下的手机,是不支持128位的uuid的,只支持16位的。如果过滤的uuid填入128位的话,是搜索不到设备的。所以要分别判断。

部分手机(三星遇到过),在蓝牙关闭情况下,仍然可以使用ble功能,但是经常会有问题,所以请务必打开蓝牙再开始通信。

部分手机,如果1次scan不到,后续都不会scan到了,这时要停止scan,重新启动一次。

连接错误

国内Android手机的蓝牙不知道为何,非常不稳定。在连接的过程中,会有各种各样的错误,我用了github上总结的错误,

https://github.com/Twelvelines/AndroidMuseumBleManager/blob/ef5e866c0aa8955320bac7ee9884f60be5bbe1d5/bluetooth-manager-lib/src/main/java/com/blakequ/bluetooth_manager_lib/connect/GattError.java

或者

https://blog.csdn.net/ocean20/article/details/65431478

这些错误会频繁的遇到,所以为了成功率更高,需要在连接的时候增加重试机制。

遇到这些错误,立刻断开连接,等待1-2秒后再次尝试连接。

具体见以下代码:

onConnectError的回调里,可以直接去重试连接。这样可以提高成功率。但是重新连接之前一定要close当前连接,否则会出现各种问题。

但是重试随之而来的问题是老的gatt对象可能仍然会回调(系统回调不知道什么时候回来),所以在接收回调的时候,最好判断一下当前的gatt对象是否是最新的。

然后还有就是通信问题,通信相对于连接来说,是稳定很多的,但是仍然会有一定的几率,收不到消息。所以硬件设备和手机的蓝牙代码,最好有一方有重试机制,一般来说手机重试就足够了。

参考文献

请关注我的后续文章哦:Android连接的源码分析

今天是520哦,欢迎打赏!

  • 发表于:
  • 原文链接:https://kuaibao.qq.com/s/20180520G1BYC000?refer=cp_1026

同媒体快讯

相关快讯

扫码关注云+社区