Android藍牙低功耗(BLE)快速開發框架

GIthub傳送帶點這里

  • minSdk 24
  • targetSdk 33

基于Kotlin、協程

基于sdk 33,最新API

詳細的完整的容錯機制

基于多個藍牙庫的設計思想

強大的Notify\Indicate\Read\Write任務隊列

20230613110126.png
20230613110146.png
20230614090104.png

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、藍牙廣播
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容