FastBle蓝牙低功耗框架的使用

举报
yd_57386892 发表于 2020/12/28 23:21:06 2020/12/28
【摘要】 因为自己的项目中有用到了蓝牙相关的功能,所以之前也断断续续地针对蓝牙通信尤其是BLE通信进行了一番探索,整理出了一个开源框架FastBle与各位分享经验。 源码地址: https://github.com/Jasonchenlijian/FastBle 随着对FastBle框架关注的人越来越多,与我讨论问题的小伙伴也多起来,所以整理了一篇文章,详细介绍一下框架的...

因为自己的项目中有用到了蓝牙相关的功能,所以之前也断断续续地针对蓝牙通信尤其是BLE通信进行了一番探索,整理出了一个开源框架FastBle与各位分享经验。
源码地址:

https://github.com/Jasonchenlijian/FastBle

随着对FastBle框架关注的人越来越多,与我讨论问题的小伙伴也多起来,所以整理了一篇文章,详细介绍一下框架的用法,一些坑,还有我对Android BLE开发实践方面的理解。

文章分为3个部分:

  • FastBle的使用
  • BLE开发实践方面的理解
  • FastBle源码解析

1. FastBle的使用

1.1 声明权限


  
  1. <uses-permission android:name="android.permission.BLUETOOTH" />
  2. <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
  3. <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  4. <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  • android.permission.BLUETOOTH : 这个权限允许程序连接到已配对的蓝牙设备, 请求连接/接收连接/传输数据需要改权限, 主要用于对配对后进行操作;
  • android.permission.BLUETOOTH_ADMIN : 这个权限允许程序发现和配对蓝牙设备, 该权限用来管理蓝牙设备, 有了这个权限, 应用才能使用本机的蓝牙设备, 主要用于对配对前的操作;
  • android.permission.ACCESS_COARSE_LOCATION和android.permission.ACCESS_FINE_LOCATION:Android 6.0以后,这两个权限是必须的,蓝牙扫描周围的设备需要获取模糊的位置信息。这两个权限属于同一组危险权限,在清单文件中声明之后,还需要再运行时动态获取。

1.2. 初始化及配置


  
  1. @Override
  2. protected void onCreate(Bundle savedInstanceState) {
  3. super.onCreate(savedInstanceState);
  4. setContentView(R.layout.activity_main);
  5. BleManager.getInstance().init(getApplication());
  6. BleManager.getInstance()
  7. .enableLog(true)
  8. .setReConnectCount(1, 5000)
  9. .setOperateTimeout(5000);
  10. }

在使用之前,需要事先调用初始化init(Application app)方法。此外,可以进行一些自定义的配置,比如是否显示框架内部日志,重连次数和重连时间间隔,以及操作超时时间。

1.3. 扫描外围设备

APP作为中心设备,想要与外围硬件设备建立蓝牙通信的前提是首先得到设备对象,途径是扫描。在调用扫描方法之前,你首先应该先处理下面的准备工作。

  • 判断当前Android设备是否支持BLE。
    Android 4.3以后系统中加入了蓝牙BLE的功能。

     BleManager.getInstance().isSupportBle();
    
       
  • 判断当前Android设备的蓝牙是否已经打开。
    可以直接调用下面的判断方法来判断本机是否已经打开了蓝牙,如果没有,向用户抛出提示。

    BleManager.getInstance().isBlueEnable();
    
       
  • 主动打开蓝牙。
    除了判断蓝牙是否打开给以用户提示之外,我们也可以通过程序直接帮助用户打开蓝牙开关,打开方式有这几种:
    方法1:通过蓝牙适配器直接打开蓝牙。

    BleManager.getInstance().enableBluetooth();
    
       

    方法2:通过startActivityForResult引导界面引导用户打开蓝牙。

    
        
    1. Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    2. startActivityForResult(intent, 0x01);

    需要注意的是,第一种方法是异步的,打开蓝牙需要一段时间,调用此方法后,蓝牙不会立刻就处于开启状态。如果使用此方法后紧接者就需要进行扫描,建议维护一个阻塞线程,内部每隔一段时间查询蓝牙是否处于开启状态,外部显示等待UI引导用户等待,直至开启成功。使用第二种方法,会通过系统弹出框的形式引导用户开启,最终通过onActivityResult的形式回调通知是否开启成功。

  • 6.0及以上机型动态获取位置权限。
    蓝牙打开之后,进行扫描之前,需要判断下当前设备是否是6.0及以上,如果是,需要动态获取之前在Manifest中声明的位置权限。

  • 配置扫描规则
    扫描规则可以配置1个或多个,也可以不配置使用默认(扫描10秒)。扫描的时候,会根据配置的过滤选项,对扫描到的设备进行过滤,结果返回过滤后的设备。扫描时间配置为小于等于0,会实现无限扫描,直至调用BleManger.getInstance().cancelScan()来中止扫描。

    
        
    1. BleScanRuleConfig scanRuleConfig = new BleScanRuleConfig.Builder()
    2. .setServiceUuids(serviceUuids) // 只扫描指定的服务的设备,可选
    3. .setDeviceName(true, names) // 只扫描指定广播名的设备,可选
    4. .setDeviceMac(mac) // 只扫描指定mac的设备,可选
    5. .setAutoConnect(isAutoConnect) // 连接时的autoConnect参数,可选,默认false
    6. .setScanTimeOut(10000) // 扫描超时时间,可选,默认10秒
    7. .build();
    8. BleManager.getInstance().initScanRule(scanRuleConfig);

    以上准备工作完成后,就可以开始进行扫描。

    
        
    1. BleManager.getInstance().scan(new BleScanCallback() {
    2. @Override
    3. public void onScanStarted(boolean success) {
    4. }
    5. @Override
    6. public void onLeScan(BleDevice bleDevice) {
    7. }
    8. @Override
    9. public void onScanning(BleDevice bleDevice) {
    10. }
    11. @Override
    12. public void onScanFinished(List<BleDevice> scanResultList) {
    13. }
    14. });

