为何要使用三方库
起因是我写的 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 是否好用时:
- 测试密码验证过程后,蓝牙指示灯是否正常
- 修改为错误的密码后,蓝牙指示灯是否闪烁。果然又开始闪烁了。
但是发现一个奇妙的现象,似乎输入错误的密码之后,蓝牙设备主动断开连接后, 这个三方库在自动尝试重连,非常好。
从日志看 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 聊聊, 查看更多联系方式