BLE基础理论/Android BLE开发示例

news/2024/4/24 0:22:25/文章来源:https://blog.csdn.net/zhuguanlin121/article/details/131996761

参考:https://blog.csdn.net/qq_36075612/article/details/127739150?spm=1001.2014.3001.5502
参考: https://blog.csdn.net/qq_36075612/article/details/122772966?spm=1001.2014.3001.5502

目录

  • 蓝牙的分类
    • 传统蓝牙
    • 低功耗蓝牙
  • 蓝牙专业词汇(原文)
    • SIG
    • Profile
    • service
    • characteristic
    • decriptor
    • UUID
  • 蓝牙的几个 Profile(原文)
    • GAP Profile
    • SDAP Profile
    • SPP Profile
    • GOEP Profile
    • A2DP Profile
    • DUN Profile
    • VRCP Profile
    • HID Profile
  • 低功耗蓝牙
    • 设备角色
    • 广播数据
    • 广播流程
    • 广播的网络拓扑结构
    • GATT
    • GATT 连接的网络拓扑
    • GATT 通信事务
    • GATT 结构
      • Profile
      • Service
      • Characteristic
    • 更多内容
  • BLE蓝牙参数设置:蓝牙扫描列表数量/扫描包/扫描响应包设置等
  • Android BLE 快速开发示例
    • FastBle的使用
    • BLE开发基础
    • FastBle源码解析

蓝牙的分类

传统蓝牙

传统蓝牙是在之前的 1.0.1.2 , 2.0+EDR,2.1+EDR,3.0+EDR 等基础上发展和完善起来
的。

传统蓝牙可以用与数据量比较大的传输,如语音,音乐,较高数据量传输等,低功耗蓝牙这样应用于实时性要求比较高,但是数据速率比较低的产品,如遥控类的,如鼠标,键盘,遥控鼠标 (Air Mouse),传感设备的数据发送,如心跳带,血压计,温度传感器等。传统蓝牙有 3 个功率级别, Class1,Class2,Class3, 分别支持 100m,10m,1m 的传输距离,而低功耗蓝牙无功率级别,一般发送功率在 7dBm ,一般在空旷距离,达到 20m 应该是没有问题的。

由于苹果对经典蓝牙数据传输接口有限制(需要过 MFI 认证),加上功耗偏大,因此在
目前移动互联应用中慢慢地被淘汰 。

低功耗蓝牙

低功耗蓝牙是 Nokia 的 Wibree 标准上发展起来的。

蓝牙低能耗(BLE)技术是低成本、短距离、可互操作的鲁棒性无线技术,工作在免许可的2.4GHz ISM 射频频段。它从一开始就设计为超低功耗 (ULP)无线技术。它利用许多智能手段最大限度地降低功耗。

蓝牙低功耗架构共有两种芯片构成:单模芯片和双模芯片。

  • 蓝牙单模芯片可以和其它单模芯片及双模芯片通信,此时后者需要使用自身架构中的蓝牙低功耗技术部分进行收发数据。
  • 双模芯片也能与标准蓝牙技术及使用传统蓝牙架构的其它双模芯片通信。

蓝牙专业词汇(原文)

SIG

蓝牙技术联盟 (Bluetooth Special Interest Group)是一家贸易协会,由电信、计算机、汽车制造、工业自动化和网络行业的领先厂商组成。该小组致力于推动蓝牙无线技术的发展,为短距离连接移动设备制定低成本的无线规范,并将其推向市场。

Profile

Bluetooth 的一个很重要特性,就是所有的 Bluetooth 产品都必须实现全部的Bluetooth 规范。为了更容易的保持 Bluetooth 设备之间的兼容, Bluetooth 规范中定义了Profile 。 Profile 定义了设备如何实现一种连接或者应用,你可以把 Profile 理解为连接层或者应用层的协议规范
蓝牙组织规定了一些标准的 profile ,例如 HID OVER GATT ,防丢器 ,心率计等。每个 profile 中会包含多个 service ,每个 service 代表从机的一种能力。

service

service 可以理解为一个服务 。在 BLE 从机中,通过有多个服务,例如电量信息服务、系统信息服务等,每个 service 中又包含多个 characteristic 特征值 。每个具体的characteristic 特征值才是 BLE 通信的主题。比如当前的电量是 80%,所以会通过电量的 characteristic 特征值存在从机的 profile 里,这样主机就可以通过这个 characteristic 来读取 80% 这个数据。

characteristic

Characteristic 特征值 。 BLE 主从机的通信均是通过 characteristic 来实现,可以理解为一个标签,一个属性,通过这个标签可以获取或者写入想要的内容。

decriptor

Decriptor 描述符 。描述符就是描述 Characteristic 的,描述符有读写属性,描述符也可以被读写。

UUID

