Android BLE 蓝牙操作设备三方库 NordicSemiconductor/Android-BLE-Library

更新日期: 2023-08-19 阅读次数: 2018 字数: 1246 分类: Android

为何要使用三方库

起因是我写的 Android 低功耗蓝牙 App 有几个现存的问题:

  • 写操作的并发问题导致写入失败,需要一个队列。iOS 内置了这个实现,甚至连微信小程序都实现了,但是 Android 官方没有实现,渣渣。
  • 信号不稳定,蓝牙断开连接,需自动重连
  • 未来需要连接多个蓝牙设备,但是我目前的架构不支持
  • 如果设备信息进一步增多,就要涉及到数据包的拆分,组合

我去自己实现,非常耗费时间,在我看来都是非常基础的功能。不如找个三方稳定的实现。

NordicSemiconductor/Android-BLE-Library 介绍

https://github.com/NordicSemiconductor/Android-BLE-Library

目前在 Github 上有 1.7K 的 star,虽然不及那个 RxJava 的库,但是看起来这个更容易上手。 关键是,我需要的功能,这个库基本都支持了。只是设备扫描功能不支持,但是不影响我的使用,因为我也不想重构设备扫描部分。

这个库的所属公司比较有意思,查了一下,在行业内很知名,但是搞硬件的同事居然都没听说过。。。

Nordic Semiconductor(诺迪克半导体)是一家总部位于挪威的全球领先的无线通信解决方案供应商。该公司成立于1983年,专注于设计和制造低功耗无线系统芯片,为物联网(IoT)和无线连接应用提供解决方案。微信里能搜索到这个公司的深圳分公司。

Nordic Semiconductor 的产品主要集中在无线连接领域,其中最著名的是其低功耗蓝牙(Bluetooth Low Energy)技术。公司的蓝牙芯片系列具有出色的功耗效率和性能,广泛应用于智能家居、可穿戴设备、健康监测、工业自动化、智能城市和物流等领域。

在全球 BLE 市场中,Nordic 大约占 40% 市场份额,处于领头羊地位。

安装

支持 kotlin 扩展, 及 LiveData:

implementation 'no.nordicsemi.android:ble-ktx:2.6.1'
implementation 'no.nordicsemi.android:ble-livedata:2.6.1'

This extension adds ObservableBleManager with state and bondingState properties, which notify about connection and bond state using androidx.lifecycle.LiveData.

如果能用 LiveData 监听状态变化,那可太方便了。

如果是项目重构,还是新建一个 git 分支比较方便。

Kotlin 的相关代码示例

这个项目的 examples 下有个 ble-gatt-client 的项目,可以

https://github.com/NordicSemiconductor/Android-BLE-Library/blob/main/examples/ble-gatt-client/src/main/java/no/nordicsemi/android/ble/ble_gatt_client/GattService.kt

建立连接

val clientManager = ClientManager()
clientManager.connect(device).useAutoConnect(true).enqueue()

这里的 ClientManager 是 BleManager 的实现。

private inner class ClientManager : BleManager(this@GattService) {

需要实现三个方法:

  • isRequiredServiceSupported(BluetoothGatt gatt):实际就是判断指定 uuid 的 service 及 特性是否存在
  • onServicesInvalidated():断开连接时,将相关特性置空即可
  • initialize():做一下初始化操作,例如,设置 mtu,山寨密码验证等。

实战改进:

  • clientManager 更名为 deviceManager 更合理一点。对我的项目而言,都是一堆外部设备
  • 扩展的类 ClientManager 应该更具体一些,因为如果要连接多个不同类型的设备时,会涉及到不同的Server / 特性 UUID 需要配置,所以应该称之为 SomeDeviceManager

发现服务

在 isRequiredServiceSupported 中获取需要的 server 及 characteristic 即可。

修改 MTU

以 java 代码为例:(感觉 Kotlin 的可读性还不如 Java)

class MyBleManager extends BleManager {
	...

    @Override
    protected void initialize() {
        // Initialize your device.
        // This means e.g. enabling notifications, setting notification callbacks, or writing
        // something to a Control Point characteristic.
        // Kotlin projects should not use suspend methods here, as this method does not suspend.
        requestMtu(517)
            .enqueue();
    }

	...
}

Kotlin 版:

在 initialize 函数中:

requestMtu(200).with { _, data ->
	println("mtu changed to $data")
}.done {
	println("mtu done")
}.enqueue()

监听 indication / notification

实际是分两步,一是 enable notification, 二是收到 notification 时的回调函数,以处理接收到的数据。

写入

writeCharacteristic(
	characteristicToWrite,
	"010203".decodeHex().crc16(),
	BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
).enqueue()

监听连接状态变化

这个还是很有必要的,用于显示界面上的连接状态。

自动重连

在测试这个 lib 是否好用时:

  1. 测试密码验证过程后,蓝牙指示灯是否正常
  2. 修改为错误的密码后,蓝牙指示灯是否闪烁。果然又开始闪烁了。

但是发现一个奇妙的现象,似乎输入错误的密码之后,蓝牙设备主动断开连接后, 这个三方库在自动尝试重连,非常好。

从日志看 initialize() 函数,在每次尝试重连时,都会被调用。

多蓝牙设备连接

我们知道 Android 系统最多支持同时连接 7 个 BLE 蓝牙设备。

A BleManager instance is responsible for connecting and communicating with a single Bluetooth LE peripheral. By having multiple instances of the manager it is possible to connect to multiple devices simultaneously.

实际上多建立几个 BleManager 即可以实际。

参考:

https://github.com/NordicSemiconductor/Android-BLE-Library/blob/main/USAGE.md

例如:

示例代码中 ble_gatt_client/GattService.kt 中 Service 定义了一个 clientManagers 属性,

private val clientManagers = mutableMapOf<String, ClientManager>()

key 为蓝牙设备 Mac 地址。

新增设备连接:

private fun addDevice(device: BluetoothDevice) {
	if (!clientManagers.containsKey(device.address)) {
		val clientManager = ClientManager()
		clientManager.connect(device).useAutoConnect(true).enqueue()
		clientManagers[device.address] = clientManager
	}
}

如果是多种不同类型的设备,实际上也没必要这样写,每个定义一个变量就行。

开机启动

USAGE.md 中有个开机启动服务的示例,很好,非常适合我的平板使用场景

class OsNotificationReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        when (intent?.action) {
            // Start our Gatt service as a result of the system booting up
            Intent.ACTION_BOOT_COMPLETED -> {
               context?.startForegroundService(Intent(context, GattService::class.java))
            }
        }
    }
}

关于作者 🌱

我是来自山东烟台的一名开发者,有敢兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊, 查看更多联系方式