onScanStarted(boolean success): 会回到主线程,参数表示本次扫描动作是否开启成功。由于蓝牙没有打开,上一次扫描没有结束等原因,会造成扫描开启失败。
onLeScan(BleDevice bleDevice):扫描过程中所有被扫描到的结果回调。由于扫描及过滤的过程是在工作线程中的,此方法也处于工作线程中。同一个设备会在不同的时间,携带自身不同的状态(比如信号强度等),出现在这个回调方法中,出现次数取决于周围的设备量及外围设备的广播间隔。
onScanning(BleDevice bleDevice):扫描过程中的所有过滤后的结果回调。与onLeScan区别之处在于:它会回到主线程;同一个设备只会出现一次;出现的设备是经过扫描过滤规则过滤后的设备。
onScanFinished(List<BleDevice> scanResultList):本次扫描时段内所有被扫描且过滤后的设备集合。它会回到主线程,相当于onScanning设备之和。

1.4. 设备信息

扫描得到的BLE外围设备,会以BleDevice对象的形式,作为后续操作的最小单元对象。它本身含有这些信息:
String getName():蓝牙广播名
String getMac():蓝牙Mac地址
byte[] getScanRecord(): 被扫描到时候携带的广播数据
int getRssi() :被扫描到时候的信号强度
后续进行设备连接、断开、判断设备状态,读写操作等时候,都会用到这个对象。可以把它理解为外围蓝牙设备的载体,所有对外围蓝牙设备的操作,都通过这个对象来传导。

1.5. 连接、断连、监控连接状态

拿到设备对象之后,可以进行连接操作。


  
  1. BleManager.getInstance().connect(bleDevice, new BleGattCallback() {
  2. @Override
  3. public void onStartConnect() {
  4. }
  5. @Override
  6. public void onConnectFail(BleException exception) {
  7. }
  8. @Override
  9. public void onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status) {
  10. }
  11. @Override
  12. public void onDisConnected(boolean isActiveDisConnected, BleDevice bleDevice, BluetoothGatt gatt, int status) {
  13. }
  14. });

onStartConnect():开始进行连接。
onConnectFail(BleException exception):连接不成功。
onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status):连接成功并发现服务。
onDisConnected(boolean isActiveDisConnected, BleDevice bleDevice, BluetoothGatt gatt, int status):连接断开,特指连接后再断开的情况。在这里可以监控设备的连接状态,一旦连接断开,可以根据自身情况考虑对BleDevice对象进行重连操作。需要注意的是,断开和重连之间最好间隔一段时间,否则可能会出现长时间连接不上的情况。此外,如果通过调用disconnect(BleDevice bleDevice)方法,主动断开蓝牙连接的结果也会在这个方法中回调,此时isActiveDisConnected将会是true。

1.6. GATT协议

BLE连接都是建立在 GATT (Generic Attribute Profile) 协议之上。GATT 是一个在蓝牙连接之上的发送和接收很短的数据段的通用规范,这些很短的数据段被称为属性(Attribute)。它定义两个 BLE 设备通过Service 和 Characteristic 进行通信。GATT 就是使用了 ATT(Attribute Protocol)协议,ATT 协议把 Service, Characteristic以及对应的数据保存在一个查找表中,次查找表使用 16 bit ID 作为每一项的索引。

关于GATT这部分内容会在下面重点讲解。总之,中心设备和外设需要双向通信的话,唯一的方式就是建立 GATT 连接。当连接成功之后,外围设备与中心设备之间就建立起了GATT连接。
上面讲到的connect(BleDevice bleDevice, BleGattCallback bleGattCallback)方法其实是有返回值的,这个返回值就是BluetoothGatt。当然还有其他方式可以获取BluetoothGatt对象,连接成功后,调用:

BluetoothGatt gatt = BleManager.getInstance().getBluetoothGatt(BleDevice bleDevice);

 

通过BluetoothGatt对象作为连接桥梁,中心设备可以获取外围设备的很多信息,以及双向通信。

首先,就可以获取这个蓝牙设备所拥有的Service和Characteristic。每一个属性都可以被定义作不同的用途,通过它们来进行协议通信。下面的方法,就是通过BluetoothGatt,查找出所有的Service和Characteristic的UUID:


  
  1. List<BluetoothGattService> serviceList = bluetoothGatt.getServices();
  2. for (BluetoothGattService service : serviceList) {
  3. UUID uuid_service = service.getUuid();
  4. List<BluetoothGattCharacteristic> characteristicList= service.getCharacteristics();
  5. for(BluetoothGattCharacteristic characteristic : characteristicList) {
  6. UUID uuid_chara = characteristic.getUuid();
  7. }
  8. }