UUID,统一识别码,我们刚才提到的 service 和 characteristic,都需要一个唯一的UUID 来标识。

SIG 定义 UUID 共用了一个 基本 UUID : 0x0000xxxx-0000-1000-8000-00805F9B34FB ,
总共 128 位。为了进一步简化基本 UUID ,每一个 SIG 定义的属性有一个唯一的 16 位 UUID ,以代替上面的基本 UUID 的‘x ’部分。使用 16 位的 UUID 便于记忆和操作,例如 SIG 定义了“ Device Information ”的 16 位 UUID 为 0x180A 。

蓝牙的几个 Profile(原文)

在所有的 Profile 中,有四种是基本的 Profile ,这些 Profile 会被其它的 Profile 使用,它们包括 GAP/SDAP/SPP/GOEP Profile 。

GAP Profile

GAP Profile: Generic Access Profile,该 Profile 保证不同的 Bluetooth 产品可以互相发现对方并建立连接。 GAP 规定的是一些一般性的运行任务。因此,它具有强制性,并作为所有其它蓝牙应用规范的基础 。

SDAP Profile

SDAP Profile: Service Discovery Application Profile,通过该 Profile,一个 Bluetooth 设备可以找到其它 Bluetooth 设备提供的服务,以及查询相关的信息。

SPP Profile

全称 Serial Port Profile ,定义了如何在两台 BT 设备之间建立虚拟串口并进行连接。
例如,在两台电脑或者 Labtop 之间就可以建立这种连接。

GOEP Profile

GOEP Profile: Generic Object Exchange Profile,通用对象交换。这个 Profile的名字有些费解,它定义的是数据的传输,包括同步,文件传输,或者推送其它的数据。可以理解为与内容无关的传输层协议,可以被任何应用用来传输自己定义的数据对象。

A2DP Profile

A2DP Profile 全名是 Advenced Audio Distribution Profile 蓝牙音频传输模型协定。

DUN Profile

DUN Profile 全称 Dial-up Networking (DUN) Profile,实现一台蓝牙设备通过另外一个带无线功能的蓝牙设备共享上网。

VRCP Profile

AVRCP(Audio/Video Remote Control Profile ),也就是音频 / 视频远程控制配置文件。

HID Profile

HID 全称 Human Interface Device Profile, 即人机接口设备 Profile。

低功耗蓝牙

现在低功耗蓝牙(BLE )连接都是建立在 GATT (Generic Attribute Profile) 协议之上。 GATT 是一个在蓝牙连接之上的发送和接收很短的数据段的通用规范,这些很短的数据
段被称为属性(Attribute )。

GAP 使你的设备被其他设备可见,并决定了你的设备是否可以或者怎样与合同设备进行交互。例如 Beacon 设备就只是向外广播,不支持连接,小米手环就等设备就可以与中心设备连接。

设备角色

GAP 给设备定义了若干角色,其中主要的两个是:外围设备( Peripheral )和中心设备
(Central )。

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

广播数据

在 GAP 中 外围设备 通过两种方式向外广播数据: Advertising Data Payload ( 广播数据 )和 Scan Response Data Payload ( 扫描回复 ),每种数据最长可以包含 31 byte 。

这里广播数据是必需的,因为外设必需不停的向外广播,让中心设备知道它的存在。扫描回
复是可选的,中心设备可以向外设请求扫描回复,这里包含一些设备额外的信息,例如设备
的名字。

广播流程

GAP 的广播工作流程如下图所示。

在这里插入图片描述

从图中我们可以清晰看出广播数据和扫描回复数据是怎么工作的。外围设备会设定一个广播间隔,每个广播间隔中,它会重新发送自己的广播数据。广播间隔越长,越省电,同时也不太容易扫描到。

广播的网络拓扑结构

大部分情况下,外设通过广播自己来让中心设备发现自己,并建立 GATT 连接,从而进行更多的数据交换。也有些情况是不需要连接的,只要外设广播自己的数据即可。用这种方式主要目的是让外围设备,把自己的信息发送给多个中心设备。因为基于 GATT 连接的方式 的,只能是一个外设连接一个中心设备。使用广播这种方式最典型的应用就是苹果的iBeacon 。广播工作模式下的网络拓扑图如下:

在这里插入图片描述

GATT

GATT 的全名是 Generic Attribute Profile (普通属性协议),它定义两个 BLE 设备通过叫做 Service 和 Characteristic 的东西进行通信。 GATT 就是使用了 ATT(Attribute Protocol )协议, ATT 协议把 Service, Characteristic 对应的数据保存在一个查找表中,查表使用 16 bit ID 作为每一项的索引。

一旦两个设备建立起了连接,GATT 就开始起作用了,这也意味着,你必需完成前面的 GAP
协议。这里需要说明的是 GATT 连接,必需先经过 GAP 协议 。

