GIthub傳送帶點這里
- minSdk 24
- targetSdk 33
基于Kotlin、協程
基于sdk 33,最新API
詳細的完整的容錯機制
基于多個藍牙庫的設計思想
強大的Notify\Indicate\Read\Write任務隊列
20230613110126.png
20230613110146.png
20230614090104.png
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、藍牙廣播