zoukankan      html  css  js  c++  java
  • Android 低功耗蓝牙BLE 开发注意事项

    基本概念和问题

    1、蓝牙设计范式?

    当手机通过扫描低功耗蓝牙设备并连接上后,手机与蓝牙设备构成了客户端-服务端架构。手机通过连接蓝牙设备,可以读取蓝牙设备上的信息。手机就是客户端,蓝牙设备是服务端。

    手机做为客户端可以连接多个蓝牙设备,所以手机又可以叫中心设备(Central),蓝牙设备叫外围设备(Peripheral)。

    还有另外一个称谓:手机叫主设备(Master),蓝牙设备叫从设备(Slave)。

    Android4.3 开始支持低功耗蓝牙,此版本只支持单模式:同时只能工作在中心设备模式或者外围设备模式

    Android5.0 开始支持主从一体。换句话说,手机可以扫描并进行连接,连接着蓝牙设备的同时,又可以作为广播者,发送蓝牙广播,等待别的支持蓝牙扫描的设备连接自己。

    2、从设备连接数量的问题?

    理论层面

    从经典蓝牙时代开始,蓝牙有个星型拓扑的概念,一个主设备(Central)外围有七个从设备(Peripheral),蓝牙核心文档规定了:同一时间只允许七个从设备进行连接。

    系统层面

    Android系统蓝牙协议栈源码中也使用了这个数值,Android手机的蓝牙芯片都是双模蓝牙芯片,即同时支持经典蓝牙和低功耗蓝牙,分析过协议栈源码,建立连接的过程经典蓝牙和低功耗蓝牙是公用的代码,所以手机作为主设备(Central)时,从设备(Peripheral)同时连接的最大值就是7台设备。

    实际情况

    开发Android客户端以来,遇到的实际情况就是,部分手机(偏低端一些机型,比如采用联发科的解决方案,手机的GPS、蓝牙、Wi-Fi等都是共模的,都集成在一个芯片上)不能达到7台设备。

    3、ATT是什么?

    ATT是属性协议(Attribute Protocol),定义了客户端与服务器如何相互发送符合标准的消息。

    4、GATT是什么?

    GATT是通用属性规范(Generic Attribute Profile),定义了如何发现与使用服务、特性与描述符的标准方法。

    GATT的规程基本分为:

    发现规程:发现服务(Service)、发现特征(Characteristic)等

    客户端发起规程:读取特征(readCharacteristic)、写入特征(writeCharacteristic)等

    服务端发起规程:比如通知(Notification)和指示(Indicate)

    5、低功耗蓝牙频段和信道问题

    蓝牙工作在2.45G ISM频段,波段范围是:2400-2483.5 MHz

    信道:低功耗蓝牙使用用40个RF信道,这些RF信道中心频率为:f=2402+k*2 MHz, k=0, ... ,39

    因为调试指数放宽,低功耗蓝牙的信道与经典蓝牙有所不同。每个信道的功率谱更宽,因此,为了避免邻近信道干扰,低功耗蓝牙的信道宽度为2MHz,而不是经典蓝牙的1MHz

    低功耗蓝牙使用的2.45GHz频段已经非常拥挤,仅仅考虑标准的技术就包括:经典蓝牙、低功耗蓝牙、IEEE 802.11、IEEE802.11b、IEEE802.11g、IEEE802.11n以及IEEE 802.15.4。另外,许多私有的无线电同样使用这个频段,包括X10视频中继器、无线报警、键盘和鼠标等。许多其他设备也会在该频段发射噪声,例如街灯和微波炉。

    对于2.45G这个频段有个很尴尬的特性:怕水。

    举个例子:微波炉的工作原理就是向带有水分的物体发射2.45GHz的微波,利用了水分子能够很好的吸收2.45GHz电磁波,将电磁波能量转换成为自身的热量。也正式这个特性,在很长一段时间里,2.4GHz信道不被人所重视,下雨、雾气甚至是潮湿的墙壁都能吸收无线电波,使传输距离大大衰减。估计这也是全球都对此频段不屑而免费开放的理由之一吧。当人站在两块蓝牙设备中间,并且距离其中一块模块1米左右时,能够检测信号衰减了将近10dB左右!因为人体的70%左右是水分。

     
     
     
     
     
     
     
     
     
    蓝牙链路层信道映射图:数据信道:0-36;广播信道:37、38、39
    有人也许好奇为啥广播信道这么设计,请看下图:
    链路层信道与Wi-Fi信道共存

    是为了尽量避开冲突频段,增加通信的鲁棒性。

    6、关于autoConnect参数为true的意义?

    在蓝牙核心文档Vol3: Core System Package[Host volume]->Part C: Generic Access Profile的Connection Modes and Procedures章节中有涉及到自动连接建立规程(Auto Connection Establishment Procedure)的定义。

    自动连接建立规程用来向多个设备同时发起连接。一个中央设备的主机与多个外围设备绑定,只要它们开始广播,便立刻与其建立连接。跟多细节请参考蓝牙核心文档和协议栈源码。

    一些API使用问题

    Android 4.3

    此版本是首个支持BLE的Android版本,稳定性一般,现在的系统分布情况,基本可以把最低支持版本提高的Android4.4了

    Android 5.0

    Samsung手机出现BluetoothAdapter.startLeScan()方法使用不当导致的Crash

    正常调用过程startLeScan() -> stopLeScan() -> startLeScan() -> stopLeScan(),不会出现Crash

    异常调用startLeScan() -> startLeScan()会出现Crash

    Android 6.0

    在Android 6.0版本,需要APP获取位置权限才可以使用蓝牙API,部分机型在未授权时,调用蓝牙API会引起Crash

    Android 6.0.1

    Android6.0.1有个连接问题,是系统bug,影响连接问题。

    Android 7.0

    30s内连续扫描次数不允许大于5次,否则会引起无法扫描到设备的问题,需要重启才可以恢复正常。

    并发执行BluetoothGatt.readRemoterssi()会引发DeadObjectException,三星手机出现概率较高。

    Android 8.1 

    扫描方法BluetoothAdapter.startLeScan(UUID[] serviceUuids, BluetoothAdapter.LeScanCallback callback);

    部分手机如果没有指定serviceuuids值,手机锁屏后,扫描回调会失败,无法扫描到设备。Google亲儿子Pixel系列必现。

    待更新......

    另外一些普遍问题

    BluetoothDevice.getName() 获取名字是有些不可靠的,因为有些情况下获取Name是空,慎用此方法获取的值来作为扫描过滤条件。

    BLE设备的建立和断开连接的操作,最好都放在主线程中:例如BluetoothDevice.connectGatt(),BluetoothGatt.connect(),BluetoothGatt.disconnect(),BluetoothGatt.discoverServices()等

    BLE 应用异常耗电问题,在连接 BLE 设备的过程中,系统会持有这个 WakeLock,直到连接上或者主动断开连接才会释放。如果BLE设备不在范围内,这个超时时间大约为30s,而这时你可能又要尝试重新连接,这个WakeLock又被重新持有,这样系统就永远不能休眠了。

    Android BLE蓝牙的各种问题,只要做到如下几点,大部分问题会得到解决:

    原则一:startLeScan()和stopLeScan()一定要确保成对出现、顺序调用。

    否则会导致协议栈中mClientIf达到上限,扫描registerClient失败,再也不能扫描到设备,此时onScanFailed会发生errorCode=2。

    原则二:BluetoothDevice.connectGatt()、BluetoothGatt.disconnect()和BluetoothGatt.close()一定要顺序调用。一些情况下可以直接越过disconnect()方法直接调用close()。

    close()非常重要,对于一个执行过连接方法的设备,不管是否连接成功,最后都要调用close(),让系统底层回收掉资源,否则会有各种问题让你崩溃。

    对于连接成功的蓝牙设备,想断开时,可以先调用BluetoothGatt.disconnect(),等待onConnectionStateChange响应断开后,再执行close()。

    如果是执行连接方法时出现了无法恢复的错误,比如133、8、19、22、62等,可以直接调用close()。

    原则三:读/写特征和描述、设置通知和指示等操作,要确保上一个执行完成了,再执行下一个调用。

    Android源码中使用了mDeviceBusy全局变量,同时调用两个API,会导致后调用的直接失败。

    附录:API常见错误码

    GATT_ERROR    0x85    //133任何不惧名字的错误都出现这个错误码,出现了就认怂吧,重新连接吧。

    GATT_CONN_TIMEOUT    0x08    //8  连接超时,大多数情况是设备离开可连接范围,然后手机端连接超时断开返回此错误码。

    GATT_CONN_TERMINATE_PEER_USER     0x13    //19  连接被对端设备终止,直白点就是手机去连接外围设备,外围设备任性不让连接执行了断开。

    GATT_CONN_TERMINATE_LOCAL_HOST    0x16    //22  连接被本地主机终止,可以解释为手机连接外围设备,但是连接过程中出现一些比如鉴权等问题,无法继续保持连接,主动执行了断开操作。

    GATT_CONN_FAIL_ESTABLISH      03E    //62  连接建立失败。

    为了容易的避坑,基于Android蓝牙接口封装了一个库,将一些会引发错误的地方规避掉了,以后再开发相关应用,可以省一些心力。

    项目地址:https://github.com/bingerz/flip-ble

  • 相关阅读:
    codeforces C. No to Palindromes!
    codeforces D. Pashmak and Parmida's problem
    codeforces C. Little Pony and Expected Maximum
    codeforces D. Count Good Substrings
    codeforces C. Jzzhu and Chocolate
    codeforces C. DZY Loves Sequences
    codeforces D. Multiplication Table
    codeforces C. Painting Fence
    hdu 5067 Harry And Dig Machine
    POJ 1159 Palindrome
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/11507349.html
Copyright © 2011-2022 走看看