Android BLE設備藍牙通信框架BluetoothKit

BluetoothKit是一款功能強大的Android藍牙通信框架,支持低功耗藍牙設備的連接通信、藍牙廣播掃描及Beacon解析。

關於該項目的詳細文檔請關注:https://github.com/dingjikerbo/BluetoothKit

這套框架存在的意義

一、統一解決Android藍牙通信過程中的兼容性問題
二、提供儘可能簡單易用的接口,屏蔽藍牙通信中的技術細節,只開放連接,讀寫,通知等語義。
三、實現串行化任務隊列,統一處理藍牙通信中的失敗以及超時,支持可配置的容錯處理
四、統一管理連接句柄,避免句柄泄露
五、方便監控各設備連接狀態,比如手機可同時連接的設備數有限,可以檢測設備連接活躍度,在儘可能維持連接的情況下,將最不活躍的設備自動斷開。
六、支持多進程APP架構下藍牙連接的管理
七、支持攔截所有對藍牙原生接口的調用

對於剛接觸Android藍牙開發的初學者來說,會經常遇到一些奇怪的坑,我也是一路走過來的,將我遇到的一些坑總結了一下,這些坑在這個項目中都修復了,所以大家不必再費時費力去重複踩一遍。這個項目目前正在不斷更新,有什麼更好的建議大家可以隨時提出來。

藍牙掃描相關的坑

一、startDiscovery在大多數手機上是可以同時發現經典藍牙和Ble的,但是startDiscovery的回調無法返回Ble的廣播,所以無法通過廣播識別設備,且startDiscovery掃描Ble的效率比StartLeScan低很多。所以在實際應用中,還是StartDiscovery和StartLeScan分開掃,前者掃經典藍牙,後者掃低功耗藍牙。

二、startLeScan() 的時候,在onLeScan() 中不能做耗時操作,特別是周圍的BLE設備多的時候,容易導致底層堵塞。如果有耗時操作請丟到子線程中去處理。

三、有的手機會過濾設備廣播,一次掃描過程中只發一次request,如果沒有收到response就再也收不到該設備廣播回調了,解決的辦法是多掃幾次。

藍牙連接相關的坑

一、對藍牙設備的操作不能並行,只能串行,即每次都要在收到上一個操作的回調後才能繼續下一個操作。但是斷開連接例外,斷開連接要馬上closeGatt,不用等任務隊列中的其他操作了。而且要給所有正在執行或者準備執行的任務都cancel。

二、有時候藍牙協議棧出現異常可能收不到回調,所以我們要對每個操作做超時檢查,否則後面的所有操作都被阻塞了。

三、對於超時的任務,最好closeGatt,下次重新連接的時候重開一個gatt。

四、藍牙連接可能不穩定,最好支持失敗自動重試機制,尤其是連接和發現服務,因爲80%的問題都發生在建立連接和發現服務的時候,而且這一塊也是最耗時的。

五、當連接建立後,可以由設備端發起更改連接間隔,這樣能加快後續發現服務以及數據讀寫的速度。有的手機discover service很慢,原因是connect interval太大了,有的手機會主動向設備發起更改connect interval,而有的手機卻不會。這樣的話connect interval相差就會很大,實踐中發現有的手機是7ms,有的手機是默認的50ms,所以發現service都要8s,甚至20s的都很尋常,這對用戶來說是無法忍受的。所以比較好的辦法是設備主動發起更改connect interval

六、同一個設備的所有操作最好都放在同一個線程串行執行,最好不要放在UI線程,雖然這些操作都是異步的,理論上來說不會耗時,但是由於涉及到跨進程,還是有可能出現ANR。另外不建議每個設備都開一個線程,設備多了會費內存也會降低性能。較好的做法是開一個線程,所有設備的操作都在該線程中發起,雖然佔用同一個線程,但是每個設備各自維護自己的任務隊列。

七、藍牙操作都是異步的,回調通常都在binder線程裏執行,因爲這是跨進程回調回來的。一定要注意到這一點,否則會出現一些奇怪的問題。比如writeCharacter在工作線程,但是onCharacterWrite是在binder線程,回調裏如果涉及到任務隊列的調度一定要post回工作線程中處理,否則會出現多線程造成的數據不一致問題。

八、接着第七條,在回調中post回工作線程處理時要注意不要帶句柄,而是要帶數據。比如對於onCharacteristicWrite,不要帶BluetoothGattCharacteristic,而是要帶其中的value。否則會漏掉一些數據,且最後可能收到一組重複的數據。同樣的問題在notify上也有。

九、當設備固件升級後,profile可能發生了變化,然而下次discover service的時候還是返回的舊的緩存,這樣讀寫character可能會失敗。解決辦法是固件升級後,下次連接時刷新一下該設備的緩存。當然,重啓藍牙也會刷新緩存,不過會影響到所有設備。另外有時候discoverService服務發現的不全,或者根本發現不了服務,也可以考慮清除一下緩存。

十、有的手機上discoverService可能會回調不止一次onServiceDiscover,這個要注意防禦。

十一、service不要緩存,雖然uuid什麼的可能都沒變,但是這些service都會和gatt關聯的,如果gatt變了,那service就報廢了,對這些service和character做任何讀寫操作都會出錯。所以建議每次連接上時都去discover service,不要緩存。

十二、固件升級通常是寫設備,爲加快寫速度,可以在write character時指定no response標誌,實踐發現速度可以提升2~3倍。不過要注意的是即便帶了no response標誌,也不代表這種寫操作是沒有回調的,我們仍然要遵循收到上一次寫回調後才能進行下一次寫操作。提升寫速度的手段還有更改MTU和更改連接間隔,不過更改MTU硬件不一定支持,得嘗試幾次。

十三、寫的時候不要指定writeType,不指定writeType不代表writeType就是WRITE_TYPE_DEFAULT,事實上系統會自動根據property來決定writeType,如果帶PROPERTY_WRITE_NO_RESPONSE屬性,則會自動選擇WRITE_TYPE_NO_RESPONSE,否則纔會選擇WRITE_TYPE_DEFAULT。

十四、打開/關閉character的notify,必須等收到onDescriptorWrite回調之後纔算結束,才能開始下一個任務。

十五、設備的gatt在不用時要及時關閉,系統支持的連接句柄數是有限的,當達到上限後無法再建立新的連接了。

十六、當連接斷開後要調closeGatt釋放資源,不用調disconnect,也不要下次複用之前的gatt來reconnect,因爲有的手機上重連可能會存在問題,比如重連後死活發現不了service。這種情況下,最好只要斷開連接就close gatt,下次連接時打開全新的gatt,這樣就可以發現service了。


使用

使用起來非常簡單,添加一行依賴,然後創建一個全局單例BluetoothClient,詳細接口調用方法參考上面的鏈接。

1、在build.gradle中添加依賴

compile 'com.inuker.bluetooth:library:1.3.8'

2、創建一個BluetoothClient,最好做成單例全局使用

BluetoothClient mClient = new BluetoothClient(context);


使用到的一些技術

**一、實現了一個
完整的跨進程異步任務隊列,支持任務超時、出錯重試及防禦隊列溢出**

二、攔截並Hook系統層藍牙Binder,實現對所有藍牙設備通信的監控,當同時連接設備數過多時會自動斷掉活躍度最低的設備

三、整個框架封裝在一個service中,可靈活指定service所在進程。

四、屏蔽了接口異步回調可能持有調用端Activity引用導致的內存泄露

五、利用動態代理自動將所有操作封閉在工作線程,所以整個框架無鎖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章