Android BLE 快速开发示例

2023-10-29

目录

概述

1. FastBle的使用

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

3. FastBle源码解析


概述

        思来想去,还是写这篇博文,记录一下,当时学习BLE的一些心得,重捡回当前Android知识。想深入了解蓝牙通讯知识,这个案例是非常不错的选择。

FastBle框架分3步构成

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

1. FastBle的使用

1.1 声明权限

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

1.2. 初始化及配置

@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)方法。此外,可以进行一些自定义的配置,比如是否显示框架内部日志,重连次数和重连时间间隔,以及操作超时时间。

1.3. 扫描外围设备

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() {
          @Override
          public void onScanStarted(boolean success) {
          }
    
          @Override
          public void onLeScan(BleDevice bleDevice) {
          }
    
          @Override
          public void onScanning(BleDevice bleDevice) {
          }
    
          @Override
          public void onScanFinished(List<BleDevice> scanResultList) {
          }
      });
    

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. 连接、断连、监控连接状态

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

    BleManager.getInstance().connect(bleDevice, new BleGattCallback() {
        @Override
        public void onStartConnect() {
        }

        @Override
        public void onConnectFail(BleException exception) {
        }

        @Override
        public void onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status) {
        }

        @Override
        public 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。

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:

    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();
        }
    }

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

      BleManager.getInstance().notify(
              bleDevice,
              uuid_service,
              uuid_characteristic_notify,
              new BleNotifyCallback() {
                  @Override
                  public void onNotifySuccess() {
                      // 打开通知操作成功
                  }
    
                  @Override
                  public void onNotifyFailure(BleException exception) {
                      // 打开通知操作失败
                  }
    
                  @Override
                  public 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() {
                  @Override
                  public void onIndicateSuccess() {
                      // 打开通知操作成功
                  }
    
                  @Override
                  public void onIndicateFailure(BleException exception) {
                      // 打开通知操作失败
                  }
    
                  @Override
                  public void onCharacteristicChanged(byte[] data) {
                      // 打开通知后,设备发过来的数据将在这里出现
                  }
              });
    

    关闭indicate

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

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

  • 读写

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

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

  • 设置最大传输单元MTU

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

      BleManager.getInstance().readRssi(
              bleDevice,
              new BleRssiCallback() {
    
                  @Override
                  public void onRssiFailure(BleException exception) {
                      // 读取设备的信号强度失败
                  }
    
                  @Override
                  public void onRssiSuccess(int rssi) {
                      // 读取设备的信号强度成功
                  }
              });
    

在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()方法:

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就是控制多设备连接的。它里面有增加和移除设备的方法,如下图的addBleBluetoothremoveBleBluetooth,传入的参数就是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());
    }
}

回到BleBlutoothconnect方法:

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