1.7. 协议通信

APP与设备建立了连接,并且知道了Service和Characteristic(需要与硬件协议沟通确认)之后,我们就可以通过BLE协议进行通信了。通信的桥梁,主要就是是通过 标准的或者自定义的Characteristic,中文我们称之为“特征”。我们可以从 Characteristic 读数据和写数据。这样就实现了双向的通信。站在APP作为中心设备的角度,常用于数据交互的通信方式主要有3种:接收通知、写、读,此外还有设置最大传输单元,获取实时信号强度等通信操作。

  • 接收通知
    有两种方式可以接收通知,indicate和notify。indicate和notify的区别就在于,indicate是一定会收到数据,notify有可能会丢失数据。indicate底层封装了应答机制,如果没有收到中央设备的回应,会再次发送直至成功;而notify不会有central收到数据的回应,可能无法保证数据到达的准确性,优势是速度快。通常情况下,当外围设备需要不断地发送数据给APP的时候,比如血压计在测量过程中的压力变化,胎心仪在监护过程中的实时数据传输,这种频繁的情况下,优先考虑notify形式。当只需要发送很少且很重要的一条数据给APP的时候,优先考虑indicate形式。当然,从Android开发角度的出发,如果硬件放已经考虑了成熟的协议和发送方式,我们需要做的仅仅是根据其配置的数据发送方式进行相应的对接即可。
    打开notify

    
        
    1. BleManager.getInstance().notify(
    2. bleDevice,
    3. uuid_service,
    4. uuid_characteristic_notify,
    5. new BleNotifyCallback() {
    6. @Override
    7. public void onNotifySuccess() {
    8. // 打开通知操作成功
    9. }
    10. @Override
    11. public void onNotifyFailure(BleException exception) {
    12. // 打开通知操作失败
    13. }
    14. @Override
    15. public void onCharacteristicChanged(byte[] data) {
    16. // 打开通知后,设备发过来的数据将在这里出现
    17. }
    18. });

    关闭notify

      BleManager.getInstance().stopNotify(uuid_service, uuid_characteristic_notify);
    
       

    打开indicate

    
        
    1. BleManager.getInstance().indicate(
    2. bleDevice,
    3. uuid_service,
    4. uuid_characteristic_indicate,
    5. new BleIndicateCallback() {
    6. @Override
    7. public void onIndicateSuccess() {
    8. // 打开通知操作成功
    9. }
    10. @Override
    11. public void onIndicateFailure(BleException exception) {
    12. // 打开通知操作失败
    13. }
    14. @Override
    15. public void onCharacteristicChanged(byte[] data) {
    16. // 打开通知后,设备发过来的数据将在这里出现
    17. }
    18. });

    关闭indicate

      BleManager.getInstance().stopIndicate(uuid_service, uuid_characteristic_indicate);
    
       

    这里的通知操作用到了两个关键的参数,uuid_serviceuuid_characteristic_notify(或uuid_characteristic_indicate),就是上面提到的Service和Characteristic,此处以字符串的形式体现,不区分大小写。

  • 读写

    
        
    1. BleManager.getInstance().read(
    2. bleDevice,
    3. uuid_service,
    4. uuid_characteristic_read,
    5. new BleReadCallback() {
    6. @Override
    7. public void onReadSuccess(byte[] data) {
    8. // 读特征值数据成功
    9. }
    10. @Override
    11. public void onReadFailure(BleException exception) {
    12. // 读特征值数据失败
    13. }
    14. });
    15. BleManager.getInstance().write(
    16. bleDevice,
    17. uuid_service,
    18. uuid_characteristic_write,
    19. data,
    20. new BleWriteCallback() {
    21. @Override
    22. public void onWriteSuccess(int current, int total, byte[] justWrite) {
    23. // 发送数据到设备成功(分包发送的情况下,可以通过方法中返回的参数可以查看发送进度)
    24. }
    25. @Override
    26. public void onWriteFailure(BleException exception) {
    27. // 发送数据到设备失败
    28. }
    29. });

    进行BLE数据相互发送的时候,一次最多能发送20个字节。如果需要发送的数据超过20个字节,有两种方法,一种是主动尝试拓宽MTU,另一种是采用分包传输的方式。框架中的write方法,当遇到数据超过20字节的情况时,默认是进行分包发送的。

  • 设置最大传输单元MTU

    
        
    1. BleManager.getInstance().setMtu(bleDevice, mtu, new BleMtuChangedCallback() {
    2. @Override
    3. public void onSetMTUFailure(BleException exception) {
    4. // 设置MTU失败
    5. }
    6. @Override
    7. public void onMtuChanged(int mtu) {
    8. // 设置MTU成功,并获得当前设备传输支持的MTU值
    9. }
    10. });
  • 获取设备的实时信号强度Rssi

    
        
    1. BleManager.getInstance().readRssi(
    2. bleDevice,
    3. new BleRssiCallback() {
    4. @Override
    5. public void onRssiFailure(BleException exception) {
    6. // 读取设备的信号强度失败
    7. }
    8. @Override
    9. public void onRssiSuccess(int rssi) {
    10. // 读取设备的信号强度成功
    11. }
    12. });

