Android蓝牙通信过程详解

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

例子

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

过程

过程可以大致分为三步:

  • 寻找设备
  • 连接
  • 通信

寻找设备

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

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

//要先停止上一次的scan,不然无法启动新的scan
bluetoothAdapter.getBluetoothLeScanner().stopScan(bleCallback);
bluetoothAdapter.getBluetoothLeScanner().startScan(getFilters(), getSettings(), bleCallback);

然后在callback里获取广播数据

  private ScanCallback bleCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, final ScanResult result) {
           
        }
}

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

广播协议分析:

image.png

官方文档:
https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile
广播的数据,按 len 1字节,TYPE1字节, data (len -1)字节的顺序组依次组织,key的含义在上面的表格中已经给出。 下面举个栗子
假设下面是设备B的广播数据

02 01 06 05 03 F6 FE F5 FE 0E 09 5B 52 4F 41 44 42 49 54 5D 00 00 00 00 0D FF AA 00 66 EE 06 66 1F FF FF 25 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 

解析数据

02 01 06           //长度2,type 1 表示设备功能, 6表示110 -> 普通发现模式,且不支持BR/EDR
05 03 F6 FE F5 FE  //长度5,type 03,表示16位uuid列表, F6FE和F5FE两组uuid
0E 09 5B 52 4F 41 44 42 49 54 5D 00 00 00 00  长度 0E,即14, type 9,即设备名称,后面试试字符串转16进制
0D FF AA 00 66 EE 06 66 1F FF FF 25 04 00 , FF表示自定义数据,即蓝牙设备的自定义的数据

另外,设备的广播频率是可以自己定义的,从几ms到几百ms不等,广播的频率决定了手机发现设备的快慢,
有个软件叫"nRF Connect",可以很方便的观看蓝牙设备的广播速度。下载地址
软件截图:

221526440023_.pic.jpg

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

连接过程

蓝牙分为5种工作状态,

  • 准备(standby):就绪状态,准备转变为其他状态
  • 广播(advertising):向外发送数据的状态
  • 监听扫描(Scanning): 扫描状态的时候,在接受到ADV_IND包是,会发送SCAN_REQ包,可以获得更多的信息。
  • 发起连接(Initiating):发起连接状态,在ADV_IND或者ADV_DIRECT_IND之后,会发送CONNECT_REQ包,从而建立连接。
  • 已连接(Connected):根据连接时约定的参数,发送CONNECT_EVENT,保持连接不断开。

具体工作流程如下:

  1. 可被连接的设备(Advertiser),按照一定的周期广播ADV_IND或者ADV_DIRECT_IND包(可参考“蓝牙协议分析(5)_BLE广播通信相关的技术分析”)。
  2. 主动连接的设备(Initiator),在收到广播包之后,会回应一个CONNECT_REQ请求,该请求携带了可决定后续“通信时序”的参数,例如双方在哪一个时间点、哪一个Physical Channel收发数据,等等。
  3. Initiator在发出CONNECT_REQ数据包之后,自动转变为Connection状态,成为Master角色(注意:这是“自动”的,不需要等待另一方的回应)。同样,Advertiser在收到CONNECT_REQ请求之后,也自动转变为Connection状态,成为Slave角色。
  4. 此后,双方按照CONNECT_REQ参数所给出的约定,定时到切换到某一个Physical Channel上,按照Master->Slave然后Slave->Master的顺序,收发数据,直至连接断开。

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

private BluetoothGatt connectGattCompat(BluetoothGattCallback bluetoothGattCallback, BluetoothDevice device, boolean autoConnect) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            return device.connectGatt(context, autoConnect, bluetoothGattCallback, TRANSPORT_LE);
        } else {
            return device.connectGatt(context, autoConnect, bluetoothGattCallback);
        }
    }

之后在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,如果有,就可以发起通信了。

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            listener.onServicesDiscovered(status);
        }
         @Override
         public void onServicesDiscovered(final int status) {
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
                service = getGatt().getService(ServerUUID);
                if (null != service) {
                    BluetoothGattCharacteristic read_characteristic = service.getCharacteristic(readDataUUID);
                    if (null != read_characteristic) {
                        int properties = read_characteristic.getProperties();
                        if ((properties | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
                            getGatt().setCharacteristicNotification(read_characteristic, true);
                            }
                      }
                   }
             }
        }

setCharacteristicNotification 成功后,就可以在onCharacteristicChanged方法收到回调了。
蓝牙通信的过程,就是往writeCharacteristic里写数据,然后监听readCharacteristic的返回。这两个操作保持有序进行,就可以通信了。

过程总结

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


蓝牙通信完整流程.png

蓝牙采坑总结

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秒后再次尝试连接。
具体见以下代码:

 public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);

            BleLog.d("onConnectionStateChange", "status: " + status + " newState: " + newState);
            if (status != BluetoothGatt.GATT_SUCCESS) {
                BleLog.e(TAG, "连接状态异常->" + GattError.parseConnectionError(status));
                disconnectInternal(false);
                onConnectError(status);
               
            }

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

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

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

参考文献

后续会对android连接的源码分析一波

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348

推荐阅读更多精彩内容

  • 背景 蓝牙历史说到蓝牙,就不得不说下蓝牙技术联盟(Bluetooth SIG),它负责蓝牙规范制定和推广的国际组织...
    徐正峰阅读 12,262评论 6 33
  • 蓝牙 蓝牙的波段为2400-2483.5MHz(包括防护频带)。这是全球范围内无需取得执照(但定不是无管制的)的工...
    苏永茂阅读 6,125评论 0 11
  • 初识低功耗蓝牙 Android 4.3(API Level 18)开始引入Bluetooth Low Energy...
    JBD阅读 112,560评论 46 342
  • BLE简述 蓝牙是一套非常庞大复杂的协议栈,通俗的说就是一组应用于无线系统通信的约定,各个厂家根据这个约定生产出了...
    dxdingdu阅读 11,310评论 5 37
  • 寄生式发展 在南美的热带雨林里有一种名叫“绞杀榕”的植物,它紧紧缠绕在高大的树木上,通过夺取对方的阳光和养分而生存...
    阳明心阅读 561评论 0 0