实际上,我们在 Android 开发中,可以直接使用设备的 MAC 地址发起连接,可以不经过扫描的步骤。这并不意味不需要经过GAP,实际上在芯片级别已经给你做好了,蓝牙芯片发起连接,总是先扫描设备,扫描到了才会发起连接。

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

中心设备和外设需要双向通信的话,唯一的方式就是建立 GATT 连接。

GATT 连接的网络拓扑

一个外设只能连接一个中心设备,而一个中心设备可以连接多个外设。一旦建立起了连接,通信就是双向的了,对比前面的 GAP 广播的网络拓扑, GAP 通信是单向的。如果你要让两个设备外设能通信,就只能通过中心设备中转。

在这里插入图片描述

GATT 通信事务

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

一旦连接建立,外设将会给中心设备建议一个连接间隔(Connection Interval),这样中心设备就会在每个连接间隔尝试去重新连接, 检查是否有新的数据 。但是这个连接间隔只是一个建议 ,你的中心设备可能并不会严格按照这个间隔来执行,例如你的中心设备正在忙于连接其他的外设,或者中心设备资源太忙。

下图展示一个外设(GATT 服务端)和中心设备(GATT 客户端)之间的数据交换流程,可以看到的是, 每次都是主设备发起请求 :

在这里插入图片描述

GATT 结构

GATT 事务是建立在嵌套的 Profiles, Services 和 Characteristics 之上的的,如下图所示:

在这里插入图片描述

Profile

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

Service

是把数据分成一个个的独立逻辑项,它包含一个或者多个 Characteristic。每个 Service 有一个 UUID 唯一标识。 UUID 有 16 bit 的,或者 128 bit 的。6 bit 的 UUID 是官方通过认证的,需要花钱购买,128 bit 是自定义的,这个就可以自己随便设置。

官方通过了一些标准 Service,完整列表在这里(链接失效了)。以 心率 Heart Rate Service为 例 , 可 以 看 到 它 的 官 方 通 过16 bit UUID 是 0x180D , 包 含 3 个Characteristic:Heart Rate Measurement(心率测量), Body Sensor Location(车身传感器位置) 和 Heart Rate Control Point(心率控制点),并且定义了只有第一个是必须的,其他是可选实现的。

Characteristic

在 GATT 事务中的最低界别的是 Characteristic,Characteristic 是最小的逻辑数据单元,当然它可能包含一个组关联的数据,例如加速度计的 X/Y/Z 三轴值。

与 Service 类似,每个 Characteristic 用 16 bit 或者 128 bit 的 UUID唯一标识。你可以免费
使用 Bluetooth SIG 官方定义的标准 Characteristic,使用官方定义的,可以确保 BLE 的软件和硬件能相互理解。当然,你可以自定义 Characteristic,这样的话,就只有你自己的软件和外设能够相互理解。

举个例子,心率 Heart Rate Measurement Characteristic,这是上面提到的 Heart Rate Service 必需实现的 Characteristic,它的 UUID 是 0x2A37。
它的数据结构是,开始 8 bit 定义心率数据格式(是 UINT8 还是 UINT16?),接下来就是对应格式的实际心率数据。

实际上,和 BLE 外设打交道,主要是通过 Characteristic。你可以从 Characteristic 读取数据,也可以往 Characteristic 写数据。这样就实现了双向的通信。

所以你可以自己实现一个类似串口(UART)的 Sevice,这个 Service 中包含两个Characteristic,一个被配置只读的通道(RX),另一个配置为只写的通道(TX)。

更多内容

Bluetooth SIG 官方文档,如果想深入了解,可以精读。
 蓝牙核心协议文档
 Bluetooth Developer Portal
 官方通过的 BLE Profile
 官方通过的 BLE Service
 官方通过的 BLE Characteristic

 给大家推荐一本书《低功耗蓝牙权威指南》
 教程源码地址: https://github.com/HX-IoT/

BLE蓝牙参数设置:蓝牙扫描列表数量/扫描包/扫描响应包设置等

https://blog.csdn.net/WHMTBYY/article/details/125089358
https://blog.csdn.net/WHMTBYY/article/details/125861715

Android BLE 快速开发示例

FastBle的使用

声明权限

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<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以后,这两个权限是必须的,蓝牙扫描周围的设备需要获取模糊的位置信息。这两个权限属于同一组危险权限,在清单文件中声明之后,还需要再运行时动态获取。

初始化及配置

@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);BleManager.getInstance().init(getApplication());BleManager.getInstance().enableLog(true).setReConnectCount(1, 5000).setOperateTimeout(5000);
}

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