在BLE设备通信过程中,有几点经验分享给大家:

  • 两次操作之间最好间隔一小段时间,如100ms(具体时间可以根据自己实际蓝牙外设自行尝试延长或缩短)。举例,onConnectSuccess之后,延迟100ms再进行notify,之后再延迟100ms进行write
  • 连接及连接后的过程中,时刻关注onDisConnected方法,然后做处理。
  • 断开后如果需要重连,也请延迟一段时间,否则会造成阻塞。

2. BLE开发实践方面的理解

在分解FastBle源码之前,我首先介绍一下BLE通信一些理论知识。

2.1 蓝牙简介

蓝牙是一种近距离无线通信技术。它的特性就是近距离通信,典型距离是 10 米以内,传输速度最高可达 24 Mbps,支持多连接,安全性高,非常适合用智能设备上。

2.2 蓝牙技术的版本演进

  • 1999年发布1.0版本,目前市面上已很少见到;
  • 2002年发布1.1版本,目前市面上已很少见到;
  • 2004年发布2.0版本,目前市面上已很少见到;
  • 2007年发布的2.1版本,是之前使用最广的,也是我们所谓的经典蓝牙。
  • 2009年推出蓝牙 3.0版本,也就是所谓的高速蓝牙,传输速率理论上可高达24 Mbit/s;
  • 2010年推出蓝牙4.0版本,它是相对之前版本的集大成者,它包括经典蓝牙、高速蓝牙和蓝牙低功耗协议。经典蓝牙包括旧有蓝牙协议,高速蓝牙基于Wi-Fi,低功耗蓝牙就是BLE。
  • 2016年蓝牙技术联盟提出了新的蓝牙技术标准,即蓝牙5.0版本。蓝牙5.0针对低功耗设备速度有相应提升和优化,结合wifi对室内位置进行辅助定位,提高传输速度,增加有效工作距离,主要是针对物联网方向的改进。

2.3 Android上BLE功能的逐步演进

在Android开发过程中,版本的碎片化一直是需要考虑的问题,再加上厂商定制及蓝牙本身也和Android一样一直在发展过程中,所以对于每一个版本支持什么功能,是我们需要知道的。

  • Android 4.3 开始,开始支持BLE功能,但只支持Central Mode(中心模式)
  • Android 5.0开始,开始支持Peripheral Mode(外设模式)

中心模式和外设模式是什么意思?

  • Central Mode: Android端作为中心设备,连接其他外围设备。
  • Peripheral Mode:Android端作为外围设备,被其他中心设备连接。在Android 5.0支持外设模式之后,才算实现了两台Android手机通过BLE进行相互通信。

2.4 蓝牙的广播和扫描

以下内容部分参考自BLE Introduction
关于这部分内容,需要引入一个概念,GAP(Generic Access Profile),它用来控制设备连接和广播。GAP 使你的设备被其他设备可见,并决定了你的设备是否可以或者怎样与设备进行交互。例如 Beacon 设备就只是向外发送广播,不支持连接;小米手环就可以与中心设备建立连接。

在 GAP 中蓝牙设备可以向外广播数据包,广播包分为两部分: Advertising Data Payload(广播数据)和 Scan Response Data Payload(扫描回复),每种数据最长可以包含 31 byte。这里广播数据是必需的,因为外设必需不停的向外广播,让中心设备知道它的存在。扫描回复是可选的,中心设备可以向外设请求扫描回复,这里包含一些设备额外的信息,例如设备的名字。在 Android 中,系统会把这两个数据拼接在一起,返回一个 62 字节的数组。这些广播数据可以自己手动去解析,在 Android 5.0 也提供 ScanRecord 帮你解析,直接可以通过这个类获得有意义的数据。广播中可以有哪些数据类型呢?设备连接属性,标识设备支持的 BLE 模式,这个是必须的。设备名字,设备包含的关键 GATT service,或者 Service data,厂商自定义数据等等。

 

广播流程

 

外围设备会设定一个广播间隔,每个广播间隔中,它会重新发送自己的广播数据。广播间隔越长,越省电,同时也不太容易扫描到。

刚刚讲到,GAP决定了你的设备怎样与其他设备进行交互。答案是有2种方式:

  • 完全基于广播的方式
    也有些情况是不需要连接的,只要外设广播自己的数据即可。用这种方式主要目的是让外围设备,把自己的信息发送给多个中心设备。使用广播这种方式最典型的应用就是苹果的 iBeacon。这是苹果公司定义的基于 BLE 广播实现的功能,可以实现广告推送和室内定位。这也说明了,APP 使用 BLE,需要定位权限。

    基于非连接的,这种应用就是依赖 BLE 的广播,也叫作 Beacon。这里有两个角色,发送广播的一方叫做 Broadcaster,监听广播的一方叫 Observer。

  • 基于GATT连接的方式
    大部分情况下,外设通过广播自己来让中心设备发现自己,并建立 GATT 连接,从而进行更多的数据交换。这里有且仅有两个角色,发起连接的一方,叫做中心设备—Central,被连接的设备,叫做外设—Peripheral。

    • 外围设备:这一般就是非常小或者简单的低功耗设备,用来提供数据,并连接到一个更加相对强大的中心设备,例如小米手环。
    • 中心设备:中心设备相对比较强大,用来连接其他外围设备,例如手机等。

    GATT 连接需要特别注意的是:GATT 连接是独占的。也就是一个 BLE 外设同时只能被一个中心设备连接。一旦外设被连接,它就会马上停止广播,这样它就对其他设备不可见了。当设备断开,它又开始广播。中心设备和外设需要双向通信的话,唯一的方式就是建立 GATT 连接。

    GATT 通信的双方是 C/S 关系。外设作为 GATT 服务端(Server),它维持了 ATT 的查找表以及 service 和 characteristic 的定义。中心设备是 GATT 客户端(Client),它向 Server 发起请求。需要注意的是,所有的通信事件,都是由客户端发起,并且接收服务端的响应。

