GIthub传送带点这里
- minSdk 24
- targetSdk 33
基于Kotlin、协程
基于sdk 33,最新API
详细的完整的容错机制
基于多个蓝牙库的设计思想
强大的Notify\Indicate\Read\Write任务队列
demo体验
demo下载
详细用法参考demo
详细用法参考demo
详细用法参考demo
用法
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
dependencies {
implementation 'com.github.buhuiming:BleCore:latest version'
}
1、添加权限
//动态申请
val LOCATION_PERMISSION = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
)
} else {
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_ADVERTISE,
Manifest.permission.BLUETOOTH_CONNECT,
)
}
- 注意:
- 有些设备GPS是关闭状态的话,申请定位权限之后,GPS是依然关闭状态,这里要根据GPS是否打开来跳转页面
- BleUtil.isGpsOpen(context) 判断GPS是否打开
- 跳转到系统GPS设置页面,GPS设置是全局的独立的,是否打开跟权限申请无关
startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)) - 跳转到系统蓝牙设置页面
startActivity(Intent(Settings.ACTION_BLUETOOTH_SETTINGS))
1、初始化
val options =
BleOptions.builder()
.setScanServiceUuid("0000ff80-0000-1000-8000-00805f9b34fb", "0000ff90-0000-1000-8000-00805f9b34fb")
.setScanDeviceName("midea", "BYD BLE3")
.setScanDeviceAddress("70:86:CE:88:7A:AF", "5B:AE:65:88:59:5E", "B8:8C:29:8B:BE:07")
.isContainScanDeviceName(true)
.setAutoConnect(false)
.setEnableLog(true)
.setScanMillisTimeOut(12000)
//这个机制是:不会因为扫描的次数导致上一次扫描到的数据被清空,也就是onScanStart和onScanComplete
//都只会回调一次,而且扫描到的数据是所有扫描次数的总和
.setScanRetryCountAndInterval(2, 1000)
.setConnectMillisTimeOut(10000)
.setConnectRetryCountAndInterval(2, 5000)
.setOperateMillisTimeOut(6000)
.setOperateInterval(80)
.setMaxConnectNum(5)
.setMtu(500)
.setTaskQueueType(BleTaskQueueType.Operate)
.build()
BleManager.get().init(application, options)
//或者使用默认配置
BleManager.get().init(application)
setTaskQueueType方法,有3个选项分别是:
- BleTaskQueueType.Default 一个设备的Notify\Indicate\Read\Write\mtu操作所对应的任务共享同一个任务 队列(共享队列)(不区分特征值),rssi在rssi队列
- BleTaskQueueType.Operate 一个设备每个操作独立一个任务队列(不区分特征值) Notify在Notify队列中,Indicate在Indicate队列中,Read在Read队列中, Write在Write队列中,mtu在共享队列,rssi在rssi队列中, 不同操作任务之间相互不影响,相同操作任务之间先进先出按序执行 例如特征值1的写操作和特征值2的写操作,在同一个任务队列当中;特征值1的写操作和特征值1的读操作, 在两个不同的任务队列当中,特征值1的读操作和特征值2的写操作,在两个不同的任务队列当中。
- BleTaskQueueType.Independent 一个设备每个特征值下的每个操作独立一个任务队列(区分特征值) Notify\Indicate\Read\Write所对应的任务分别放入到独立的任务队列中, mtu在共享队列,rssi在rssi队列中, 且按特征值区分,不同操作任务之间相互不影响,相同操作任务之间相互不影响 例如特征值1的写操作和特征值2的写操作,在两个不同的任务队列当中;特征值1的写操作和特征值1的读操作, 在两个不同的任务队列当中,特征值1的读操作和特征值2的写操作,在两个不同的任务队列当中。
注意:BleTaskQueueType.Operate、BleTaskQueueType.Independent这两种模式下
- 1、在Notify\Indicate\Read\Write 未完成的情况下,不要执行设置Mtu,否则会导致前者操作失败
- 2、同时执行Notify\Indicate\Read\Write其中两个以上操作,会可能报设备忙碌失败
建议:以上模式主要也是针对操作之间的问题,强烈建议不要同时执行2个及以上操作,模式BleTaskQueueType.Default就是为 了让设备所有操作同一时间只执行一个,Rssi不受影响
2、扫描
注意:扫描之前先检查权限、检查GPS开关、检查蓝牙开关
扫描及过滤过程是在工作线程中进行,所以不会影响主线程的UI操作,最终每一个回调结果都会回到主线程。
开启扫描:
BleManager.get().startScan {
onScanStart {
}
onLeScan { bleDevice, currentScanCount ->
//可以根据currentScanCount是否已有清空列表数据
}
onLeScanDuplicateRemoval { bleDevice, currentScanCount ->
//与onLeScan区别之处在于:同一个设备只会出现一次
}
onScanComplete { bleDeviceList, bleDeviceDuplicateRemovalList ->
//扫描到的数据是所有扫描次数的总和
}
onScanFail {
val msg: String = when (it) {
is BleScanFailType.UnSupportBle -> "BleScanFailType.UnSupportBle: 设备不支持蓝牙"
is BleScanFailType.NoBlePermissionType -> "BleScanFailType.NoBlePermissionType: 权限不足,请检查"
is BleScanFailType.GPSDisable -> "BleScanFailType.BleDisable: 设备未打开GPS定位"
is BleScanFailType.BleDisable -> "BleScanFailType.BleDisable: 蓝牙未打开"
is BleScanFailType.AlReadyScanning -> "BleScanFailType.AlReadyScanning: 正在扫描"
is BleScanFailType.ScanError -> {
"BleScanFailType.ScanError: ${it.throwable?.message}"
}
}
BleLogger.e(msg)
Toast.makeText(application, msg, Toast.LENGTH_SHORT).show()
}
}
3、停止扫描
BleManager.get().stopScan()
4、是否扫描中
BleManager.get().isScanning()
5、连接
BleManager.get().connect(device)
BleManager.get().connect(deviceAddress)
- 在某些型号手机上,connectGatt必须在主线程才能有效,所以把连接过程放在主线程,回调也在主线程。
- 为保证重连成功率,建议断开后间隔一段时间之后进行重连。
6、断开连接
BleManager.get().disConnect(device)
BleManager.get().disConnect(deviceAddress)
- 断开后,并不会马上更新状态,所以马上连接会直接返回已连接,而且扫描不出来,要等待一定时间才可以
7、是否已连接
BleManager.get().isConnected(device)
8、扫描并连接,如果扫描到多个设备,则会连接第一个
BleManager.get().startScanAndConnect(bleScanCallback: BleScanCallback,
bleConnectCallback: BleConnectCallback)
- 扫描到首个符合扫描规则的设备后,便停止扫描,然后连接该设备。
9、获取设备的BluetoothGatt对象
BleManager.get().getBluetoothGatt(device)
10、设置Notify
BleManager.get().notify(bleDevice: BleDevice,
serviceUUID: String,
notifyUUID: String,
useCharacteristicDescriptor: Boolean = false,
bleIndicateCallback: BleIndicateCallback)
BleDescriptorGetType设计原则
- 正常情况下,每个特征值下至少有一个默认描述符,并且遵循蓝牙联盟定义的UUID规则,如 [com.bhm.ble.data.Constants.UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR]便是蓝牙联盟定义的 客户端特性配置的描述符UUID,这样做是方便BLE终端在接入不同类型设备时,能够获取到正确的配置。 比如有一个APP,需要 接入A商家的智能手表和B商家的智能手表来监听用户的心跳,而如果A商家的智能手表或者B商家的智能手表 不遵循蓝牙联盟定义关于 心跳相关的UUID,则对APP来说就要分别去获取A商家的智能手表或者B商家的智能手表对应特征值的描述 符UUID,显然是不合理的。当然这个是需要硬件设备支持的,也就是说硬件设备可以自定义UUID,但需要遵循规则。
- 在开发过程中,我们会遇到不同硬件设备定义UUID的情况,有的硬件设备通过特征值的UUID来获取描述符(用来writeDescriptor, 打开或关闭notify、indicate),而非是通过系统提供接受通知自带的UUID获取描述符。此外特征值有多个描述符时,获取其中 一个描述符来写入数据,可能会导致onCharacteristicChanged函数没有回调,我不确定是否是硬件设备需要支持修改的问题。 因此[AllDescriptor]方式则是简单粗暴的将特征值下所有的描述符都写入数据,以保证onCharacteristicChanged函数回调, 这个方法经过了一系列设备的验证可行,但不保证是完全有效的。
11、取消Notify
BleManager.get().stopNotify(bleDevice: BleDevice,
serviceUUID: String,
notifyUUID: String,
useCharacteristicDescriptor: Boolean = false)
12、设置Indicate
BleManager.get().indicate(bleDevice: BleDevice,
serviceUUID: String,
indicateUUID: String,
useCharacteristicDescriptor: Boolean = false,
bleIndicateCallback: BleIndicateCallback)
13、取消Indicate
BleManager.get().stopIndicate(bleDevice: BleDevice,
serviceUUID: String,
indicateUUID: String,
useCharacteristicDescriptor: Boolean = false)
14、读取信号值
BleManager.get().readRssi(bleDevice: BleDevice, bleRssiCallback: BleRssiCallback)
- 获取设备的信号强度,需要在设备连接之后进行。
- 某些设备可能无法读取Rssi,不会回调onRssiSuccess(),而会因为超时而回调onRssiFail()。
15、设置Mtu值
BleManager.get().setMtu(bleDevice: BleDevice, bleMtuChangedCallback: BleMtuChangedCallback)
- 设置MTU,需要在设备连接之后进行操作。
- 默认每一个BLE设备都必须支持的MTU为23。
- MTU为23,表示最多可以发送20个字节的数据。
- 该方法的参数mtu,最小设置为23,最大设置为512。
- 并不是每台设备都支持拓展MTU,需要通讯双方都支持才行,也就是说,需要设备硬件也支持拓展MTU该方法才会起效果。
调用该方法后,可以通过onMtuChanged(int mtu)查看最终设置完后,设备的最大传输单元被拓展到多少。如果设备不支持,
可能无论设置多少,最终的mtu还是23。 - 建议在indicate、notify、read、write未完成的情况下,不要执行设置Mtu,否则会导致前者操作失败
16、设置连接的优先级
BleManager.get().setConnectionPriority(connectionPriority: Int)
- 设置连接的优先级,一般用于高速传输大量数据的时候可以进行设置。
17、读特征值数据
BleManager.get().readData(bleDevice: BleDevice,
serviceUUID: String,
readUUID: String,
bleIndicateCallback: BleReadCallback)
18、写数据
BleManager.get().writeData(bleDevice: BleDevice,
serviceUUID: String,
writeUUID: String,
data: ByteArray,
bleWriteCallback: BleWriteCallback)
BleManager.get().writeData(bleDevice: BleDevice,
serviceUUID: String,
writeUUID: String,
data: SparseArray,
bleWriteCallback: BleWriteCallback)
因为分包后每一个包,可能是包含完整的协议,所以分包由业务层处理,组件只会根据包的长度和mtu值对比后是否拦截
特殊情况下:indicate\mtu\notify\read\rssi 这些操作,同一个特征值在不同地方调用(不同callback),最后面的操作
对应的回调才会触发,其他地方先前的操作对应的回调不会触发
解决方案:业务层每个特征值对应的操作维护一个单例的callback对象(假如为SingleCallback),在不同地方调用再传递callback
(放入到SingleCallback中的集合CallbackList),SingleCallback 回调时循环CallbackList中的callback,这样就达到了
同一个特征值在不同地方调用,都能收到回调indicate\mtu\notify\read\rssi这些操作 ,同一个特征值在不同地方调用,后面的操作会取消前面未完成的操作;write操作比较
特殊,每个写操作都会有回调,且write操作之间不会被取消。具体详情看taskId
19、断开某个设备的连接 释放资源
BleManager.get().close(bleDevice: BleDevice)
20、断开所有连接 释放资源
BleManager.get().closeAll()
21、一些移除监听的函数
BleManager.get().removeBleScanCallback()
BleManager.get().removeBleConnectCallback(bleDevice: BleDevice)
BleManager.get().removeBleIndicateCallback(bleDevice: BleDevice, indicateUUID: String)
BleManager.get().removeBleNotifyCallback(bleDevice: BleDevice, notifyUUID: String)
BleManager.get().removeBleRssiCallback(bleDevice: BleDevice)
BleManager.get().removeBleMtuChangedCallback(bleDevice: BleDevice)
BleManager.get().removeBleReadCallback(bleDevice: BleDevice, readUUID: String)
BleManager.get().removeBleWriteCallback(bleDevice: BleDevice, writeUUID: String)
存在问题
- 1、关闭系统蓝牙,没有触发onConnectionStateChange
解决方案:1、操作前判断蓝牙状态,2、蓝牙广播