扫描外围设备

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

  • 判断当前Android设备是否支持BLE。
    Android 4.3以后系统中加入了蓝牙BLE的功能。
 BleManager.getInstance().isSupportBle();
  • 判断当前Android设备的蓝牙是否已经打开。
    可以直接调用下面的判断方法来判断本机是否已经打开了蓝牙,如果没有,向用户抛出提示。
BleManager.getInstance().isBlueEnable();
  • 主动打开蓝牙。
    除了判断蓝牙是否打开给以用户提示之外,我们也可以通过程序直接帮助用户打开蓝牙开关,打开方式有这几种:
    方法1:通过蓝牙适配器直接打开蓝牙。
BleManager.getInstance().enableBluetooth();

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

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

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

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

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

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

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

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

设备信息

扫描得到的BLE外围设备,会以BleDevice对象的形式,作为后续操作的最小单元对象。它本身含有这些信息:

  • String getName():蓝牙广播名
  • String getMac():蓝牙Mac地址
  • byte[] getScanRecord(): 被扫描到时候携带的广播数据
  • int getRssi() :被扫描到时候的信号强度

后续进行设备连接、断开、判断设备状态,读写操作等时候,都会用到这个对象。可以把它理解为外围蓝牙设备的载体,所有对外围蓝牙设备的操作,都通过这个对象来传导。

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

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

    BleManager.getInstance().connect(bleDevice, new BleGattCallback() {@Overridepublic void onStartConnect() {}@Overridepublic void onConnectFail(BleException exception) {}@Overridepublic void onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status) {}@Overridepublic void onDisConnected(boolean isActiveDisConnected, BleDevice bleDevice, BluetoothGatt gatt, int status) {}});
  • 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。

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

    List<BluetoothGattService> serviceList = bluetoothGatt.getServices();for (BluetoothGattService service : serviceList) {UUID uuid_service = service.getUuid();List<BluetoothGattCharacteristic> characteristicList= service.getCharacteristics();for(BluetoothGattCharacteristic characteristic : characteristicList) {UUID uuid_chara = characteristic.getUuid();}}

协议通信

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

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

关闭notify

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