2.5 BLE通信基础

BLE通信的基础有两个重要的概念,ATT和GATT。

  • ATT
    全称 attribute protocol,中文名“属性协议”。它是 BLE 通信的基础。ATT 把数据封装,向外暴露为“属性”,提供“属性”的为服务端,获取“属性”的为客户端。ATT 是专门为低功耗蓝牙设计的,结构非常简单,数据长度很短。

  • GATT
    全称 Generic Attribute Profile, 中文名“通用属性配置文件”。它是在ATT 的基础上,对 ATT 进行的进一步逻辑封装,定义数据的交互方式和含义。GATT是我们做 BLE 开发的时候直接接触的概念。

  • GATT 层级
    GATT按照层级定义了4个概念:配置文件(Profile)、服务(Service)、特征(Characteristic)和描述(Descriptor)。他们的关系是这样的:Profile 就是定义了一个实际的应用场景,一个 Profile包含若干个 Service,一个 Service 包含若干个 Characteristic,一个 Characteristic 可以包含若干 Descriptor。

     

    GATT层级

  • Profile
    Profile 并不是实际存在于 BLE 外设上的,它只是一个被 Bluetooth SIG 或者外设设计者预先定义的 Service 的集合。例如心率Profile(Heart Rate Profile)就是结合了 Heart Rate Service 和 Device Information Service。所有官方通过 GATT Profile 的列表可以从这里找到。

  • Service
    Service 是把数据分成一个个的独立逻辑项,它包含一个或者多个 Characteristic。每个 Service 有一个 UUID 唯一标识。 UUID 有 16 bit 的,或者 128 bit 的。16 bit 的 UUID 是官方通过认证的,需要花钱购买,128 bit 是自定义的,这个就可以自己随便设置。官方通过了一些标准 Service,完整列表在这里。以 Heart Rate Service为例,可以看到它的官方通过 16 bit UUID 是 0x180D,包含 3 个 Characteristic:Heart Rate Measurement, Body Sensor LocationHeart Rate Control Point,并且定义了只有第一个是必须的,它是可选实现的。

  • Characteristic
    需要重点提一下Characteristic, 它定义了数值和操作,包含一个Characteristic声明、Characteristic属性、值、值的描述(Optional)。通常我们讲的 BLE 通信,其实就是对 Characteristic 的读写或者订阅通知。比如在实际操作过程中,我对某一个Characteristic进行读,就是获取这个Characteristic的value。

  • UUID
    Service、Characteristic 和 Descriptor 都是使用 UUID 唯一标示的。

    UUID 是全局唯一标识,它是 128bit 的值,为了便于识别和阅读,一般以 “8位-4位-4位-4位-12位”的16进制标示,比如“12345678-abcd-1000-8000-123456000000”。

    但是,128bit的UUID 太长,考虑到在低功耗蓝牙中,数据长度非常受限的情况,蓝牙又使用了所谓的 16 bit 或者 32 bit 的 UUID,形式如下:“0000XXXX-0000-1000-8000-00805F9B34FB”。除了 “XXXX” 那几位以外,其他都是固定,所以说,其实 16 bit UUID 是对应了一个 128 bit 的 UUID。这样一来,UUID 就大幅减少了,例如 16 bit UUID只有有限的 65536(16的四次方) 个。与此同时,因为数量有限,所以 16 bit UUID 并不能随便使用。蓝牙技术联盟已经预先定义了一些 UUID,我们可以直接使用,比如“00001011-0000-1000-8000-00805F9B34FB”就一个是常见于BLE设备中的UUID。当然也可以花钱定制自定义的UUID。


3. FastBle源码解析

通过上面BLE的基础理论,我们可以分析到,BLE通信实际上就是先由客户端发起与服务端的连接,再通过服务端的找到其Characteristic进行两者间的数据交互。

在FastBle源码中,首先看BleManager中的connect()方法:


  
  1. public BluetoothGatt connect(BleDevice bleDevice, BleGattCallback bleGattCallback) {
  2. if (bleGattCallback == null) {
  3. throw new IllegalArgumentException("BleGattCallback can not be Null!");
  4. }
  5. if (!isBlueEnable()) {
  6. BleLog.e("Bluetooth not enable!");
  7. bleGattCallback.onConnectFail(new OtherException("Bluetooth not enable!"));
  8. return null;
  9. }
  10. if (Looper.myLooper() == null || Looper.myLooper() != Looper.getMainLooper()) {
  11. BleLog.w("Be careful: currentThread is not MainThread!");
  12. }
  13. if (bleDevice == null || bleDevice.getDevice() == null) {
  14. bleGattCallback.onConnectFail(new OtherException("Not Found Device Exception Occurred!"));
  15. } else {
  16. BleBluetooth bleBluetooth = new BleBluetooth(bleDevice);
  17. boolean autoConnect = bleScanRuleConfig.isAutoConnect();
  18. return bleBluetooth.connect(bleDevice, autoConnect, bleGattCallback);
  19. }
  20. return null;
  21. }