private BluetoothGattCallback coreGattCallback = new BluetoothGattCallback() {

    @Override
    public 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);
                }
            }
        }
    }

    @Override
    public 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));
            }
        }
    }

    @Override
    public 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);
                    }
                }
            }
        }
    }

    @Override
    public 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);
                    }
                }
            }
        }
    }

    @Override
    public 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);
                    }
                }
            }
        }
    }

    @Override
    public 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


 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android BLE 快速开发示例 的相关文章

  • 为什么是 javascript:history.go(-1);无法在移动设备上工作?

    首先 一些背景 我有一个向用户呈现搜索页面 html 表单 的应用程序 填写标准并单击 搜索 按钮后 结果将显示在标准部分下方 在结果列表中 您可以通过单击将您带到新页面的链接来查看单个结果的详细信息 在详细信息页面中 我添加了一个 返回结
  • 图像作为电子邮件附件

    我想构建一个应用程序 我可以在电子邮件中附加图像 打开图像并将其设置为我的壁纸 我想让它跨平台 所以你能告诉我是否可以使用phonegap 或者我是否必须为iphone和android构建一个本机应用程序 您好 如果您只想通过电子邮件附加图
  • 从 BroadcastReceiver 类调用活动方法

    我知道我可以做一个内部接收器类来调用接收器中的任何方法 但我的主要活动太大了 要做的事情也很多 因此 我需要一个扩展广播接收器的类 但它不是内部类 并且可以从我的主要活动中调用一种方法 我不知道是否可能 但我的活动是家庭活动和 single
  • tomcat 中受密码保护的应用程序

    我正在使用 JSP Servlet 开发一个Web应用程序 并且我使用了Tomcat 7 0 33 as a web container 所以我的要求是tomcat中的每个应用程序都会password像受保护的manager applica
  • 如何对不同的参数类型使用相同的java方法?

    我的问题 我有 2 个已定义的记录 创建对象请求 更新对象请求 必须通过实用方法进行验证 由于这两个对象具有相同的字段 因此可以对这两种类型应用相同的验证方法 现在我只是使用两种方法进行重载 但它很冗长 public record Crea
  • Android - AudioRecord类不读取数据,audioData和fftArray返回零

    我是 Android 新手 一直在开发音调分析器应用程序 最低 SDK 8 我读了很多关于如何实现 Audiorecord 类的文章 但我想知道为什么它在我录制时不读取任何数据 我尝试显示 audioData 和 fftArray 的值 但
  • Android 构建发布失败,原因为:java.lang.ArrayIndexOutOfBoundsException:213(pr​​oguard 问题)

    我的项目使用调试构建变体构建得很好 但使用发布变体 Android Studio 会抛出 引起原因 java lang ArrayIndexOutOfBoundsException 213 可能是什么问题 如果我设置minifyEnable
  • 为什么 Java 8 不允许非公共默认方法?

    让我们举个例子 public interface Testerface default public String example return Hello public class Tester implements Testerface
  • Android 中如何通过彩信发送图片?

    我正在开发多媒体应用程序 我正在通过相机捕获一张图像 并希望将该图像和文本发送到其他号码 但我不知道如何通过彩信发送图像 MMS 只是一个 http post 请求 您应该使用执行请求额外的网络功能 final ConnectivityMa
  • 关键字“table”附近的语法不正确,无法提取结果集

    我使用 SQL Server 创建了一个项目 其中包含以下文件 UserDAO java public class UserDAO private static SessionFactory sessionFactory static se
  • Android 2.3 模拟器在更新位置时崩溃

    我正在使用 Eclipse 编写和调试 Android 应用程序 我需要做的事情之一是更新设备的位置 因此我尝试使用模拟器控制窗口中的位置控制面板 在 手动 选项卡上 我选择 十进制 输入有效的纬度和经度 然后单击 发送 不幸的是 接下来发
  • 我如何在java中读取二进制数据文件

    因此 我正在为学校做一个项目 我需要读取二进制数据文件并使用它来生成角色的统计数据 例如力量和智慧 它的设置是让前 8 位组成一个统计数据 我想知道执行此操作的实际语法是什么 是不是就像读文本文件一样 这样 File file new Fi
  • Opencv Java 灰度

    我编写了以下程序 尝试从彩色转换为灰度 Mat newImage Imgcodecs imread q1 jpg Mat image new Mat new Size newImage cols newImage rows CvType C
  • Android 中的处理程序与异步调用

    目前我正在使用处理程序来调用 Web 服务方法以使其在后台运行 问题是它需要更多的时间来给出响应 在性能方面似乎更昂贵 现在我计划使用异步调用 哪一个是最好的 Android 中的处理程序和异步调用有什么区别 请帮我想出一个最好的解决方案
  • 在 Samsung Galaxy S5 Android 5.0 上使用 MediaPlayer 加载音频流需要超过 10 秒

    由于更新至 Android 5 0 MediaPlayer 在 Samsung Galaxy S5 上无法正常工作 启动音频流后加载时间超过 10 秒 示例代码 MediaPlayer mPlayer new MediaPlayer Str
  • 上网本上可以进行Android开发吗? [关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我想使用我的上网本进行 Android 开发 但是当我尝试使用 Eclipse 运行 SDK 时 没有加载任何内容 上网本对于 Android 开发来
  • 如何检查 Android 中的同步设置

    我正在构建一个 Android 应用程序 我需要检查设备中注册的每个单独帐户的同步设置 我知道我可以通过 ContentResolver 类来做到这一点 但我遇到了一些问题 我已设法获取设备上所有帐户的列表 但我不知道在运行时从哪里获取特定
  • 找不到符号 NOTIFICATION_SERVICE?

    package com test app import android app Notification import android app NotificationManager import android app PendingIn
  • 通过系统应用程序以编程方式静默安装 apk(无需 root)

    我有带有 android sharedUserId android uid system UID 1000 的系统级应用程序 设备未root INSTALL PACKAGES 权限包含在清单中 我可以静默安装下载的 apk 吗 我已经发现这
  • Android 后台倒计时器

    我有一个 Android 应用程序 它管理一个倒计时器 类 CountDownTimer 它显示在应用程序屏幕中 以显示到达 00 00 还剩多少时间 我现在的问题是 当我按主页按钮或启动另一个应用程序时 应用程序 计时器不会在后台运行 所

随机推荐

  • 移动开发 - Android - 实现两个页面(Activity)的简单跳转(Intent)

    我们要实现的页面跳转如下图所示 第一个页面 通过点击第一个按钮 实现跳转 通过点击第二个按钮实现跳转后 点击返回按钮 结束界面 因为之前的界面还没有结束 当第二个界面结束后 露出第一个界面 使用Intent实现页面的跳转有两种方式 1 无返
  • intel cpu 漏洞 linux,Intel CPU架构漏洞越捅越大:打补丁将损失30%性能

    还记得前不久那个被曝光影响大面积Intel Core CPU产品的安全漏洞吗 这貌似只是冰山一角 就在今天人们发现了Intel处理器里一个更为致命的漏洞 从最底端的Pentium 到最新的Coffee Lake Core 无一幸免 现在全球
  • nuxt打包后文件过大的优化

    在使用nuxt js来做项目的时候 遇到了加载缓慢的问题 解决思路如下 1 大文件拆分 2 文件压缩 大文件拆分 通过nuxt build analyze或者nuxt build a命令来启用 在package json中 添加 analy
  • TensorFlow Bug记录 CUBLAS_STATUS_NOT_INITIALIZED

    昨天刚装好也能运行的tensorflow突然之间报错 tensorflow stream executor cuda cuda blas cc 366 failed to create cublas handle CUBLAS STATUS
  • 虚拟服务器和vdi,VDI虚拟化平台搭建 01--VDI基本环境准备

    说明 此文章仅用于学习研究 不做任何商业用途 VDI 虚拟桌面基础架构 Virtual Desktop Infrastructure VDI基本软件硬件版本 服务器系统版本 exsi 6 7 vcenter版本 6 7 Vmware Hor
  • Typescript 基础类型 —— 布尔值 Boolean

    在JavaScript和TypeScript里布尔值都叫做boolean 表示逻辑值 true 和 false 关键字 boolean 实例 let isRes boolean false 编译结果 var isRes false 试图给
  • 【读懂Autosar代码】-1-概述

    点击返回 Autosar从入门到精通 实战篇 总目录 案例背景 共5页精讲 聊一聊这些封装中关键字宏的命名是如何构成的 FUNC FUNC P2CONST FUNC P2VAR P2VAR P2CONST CONSTP2VAR CONSTP
  • 论文期刊一般的审稿流程

    一 经常在网上看到某某人在问什么期刊的审稿周期 或者是投稿已多久怎么还没消息之类的帖子 作为经常帮国内外期刊审稿的人 对编辑部相对有所了解 因此特意发帖告知 供大家交流 论文审稿是个复杂的过程 并不是像大部分作者想象的那样送个一个专家看个几
  • String 判断字符串是否包含某个字符

    contains 判断 s 是否包含 ss 包含返回true 不包含返回false String s 你好 String ss 你 boolean ii s contains ss if ii true System out println
  • STM32调试特定函数出现Cannot access Memory

    MDK5出现Cannot access Memory 1 问题 2 原因 3 总结 1 问题 程序每次执行到了特定函数的位置时 keil里面全速运行 一直出现Cannot access Memory 再后面就是进入硬件错误中断了 执行到这个
  • C#使用COM+实现事务控制,操作多个数据库

    其中大部分内容参考自http blog itpub net 10752043 viewspace 991224 上面的是sqlserver的操作方式 因为我实际项目是用的oracle 就拿oracle试了一下 为了自己记录或者方便其他人 其
  • Linux下如何配置环境变量

    基础知识 首先 我们来了解一下什么是 环境变量 环境变量 通俗讲是操作系统或程序执行时候默认设定的参数 比如 PATH 路径变量 当要执行某个命令或程序的时候默认寻找的路径 然后 我们再来了解一下环境变量都有哪些类型 环境变量的分类 根据变
  • C++的类型

    C 的类型 按照标准 C 只有两种类型 基本类型和复合类型 但是里面细节多导致彻底理解它们有难度 所以这里只是简单总结一下 基本类型 基本类型分成算术类型和两种特殊的类型 算术类型 算术类型分成整数类型和浮点数类型两种 整数类型 以下都是整
  • 在springboot中使用Sse(Server-sent Events)Web实时通信技术-服务器发送事件SseEmitter

    最近在练习项目时需要用到消息实时推送技术 了解到有两种实时通信技术供我选择 SSE和WebSocket 详细了解后得知SSE是基于http协议 无需导入其他依赖 特点是服务端主动给客户端推送消息 单向 适合浏览器端只做数据接收 而webso
  • 华为OD机试真题-路灯照明问题-2023年OD统一考试(B卷)

    题目描述 在一条笔直的公路上安装了N个路灯 从位置0开始安装 路灯之间间距固定为100米 每个路灯都有自己的照明半径 请计算第一个路灯和最后一个路灯之间 无法照明的区间的长度和 输入描述 第一行为一个数N 表示路灯个数 1 lt N lt
  • Pysot训练自己的数据集

    数据集预处理操作 Pysot训练自己数据集前的预处理 Vesper0412的博客 CSDN博客 Pysot源码地址 https github com STVIR pysot 1 linux系统激活环境 conda activate pyto
  • 机器学习课程学习阶段总结

    机器学习课程学习阶段总结 线性回归 逻辑回归 逻辑回归 是一种分类算法 和之前的线性回归不是同一类问题 但是对于处理问题上有相同的思想 对于线性回归问题 有较容易理解的思路 首先指定一个形式确定的 h x Tx h theta x thet
  • 笔记本连接RK61键机械键盘非损坏的win和alt对调,数字键失灵以及特殊字母键失灵恢复

    1 问题描述 在打游戏或者码字的时候 无意中按下了键盘模式切换按键 导致了键盘的假性失灵状态 具体表现为 1 win和alt键功能对调 win按键无反应 alt键出现windows窗口 虽然不影响使用 但是很别扭 2 数字键失灵 键盘打不出
  • 网络安全有哪些经典笑话

    今天程序员同事发了一个链接给我 点开是一个调侃程序员有哪些经典笑话的帖子 给我看乐了 诸如 老婆给当程序员的老公打电话 下班顺路买一斤包子带回来 如果看到卖西瓜的 就买一个 当晚 程序员老公手捧一个包子进了家门 老婆怒道 你怎么就买了一个包
  • Android BLE 快速开发示例

    目录 概述 1 FastBle的使用 2 BLE开发实践方面的理解 3 FastBle源码解析 概述 思来想去 还是写这篇博文 记录一下 当时学习BLE的一些心得 重捡回当前Android知识 想深入了解蓝牙通讯知识 这个案例是非常不错的选