打开indicate

  BleManager.getInstance().indicate(bleDevice,uuid_service,uuid_characteristic_indicate,new BleIndicateCallback() {@Overridepublic void onIndicateSuccess() {// 打开通知操作成功}@Overridepublic void onIndicateFailure(BleException exception) {// 打开通知操作失败}@Overridepublic void onCharacteristicChanged(byte[] data) {// 打开通知后,设备发过来的数据将在这里出现}});

关闭indicate

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

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

  • 读写
  BleManager.getInstance().read(bleDevice,uuid_service,uuid_characteristic_read,new BleReadCallback() {@Overridepublic void onReadSuccess(byte[] data) {// 读特征值数据成功}@Overridepublic void onReadFailure(BleException exception) {// 读特征值数据失败}});BleManager.getInstance().write(bleDevice,uuid_service,uuid_characteristic_write,data,new BleWriteCallback() {@Overridepublic void onWriteSuccess(int current, int total, byte[] justWrite) {// 发送数据到设备成功(分包发送的情况下,可以通过方法中返回的参数可以查看发送进度)}@Overridepublic void onWriteFailure(BleException exception) {// 发送数据到设备失败}});

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

  • 设置最大传输单元MTU
  BleManager.getInstance().setMtu(bleDevice, mtu, new BleMtuChangedCallback() {@Overridepublic void onSetMTUFailure(BleException exception) {// 设置MTU失败}@Overridepublic void onMtuChanged(int mtu) {// 设置MTU成功,并获得当前设备传输支持的MTU值}});
  • 获取设备的实时信号强度Rssi
  BleManager.getInstance().readRssi(bleDevice,new BleRssiCallback() {@Overridepublic void onRssiFailure(BleException exception) {// 读取设备的信号强度失败}@Overridepublic void onRssiSuccess(int rssi) {// 读取设备的信号强度成功}});

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

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

BLE开发基础

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

蓝牙简介

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

蓝牙技术的版本演进

  • 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对室内位置进行辅助定位,提高传输速度,增加有效工作距离,主要是针对物联网方向的改进。

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进行相互通信。

蓝牙的广播和扫描

以下内容部分参考自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 发起请求。需要注意的是,所有的通信事件,都是由客户端发起,并且接收服务端的响应。

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。

在这里插入图片描述

  • 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 Location 和 Heart 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。

FastBle源码解析

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

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

public BluetoothGatt connect(BleDevice bleDevice, BleGattCallback bleGattCallback) {if (bleGattCallback == null) {throw new IllegalArgumentException("BleGattCallback can not be Null!");}if (!isBlueEnable()) {BleLog.e("Bluetooth not enable!");bleGattCallback.onConnectFail(new OtherException("Bluetooth not enable!"));return null;}if (Looper.myLooper() == null || Looper.myLooper() != Looper.getMainLooper()) {BleLog.w("Be careful: currentThread is not MainThread!");}if (bleDevice == null || bleDevice.getDevice() == null) {bleGattCallback.onConnectFail(new OtherException("Not Found Device Exception Occurred!"));} else {BleBluetooth bleBluetooth = new BleBluetooth(bleDevice);boolean autoConnect = bleScanRuleConfig.isAutoConnect();return bleBluetooth.connect(bleDevice, autoConnect, bleGattCallback);}return null;
}

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

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

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

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

public synchronized void addBleBluetooth(BleBluetooth bleBluetooth) {if (bleBluetooth == null) {return;}if (!bleLruHashMap.containsKey(bleBluetooth.getDeviceKey())) {bleLruHashMap.put(bleBluetooth.getDeviceKey(), bleBluetooth);}
}public synchronized void removeBleBluetooth(BleBluetooth bleBluetooth) {if (bleBluetooth == null) {return;}if (bleLruHashMap.containsKey(bleBluetooth.getDeviceKey())) {bleLruHashMap.remove(bleBluetooth.getDeviceKey());}
}

回到BleBlutooth的connect方法:

public synchronized BluetoothGatt connect(BleDevice bleDevice,boolean autoConnect,BleGattCallback callback) {addConnectGattCallback(callback);isMainThread = Looper.myLooper() != null && Looper.myLooper() == Looper.getMainLooper();BluetoothGatt gatt;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {gatt = bleDevice.getDevice().connectGatt(BleManager.getInstance().getContext(),autoConnect, coreGattCallback, TRANSPORT_LE);} else {gatt = bleDevice.getDevice().connectGatt(BleManager.getInstance().getContext(),autoConnect, coreGattCallback);}if (gatt != null) {if (bleGattCallback != null)bleGattCallback.onStartConnect();connectState = BleConnectState.CONNECT_CONNECTING;}return gatt;
}

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

private BluetoothGattCallback coreGattCallback = new BluetoothGattCallback() {@Overridepublic void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {super.onConnectionStateChange(gatt, status, newState);if (newState == BluetoothGatt.STATE_CONNECTED) {gatt.discoverServices();} else if (newState == BluetoothGatt.STATE_DISCONNECTED) {closeBluetoothGatt();BleManager.getInstance().getMultipleBluetoothController().removeBleBluetooth(BleBluetooth.this);if (connectState == BleConnectState.CONNECT_CONNECTING) {connectState = BleConnectState.CONNECT_FAILURE;if (isMainThread) {Message message = handler.obtainMessage();message.what = BleMsg.MSG_CONNECT_FAIL;message.obj = new BleConnectStateParameter(bleGattCallback, gatt, status);handler.sendMessage(message);} else {if (bleGattCallback != null)bleGattCallback.onConnectFail(new ConnectException(gatt, status));}} else if (connectState == BleConnectState.CONNECT_CONNECTED) {connectState = BleConnectState.CONNECT_DISCONNECT;if (isMainThread) {Message message = handler.obtainMessage();message.what = BleMsg.MSG_DISCONNECTED;BleConnectStateParameter para = new BleConnectStateParameter(bleGattCallback, gatt, status);para.setAcitive(isActiveDisconnect);para.setBleDevice(getDevice());message.obj = para;handler.sendMessage(message);} else {if (bleGattCallback != null)bleGattCallback.onDisConnected(isActiveDisconnect, bleDevice, gatt, status);}}}}@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status) {super.onServicesDiscovered(gatt, status);BleLog.i("BluetoothGattCallback:onServicesDiscovered "+ '\n' + "status: " + status+ '\n' + "currentThread: " + Thread.currentThread().getId());if (status == BluetoothGatt.GATT_SUCCESS) {bluetoothGatt = gatt;connectState = BleConnectState.CONNECT_CONNECTED;isActiveDisconnect = false;BleManager.getInstance().getMultipleBluetoothController().addBleBluetooth(BleBluetooth.this);if (isMainThread) {Message message = handler.obtainMessage();message.what = BleMsg.MSG_CONNECT_SUCCESS;BleConnectStateParameter para = new BleConnectStateParameter(bleGattCallback, gatt, status);para.setBleDevice(getDevice());message.obj = para;handler.sendMessage(message);} else {if (bleGattCallback != null)bleGattCallback.onConnectSuccess(getDevice(), gatt, status);}} else {closeBluetoothGatt();connectState = BleConnectState.CONNECT_FAILURE;if (isMainThread) {Message message = handler.obtainMessage();message.what = BleMsg.MSG_CONNECT_FAIL;message.obj = new BleConnectStateParameter(bleGattCallback, gatt, status);handler.sendMessage(message);} else {if (bleGattCallback != null)bleGattCallback.onConnectFail(new ConnectException(gatt, status));}}}@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {super.onCharacteristicChanged(gatt, characteristic);Iterator iterator = bleNotifyCallbackHashMap.entrySet().iterator();while (iterator.hasNext()) {Map.Entry entry = (Map.Entry) iterator.next();Object callback = entry.getValue();if (callback instanceof BleNotifyCallback) {BleNotifyCallback bleNotifyCallback = (BleNotifyCallback) callback;if (characteristic.getUuid().toString().equalsIgnoreCase(bleNotifyCallback.getKey())) {Handler handler = bleNotifyCallback.getHandler();if (handler != null) {Message message = handler.obtainMessage();message.what = BleMsg.MSG_CHA_NOTIFY_DATA_CHANGE;message.obj = bleNotifyCallback;Bundle bundle = new Bundle();bundle.putByteArray(BleMsg.KEY_NOTIFY_BUNDLE_VALUE, characteristic.getValue());message.setData(bundle);handler.sendMessage(message);}}}}iterator = bleIndicateCallbackHashMap.entrySet().iterator();while (iterator.hasNext()) {Map.Entry entry = (Map.Entry) iterator.next();Object callback = entry.getValue();if (callback instanceof BleIndicateCallback) {BleIndicateCallback bleIndicateCallback = (BleIndicateCallback) callback;if (characteristic.getUuid().toString().equalsIgnoreCase(bleIndicateCallback.getKey())) {Handler handler = bleIndicateCallback.getHandler();if (handler != null) {Message message = handler.obtainMessage();message.what = BleMsg.MSG_CHA_INDICATE_DATA_CHANGE;message.obj = bleIndicateCallback;Bundle bundle = new Bundle();bundle.putByteArray(BleMsg.KEY_INDICATE_BUNDLE_VALUE, characteristic.getValue());message.setData(bundle);handler.sendMessage(message);}}}}}@Overridepublic void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {super.onDescriptorWrite(gatt, descriptor, status);Iterator iterator = bleNotifyCallbackHashMap.entrySet().iterator();while (iterator.hasNext()) {Map.Entry entry = (Map.Entry) iterator.next();Object callback = entry.getValue();if (callback instanceof BleNotifyCallback) {BleNotifyCallback bleNotifyCallback = (BleNotifyCallback) callback;if (descriptor.getCharacteristic().getUuid().toString().equalsIgnoreCase(bleNotifyCallback.getKey())) {Handler handler = bleNotifyCallback.getHandler();if (handler != null) {Message message = handler.obtainMessage();message.what = BleMsg.MSG_CHA_NOTIFY_RESULT;message.obj = bleNotifyCallback;Bundle bundle = new Bundle();bundle.putInt(BleMsg.KEY_NOTIFY_BUNDLE_STATUS, status);message.setData(bundle);handler.sendMessage(message);}}}}iterator = bleIndicateCallbackHashMap.entrySet().iterator();while (iterator.hasNext()) {Map.Entry entry = (Map.Entry) iterator.next();Object callback = entry.getValue();if (callback instanceof BleIndicateCallback) {BleIndicateCallback bleIndicateCallback = (BleIndicateCallback) callback;if (descriptor.getCharacteristic().getUuid().toString().equalsIgnoreCase(bleIndicateCallback.getKey())) {Handler handler = bleIndicateCallback.getHandler();if (handler != null) {Message message = handler.obtainMessage();message.what = BleMsg.MSG_CHA_INDICATE_RESULT;message.obj = bleIndicateCallback;Bundle bundle = new Bundle();bundle.putInt(BleMsg.KEY_INDICATE_BUNDLE_STATUS, status);message.setData(bundle);handler.sendMessage(message);}}}}}@Overridepublic void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {super.onCharacteristicWrite(gatt, characteristic, status);Iterator iterator = bleWriteCallbackHashMap.entrySet().iterator();while (iterator.hasNext()) {Map.Entry entry = (Map.Entry) iterator.next();Object callback = entry.getValue();if (callback instanceof BleWriteCallback) {BleWriteCallback bleWriteCallback = (BleWriteCallback) callback;if (characteristic.getUuid().toString().equalsIgnoreCase(bleWriteCallback.getKey())) {Handler handler = bleWriteCallback.getHandler();if (handler != null) {Message message = handler.obtainMessage();message.what = BleMsg.MSG_CHA_WRITE_RESULT;message.obj = bleWriteCallback;Bundle bundle = new Bundle();bundle.putInt(BleMsg.KEY_WRITE_BUNDLE_STATUS, status);bundle.putByteArray(BleMsg.KEY_WRITE_BUNDLE_VALUE, characteristic.getValue());message.setData(bundle);handler.sendMessage(message);}}}}}@Overridepublic void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {super.onCharacteristicRead(gatt, characteristic, status);Iterator iterator = bleReadCallbackHashMap.entrySet().iterator();while (iterator.hasNext()) {Map.Entry entry = (Map.Entry) iterator.next();Object callback = entry.getValue();if (callback instanceof BleReadCallback) {BleReadCallback bleReadCallback = (BleReadCallback) callback;if (characteristic.getUuid().toString().equalsIgnoreCase(bleReadCallback.getKey())) {Handler handler = bleReadCallback.getHandler();if (handler != null) {Message message = handler.obtainMessage();message.what = BleMsg.MSG_CHA_READ_RESULT;message.obj = bleReadCallback;Bundle bundle = new Bundle();bundle.putInt(BleMsg.KEY_READ_BUNDLE_STATUS, status);bundle.putByteArray(BleMsg.KEY_READ_BUNDLE_VALUE, characteristic.getValue());message.setData(bundle);handler.sendMessage(message);}}}}}
};

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

public abstract class BleBaseCallback {private String key;private Handler handler;public String getKey() {return key;}public void setKey(String key) {this.key = key;}public Handler getHandler() {return handler;}public void setHandler(Handler handler) {this.handler = handler;}
}

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

private void handleCharacteristicWriteCallback(BleWriteCallback bleWriteCallback,String uuid_write) {if (bleWriteCallback != null) {writeMsgInit();bleWriteCallback.setKey(uuid_write);bleWriteCallback.setHandler(mHandler);mBleBluetooth.addWriteCallback(uuid_write, bleWriteCallback);mHandler.sendMessageDelayed(mHandler.obtainMessage(BleMsg.MSG_CHA_WRITE_START, bleWriteCallback),BleManager.getInstance().getOperateTimeout());}
}

上面这段源码解释了这个机制,每一次write操作之后,都会对传入的callback进行唯一性标记,再通过handler用来传递操作结果,同时将这个callback加入这个设备的BleBlutooth对象的callback池中管理。

这样就形成了APP维持一个设备连接池,一个设备连接池管理多个设备管理者,一个设备管理者管理多个不同类别的callback集合,一个callback集合中含有多个同类的不同特征的callback。

在这里插入图片描述

运行结果:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
源码链接:GitHub

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_337132.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringAOP的相关概念

文章目录 一.什么是AOP二.AOP的组成部分三.SpringAOP的实现3.1 增加SpringAOP依赖3.2 创建切面3.2 创建切点3.3 创建通知3.4 创建连接点 四.SpringAOP的实现原理4.1 JDK动态代理4.2 CGLIB 动态代理总结 一.什么是AOP AOP&#xff0c;全称为Aspect-Oriented Programming&#x…

解决 tensorflow 出现的 ImportError: Could not find the DLL(s) ‘msvcp140_1.dll‘. 问题

在安装完tensorflow库后出现 问题详述&#xff1a; ImportError: Could not find the DLL(s) msvcp140_1.dll. TensorFlow requires that these DLLs be installed in a directory that is named in your %PATH% environment variable. You may install these DLLs by downlo…

新零售行业如何做会员管理和会员营销

蚓链数字化营销系统全渠道会员管理解决方案&#xff0c;线上线下统一管理&#xff0c;打造私域流量&#xff0c;微信、门店会员全渠道管理&#xff0c;打通私域流量池&#xff0c;实现裂变营销&#xff1a; 开启新零售之路&#xff0c;必然要摒弃原有的管理模式&#xff0c;大…

郑州多域名https证书

多域名https证书是https证书中比较特殊的一款&#xff0c;它保护的域名记录是众多https证书中最灵活的。不管是DV基础型的多域名https证书还是OV企业型和EV增强型的多域名https证书既可以保护多个主域名或者子域名&#xff0c;还可以主域名子域名随意组合&#xff0c;只要申请者…

【动态规划part11】| 123.买卖股票的最佳时机III、188.买卖股票的最佳时机IV

目录 &#x1f388;LeetCode123.买卖股票的最佳时机III &#x1f388;LeetCode188.买卖股票的最佳时机IV &#x1f388;LeetCode123.买卖股票的最佳时机III 链接&#xff1a;123.买卖股票的最佳时机III 给定一个数组&#xff0c;它的第 i 个元素是一支给定的股票在第 i…

无涯教程-jQuery - Pulsate方法函数

Pulsate 效果可以与effect()方法一起使用。这会使元素的不透明性产生多次脉冲。 Pulsate - 语法 selector.effect( "pulsate", {arguments}, speed ); 这是所有参数的描述- times - 脉动的时间。默认值为3。model - 效果的模式。可以是"显示(show)"&a…

基于Java+SpringBoot+vue前后端分离技术交流和分享平台设计实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

Moke 一百万条 Mysql 的数据

文章目录 前言创建数据库创建表结构生成数据 前言 想研究一下&#xff0c;数据量大的情况下&#xff0c;如何优化前端分页&#xff0c;所以需要 Moke 一些数据 创建数据库 在 Mysql的基础上&#xff0c;可以写个语句执行 CREATE DATABASE test_oneMillion; USE test_oneMi…

JAVA SE -- 第十一天

&#xff08;全部来自“韩顺平教育”&#xff09; 异常-Exception 一、异常介绍 1、基本介绍 Java语言中&#xff0c;将程序执行中发生的不正常情况为“异常”&#xff08;开发过程中的语法错误和逻辑错误不是异常&#xff09; 2、执行过程中发生的异常事件可分为两大类 …

Reinforcement Learning with Code 【Chapter 9. Policy Gradient Methods】

Reinforcement Learning with Code This note records how the author begin to learn RL. Both theoretical understanding and code practice are presented. Many material are referenced such as ZhaoShiyu’s Mathematical Foundation of Reinforcement Learning, . 文章…

opencv+ffmpeg环境(ubuntu)搭建全面详解

一.先讲讲opencv和ffmpeg之间的关系 1.1它们之间的联系 我们知道opencv主要是用来做图像处理的&#xff0c;但也包含视频解码的功能&#xff0c;而在视频解码部分的功能opencv是使用了ffmpeg。所以它们都是可以处理图像和视频的编解码&#xff0c;我个人感觉两个的侧重点不一…

SpringBoot项目部署(前后端分离、Linux部署项目)

一、架构 部署环境说明&#xff1a; 192.168.122.100(服务器A)&#xff1a; Nginx&#xff1a;部署前端项目、配置反向代理 Mysql&#xff1a;主从复制结构中的主库 192.168.122.131 (服务器B)&#xff1a; jdk: 运行Java项目 git:版本控制工具 (从gitee中拉取源码) maven:…

数据结构:快速的Redis有哪些慢操作?

redis 为什么要这莫快&#xff1f;一个就是他是基于内存的&#xff0c;另外一个就是他是他的数据结构 说到这儿&#xff0c;你肯定会说&#xff1a;“这个我知道&#xff0c;不就是 String&#xff08;字符串&#xff09;、List&#xff08;列表&#xff09;、 Hash&#xff08…

【SpringCloud Alibaba】(五)服务雪崩与容错方案

在前面的文章中&#xff0c;我们实现了用户微服务、商品微服务和订单微服务之间的远程调用&#xff0c;并且实现了服务调用的负载均衡。 但是&#xff0c;现在系统中存在着一个很明显的问题&#xff1a;那就是如果系统的并发量上来后&#xff0c;系统并没有容错的能力&#xf…

Java | 继承、多态、抽象类与接口

目录 一、类的继承 二、Object类 2.1 getClass()方法 2.2 toString()方法 2.3 equals()方法 三 、对象类型的转换 3.1 向上转换 3.2 向下转型 四、使用instanceof关键字判断对象类型 五、方法的重载 六、final关键字 6.1 final变量 6.2 final方法 6.3 final类 七…

LeetCode 1857. Largest Color Value in a Directed Graph【拓扑排序,动态规划】困难

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

【UE5】快速认识入门

目录 &#x1f31f;1. 快速安装&#x1f31f;2. 简单快捷键操作&#x1f31f;3. 切换默认的打开场景&#x1f31f;4. 虚幻引擎术语 &#x1f31f;1. 快速安装 进入Unreal Engine 5官网进行下载即可&#xff1a;UE5 &#x1f4dd;官方帮助文档 打开后在启动器里创建5.2.1引擎…

冯诺依曼体系的认识、来源、原理、组成、功能和特点

目录 一.认识冯诺依曼 二.冯诺依曼体系结构的来源 三.冯诺依曼体系结构计算机 3.1工作原理 3.2组成部件 3.3功能和特点 &#x1f381;个人主页&#xff1a;tq02的博客_CSDN博客-C语言,Java,Java数据结构领域博主 &#x1f3a5; 本文由 tq02 原创&#xff0c;首发于 CSDN&…

C++笔记之vector的resize()和clear()用法

C笔记之vector的resize()和clear()用法 code review! 文章目录 C笔记之vector的resize()和clear()用法1.resize()2.clear() 1.resize() 运行 2.clear() 运行

Jsp+Ssh+Mysql实现的简单的企业物资信息管理系统项目源码附带视频指导运行教程

由jspssh&#xff08;springstruts2mysql&#xff09;实现的企业物资信息管理系统&#xff0c;系统功能比较简单&#xff0c;实现了基本的管理员、操作员等用户管理、物品分类管理、物品管理、入库管理、出库管理、库存预警、客户管理、供应商管理等基本功能需要的可以联系我分…