这个方法将扫描到的外围设备对象传入,通过一些必要的条件判断之后,调用bleBluetooth.connect()进行连接。我们去看一下BleBluetooth这个类:


  
  1. public BleBluetooth(BleDevice bleDevice) {
  2. this.bleDevice = bleDevice;
  3. }

上面的BleBluetooth的构造方法是传入一个蓝牙设备对象。由此可见,一个BleBluetooth可能代表你的Android与这一个外围设备整个交互过程,从开始连接,到中间数据交互,一直到断开连接的整个过程。在多连接情况下,有多少外围设备,设备池中就维护着多少个BleBluetooth对象。

MultipleBluetoothController就是控制多设备连接的。它里面有增加和移除设备的方法,如下图的addBleBluetoothremoveBleBluetooth,传入的参数就是BleBluetooth对象,验证了上面的说法。


  
  1. public synchronized void addBleBluetooth(BleBluetooth bleBluetooth) {
  2. if (bleBluetooth == null) {
  3. return;
  4. }
  5. if (!bleLruHashMap.containsKey(bleBluetooth.getDeviceKey())) {
  6. bleLruHashMap.put(bleBluetooth.getDeviceKey(), bleBluetooth);
  7. }
  8. }
  9. public synchronized void removeBleBluetooth(BleBluetooth bleBluetooth) {
  10. if (bleBluetooth == null) {
  11. return;
  12. }
  13. if (bleLruHashMap.containsKey(bleBluetooth.getDeviceKey())) {
  14. bleLruHashMap.remove(bleBluetooth.getDeviceKey());
  15. }
  16. }

回到BleBlutoothconnect方法:


  
  1. public synchronized BluetoothGatt connect(BleDevice bleDevice,
  2. boolean autoConnect,
  3. BleGattCallback callback) {
  4. addConnectGattCallback(callback);
  5. isMainThread = Looper.myLooper() != null && Looper.myLooper() == Looper.getMainLooper();
  6. BluetoothGatt gatt;
  7. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  8. gatt = bleDevice.getDevice().connectGatt(BleManager.getInstance().getContext(),
  9. autoConnect, coreGattCallback, TRANSPORT_LE);
  10. } else {
  11. gatt = bleDevice.getDevice().connectGatt(BleManager.getInstance().getContext(),
  12. autoConnect, coreGattCallback);
  13. }
  14. if (gatt != null) {
  15. if (bleGattCallback != null)
  16. bleGattCallback.onStartConnect();
  17. connectState = BleConnectState.CONNECT_CONNECTING;
  18. }
  19. return gatt;
  20. }

可见,最终也是调用了原生API中的BluetoothDeviceconnectGatt()方法。在蓝牙原理分析中讲到,连接过程中要创建一个BluetoothGattCallback,用来作为回调,这个类非常重要,所有的 GATT 操作的回调都在这里。而此处的coreGattCallback应该就扮演着这个角色,它是BluetoothGattCallback的实现类对象,对操作回调结果做了封装和分发。


  
  1. private BluetoothGattCallback coreGattCallback = new BluetoothGattCallback() {
  2. @Override
  3. public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
  4. super.onConnectionStateChange(gatt, status, newState);
  5. if (newState == BluetoothGatt.STATE_CONNECTED) {
  6. gatt.discoverServices();
  7. } else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
  8. closeBluetoothGatt();
  9. BleManager.getInstance().getMultipleBluetoothController().removeBleBluetooth(BleBluetooth.this);
  10. if (connectState == BleConnectState.CONNECT_CONNECTING) {
  11. connectState = BleConnectState.CONNECT_FAILURE;
  12. if (isMainThread) {
  13. Message message = handler.obtainMessage();
  14. message.what = BleMsg.MSG_CONNECT_FAIL;
  15. message.obj = new BleConnectStateParameter(bleGattCallback, gatt, status);
  16. handler.sendMessage(message);
  17. } else {
  18. if (bleGattCallback != null)
  19. bleGattCallback.onConnectFail(new ConnectException(gatt, status));
  20. }
  21. } else if (connectState == BleConnectState.CONNECT_CONNECTED) {
  22. connectState = BleConnectState.CONNECT_DISCONNECT;
  23. if (isMainThread) {
  24. Message message = handler.obtainMessage();
  25. message.what = BleMsg.MSG_DISCONNECTED;
  26. BleConnectStateParameter para = new BleConnectStateParameter(bleGattCallback, gatt, status);
  27. para.setAcitive(isActiveDisconnect);
  28. para.setBleDevice(getDevice());
  29. message.obj = para;
  30. handler.sendMessage(message);
  31. } else {
  32. if (bleGattCallback != null)
  33. bleGattCallback.onDisConnected(isActiveDisconnect, bleDevice, gatt, status);
  34. }
  35. }
  36. }
  37. }
  38. @Override
  39. public void onServicesDiscovered(BluetoothGatt gatt, int status) {
  40. super.onServicesDiscovered(gatt, status);
  41. BleLog.i("BluetoothGattCallback:onServicesDiscovered "
  42. + '\n' + "status: " + status
  43. + '\n' + "currentThread: " + Thread.currentThread().getId());
  44. if (status == BluetoothGatt.GATT_SUCCESS) {
  45. bluetoothGatt = gatt;
  46. connectState = BleConnectState.CONNECT_CONNECTED;
  47. isActiveDisconnect = false;
  48. BleManager.getInstance().getMultipleBluetoothController().addBleBluetooth(BleBluetooth.this);
  49. if (isMainThread) {
  50. Message message = handler.obtainMessage();
  51. message.what = BleMsg.MSG_CONNECT_SUCCESS;
  52. BleConnectStateParameter para = new BleConnectStateParameter(bleGattCallback, gatt, status);
  53. para.setBleDevice(getDevice());
  54. message.obj = para;
  55. handler.sendMessage(message);
  56. } else {
  57. if (bleGattCallback != null)
  58. bleGattCallback.onConnectSuccess(getDevice(), gatt, status);
  59. }
  60. } else {
  61. closeBluetoothGatt();
  62. connectState = BleConnectState.CONNECT_FAILURE;
  63. if (isMainThread) {
  64. Message message = handler.obtainMessage();
  65. message.what = BleMsg.MSG_CONNECT_FAIL;
  66. message.obj = new BleConnectStateParameter(bleGattCallback, gatt, status);
  67. handler.sendMessage(message);
  68. } else {
  69. if (bleGattCallback != null)
  70. bleGattCallback.onConnectFail(new ConnectException(gatt, status));
  71. }
  72. }
  73. }
  74. @Override
  75. public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
  76. super.onCharacteristicChanged(gatt, characteristic);
  77. Iterator iterator = bleNotifyCallbackHashMap.entrySet().iterator();
  78. while (iterator.hasNext()) {
  79. Map.Entry entry = (Map.Entry) iterator.next();
  80. Object callback = entry.getValue();
  81. if (callback instanceof BleNotifyCallback) {
  82. BleNotifyCallback bleNotifyCallback = (BleNotifyCallback) callback;
  83. if (characteristic.getUuid().toString().equalsIgnoreCase(bleNotifyCallback.getKey())) {
  84. Handler handler = bleNotifyCallback.getHandler();
  85. if (handler != null) {
  86. Message message = handler.obtainMessage();
  87. message.what = BleMsg.MSG_CHA_NOTIFY_DATA_CHANGE;
  88. message.obj = bleNotifyCallback;
  89. Bundle bundle = new Bundle();
  90. bundle.putByteArray(BleMsg.KEY_NOTIFY_BUNDLE_VALUE, characteristic.getValue());
  91. message.setData(bundle);
  92. handler.sendMessage(message);
  93. }
  94. }
  95. }
  96. }
  97. iterator = bleIndicateCallbackHashMap.entrySet().iterator();
  98. while (iterator.hasNext()) {
  99. Map.Entry entry = (Map.Entry) iterator.next();
  100. Object callback = entry.getValue();
  101. if (callback instanceof BleIndicateCallback) {
  102. BleIndicateCallback bleIndicateCallback = (BleIndicateCallback) callback;
  103. if (characteristic.getUuid().toString().equalsIgnoreCase(bleIndicateCallback.getKey())) {
  104. Handler handler = bleIndicateCallback.getHandler();
  105. if (handler != null) {
  106. Message message = handler.obtainMessage();
  107. message.what = BleMsg.MSG_CHA_INDICATE_DATA_CHANGE;
  108. message.obj = bleIndicateCallback;
  109. Bundle bundle = new Bundle();
  110. bundle.putByteArray(BleMsg.KEY_INDICATE_BUNDLE_VALUE, characteristic.getValue());
  111. message.setData(bundle);
  112. handler.sendMessage(message);
  113. }
  114. }
  115. }
  116. }
  117. }
  118. @Override
  119. public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
  120. super.onDescriptorWrite(gatt, descriptor, status);
  121. Iterator iterator = bleNotifyCallbackHashMap.entrySet().iterator();
  122. while (iterator.hasNext()) {
  123. Map.Entry entry = (Map.Entry) iterator.next();
  124. Object callback = entry.getValue();
  125. if (callback instanceof BleNotifyCallback) {
  126. BleNotifyCallback bleNotifyCallback = (BleNotifyCallback) callback;
  127. if (descriptor.getCharacteristic().getUuid().toString().equalsIgnoreCase(bleNotifyCallback.getKey())) {
  128. Handler handler = bleNotifyCallback.getHandler();
  129. if (handler != null) {
  130. Message message = handler.obtainMessage();
  131. message.what = BleMsg.MSG_CHA_NOTIFY_RESULT;
  132. message.obj = bleNotifyCallback;
  133. Bundle bundle = new Bundle();
  134. bundle.putInt(BleMsg.KEY_NOTIFY_BUNDLE_STATUS, status);
  135. message.setData(bundle);
  136. handler.sendMessage(message);
  137. }
  138. }
  139. }
  140. }
  141. iterator = bleIndicateCallbackHashMap.entrySet().iterator();
  142. while (iterator.hasNext()) {
  143. Map.Entry entry = (Map.Entry) iterator.next();
  144. Object callback = entry.getValue();
  145. if (callback instanceof BleIndicateCallback) {
  146. BleIndicateCallback bleIndicateCallback = (BleIndicateCallback) callback;
  147. if (descriptor.getCharacteristic().getUuid().toString().equalsIgnoreCase(bleIndicateCallback.getKey())) {
  148. Handler handler = bleIndicateCallback.getHandler();
  149. if (handler != null) {
  150. Message message = handler.obtainMessage();
  151. message.what = BleMsg.MSG_CHA_INDICATE_RESULT;
  152. message.obj = bleIndicateCallback;
  153. Bundle bundle = new Bundle();
  154. bundle.putInt(BleMsg.KEY_INDICATE_BUNDLE_STATUS, status);
  155. message.setData(bundle);
  156. handler.sendMessage(message);
  157. }
  158. }
  159. }
  160. }
  161. }
  162. @Override
  163. public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
  164. super.onCharacteristicWrite(gatt, characteristic, status);
  165. Iterator iterator = bleWriteCallbackHashMap.entrySet().iterator();
  166. while (iterator.hasNext()) {
  167. Map.Entry entry = (Map.Entry) iterator.next();
  168. Object callback = entry.getValue();
  169. if (callback instanceof BleWriteCallback) {
  170. BleWriteCallback bleWriteCallback = (BleWriteCallback) callback;
  171. if (characteristic.getUuid().toString().equalsIgnoreCase(bleWriteCallback.getKey())) {
  172. Handler handler = bleWriteCallback.getHandler();
  173. if (handler != null) {
  174. Message message = handler.obtainMessage();
  175. message.what = BleMsg.MSG_CHA_WRITE_RESULT;
  176. message.obj = bleWriteCallback;
  177. Bundle bundle = new Bundle();
  178. bundle.putInt(BleMsg.KEY_WRITE_BUNDLE_STATUS, status);
  179. bundle.putByteArray(BleMsg.KEY_WRITE_BUNDLE_VALUE, characteristic.getValue());
  180. message.setData(bundle);
  181. handler.sendMessage(message);
  182. }
  183. }
  184. }
  185. }
  186. }
  187. @Override
  188. public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
  189. super.onCharacteristicRead(gatt, characteristic, status);
  190. Iterator iterator = bleReadCallbackHashMap.entrySet().iterator();
  191. while (iterator.hasNext()) {
  192. Map.Entry entry = (Map.Entry) iterator.next();
  193. Object callback = entry.getValue();
  194. if (callback instanceof BleReadCallback) {
  195. BleReadCallback bleReadCallback = (BleReadCallback) callback;
  196. if (characteristic.getUuid().toString().equalsIgnoreCase(bleReadCallback.getKey())) {
  197. Handler handler = bleReadCallback.getHandler();
  198. if (handler != null) {
  199. Message message = handler.obtainMessage();
  200. message.what = BleMsg.MSG_CHA_READ_RESULT;
  201. message.obj = bleReadCallback;
  202. Bundle bundle = new Bundle();
  203. bundle.putInt(BleMsg.KEY_READ_BUNDLE_STATUS, status);
  204. bundle.putByteArray(BleMsg.KEY_READ_BUNDLE_VALUE, characteristic.getValue());
  205. message.setData(bundle);
  206. handler.sendMessage(message);
  207. }
  208. }
  209. }
  210. }
  211. }
  212. };

在收到连接状态、读、写、通知等操作的结果回调之后,通过消息队列机制,交由相应的Handler去处理。那处理消息的Handler在哪里?举例其中的write操作Handler handler = bleWriteCallback.getHandler();,handler对象被包含在了这个write操作的callback中。


  
  1. public abstract class BleBaseCallback {
  2. private String key;
  3. private Handler handler;
  4. public String getKey() {
  5. return key;
  6. }
  7. public void setKey(String key) {
  8. this.key = key;
  9. }
  10. public Handler getHandler() {
  11. return handler;
  12. }
  13. public void setHandler(Handler handler) {
  14. this.handler = handler;
  15. }
  16. }

所有的操作的callback都继承自这个BleBaseCallback抽象类,它有两个成员变量。一个key,标识着这个callback归属于哪一个Characteristic的操作;另一个handler,用于传递底层发来的操作结果,最终将结果交由callback去抛给调用者,完成一次接口回调。


  
  1. private void handleCharacteristicWriteCallback(BleWriteCallback bleWriteCallback,
  2. String uuid_write) {
  3. if (bleWriteCallback != null) {
  4. writeMsgInit();
  5. bleWriteCallback.setKey(uuid_write);
  6. bleWriteCallback.setHandler(mHandler);
  7. mBleBluetooth.addWriteCallback(uuid_write, bleWriteCallback);
  8. mHandler.sendMessageDelayed(
  9. mHandler.obtainMessage(BleMsg.MSG_CHA_WRITE_START, bleWriteCallback),
  10. BleManager.getInstance().getOperateTimeout());
  11. }
  12. }

上面这段源码解释了这个机制,每一次write操作之后,都会对传入的callback进行唯一性标记,再通过handler用来传递操作结果,同时将这个callback加入这个设备的BleBlutooth对象的callback池中管理。
这样就形成了APP维持一个设备连接池,一个设备连接池管理多个设备管理者,一个设备管理者管理多个不同类别的callback集合,一个callback集合中含有多个同类的不同特征的callback。

架构图



作者:陈利健
链接:https://www.jianshu.com/p/795bb0a08beb
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

文章来源: blog.csdn.net,作者:冉航--小虾米,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/gaoxiaoweiandy/article/details/82958204

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。