基本概念和問題
1、藍牙設計范式?
當手機通過掃描低功耗藍牙設備并連接上后,手機與藍牙設備構成了客戶端-服務端架構。手機通過連接藍牙設備,可以讀取藍牙設備上的信息。手機就是客戶端,藍牙設備是服務端。
手機做為客戶端可以連接多個藍牙設備,所以手機又可以叫中心設備(Central),藍牙設備叫外圍設備(Peripheral)。
還有另外一個稱謂:手機叫主設備(Master),藍牙設備叫從設備(Slave)。
Android4.3 開始支持低功耗藍牙,此版本只支持單模式:同時只能工作在中心設備模式或者外圍設備模式
Android5.0 開始支持主從一體。換句話說,手機可以掃描并進行連接,連接著藍牙設備的同時,又可以作為廣播者,發(fā)送藍牙廣播,等待別的支持藍牙掃描的設備連接自己。
2、從設備連接數(shù)量的問題?
理論層面
從經(jīng)典藍牙時代開始,藍牙有個星型拓撲的概念,一個主設備(Central)外圍有七個從設備(Peripheral),藍牙核心文檔規(guī)定了:同一時間只允許七個從設備進行連接。
系統(tǒng)層面
Android系統(tǒng)藍牙協(xié)議棧源碼中也使用了這個數(shù)值,Android手機的藍牙芯片都是雙模藍牙芯片,即同時支持經(jīng)典藍牙和低功耗藍牙,分析過協(xié)議棧源碼,建立連接的過程經(jīng)典藍牙和低功耗藍牙是公用的代碼,所以手機作為主設備(Central)時,從設備(Peripheral)同時連接的最大值就是7臺設備。
實際情況
開發(fā)Android客戶端以來,遇到的實際情況就是,部分手機(偏低端一些機型,比如采用聯(lián)發(fā)科的解決方案,手機的GPS、藍牙、Wi-Fi等都是共模的,都集成在一個芯片上)不能達到7臺設備。
3、ATT是什么?
ATT是屬性協(xié)議(Attribute Protocol),定義了客戶端與服務器如何相互發(fā)送符合標準的消息。
4、GATT是什么?
GATT是通用屬性規(guī)范(Generic Attribute Profile),定義了如何發(fā)現(xiàn)與使用服務、特性與描述符的標準方法。
GATT的規(guī)程基本分為:
發(fā)現(xiàn)規(guī)程:發(fā)現(xiàn)服務(Service)、發(fā)現(xiàn)特征(Characteristic)等
客戶端發(fā)起規(guī)程:讀取特征(readCharacteristic)、寫入特征(writeCharacteristic)等
服務端發(fā)起規(guī)程:比如通知(Notification)和指示(Indicate)
5、低功耗藍牙頻段和信道問題
藍牙工作在2.45G ISM頻段,波段范圍是:2400-2483.5 MHz
信道:低功耗藍牙使用用40個RF信道,這些RF信道中心頻率為:f=2402+k*2 MHz, k=0, ... ,39
因為調(diào)試指數(shù)放寬,低功耗藍牙的信道與經(jīng)典藍牙有所不同。每個信道的功率譜更寬,因此,為了避免鄰近信道干擾,低功耗藍牙的信道寬度為2MHz,而不是經(jīng)典藍牙的1MHz
低功耗藍牙使用的2.45GHz頻段已經(jīng)非常擁擠,僅僅考慮標準的技術就包括:經(jīng)典藍牙、低功耗藍牙、IEEE 802.11、IEEE802.11b、IEEE802.11g、IEEE802.11n以及IEEE 802.15.4。另外,許多私有的無線電同樣使用這個頻段,包括X10視頻中繼器、無線報警、鍵盤和鼠標等。許多其他設備也會在該頻段發(fā)射噪聲,例如街燈和微波爐。
對于2.45G這個頻段有個很尷尬的特性:怕水。
舉個例子:微波爐的工作原理就是向帶有水分的物體發(fā)射2.45GHz的微波,利用了水分子能夠很好的吸收2.45GHz電磁波,將電磁波能量轉換成為自身的熱量。也正式這個特性,在很長一段時間里,2.4GHz信道不被人所重視,下雨、霧氣甚至是潮濕的墻壁都能吸收無線電波,使傳輸距離大大衰減。估計這也是全球都對此頻段不屑而免費開放的理由之一吧。當人站在兩塊藍牙設備中間,并且距離其中一塊模塊1米左右時,能夠檢測信號衰減了將近10dB左右!因為人體的70%左右是水分。

有人也許好奇為啥廣播信道這么設計,請看下圖:

是為了盡量避開沖突頻段,增加通信的魯棒性。
6、關于autoConnect參數(shù)為true的意義?
在藍牙核心文檔Vol3: Core System Package[Host volume]->Part C: Generic Access Profile的Connection Modes and Procedures章節(jié)中有涉及到自動連接建立規(guī)程(Auto Connection Establishment Procedure)的定義。
自動連接建立規(guī)程用來向多個設備同時發(fā)起連接。一個中央設備的主機與多個外圍設備綁定,只要它們開始廣播,便立刻與其建立連接。跟多細節(jié)請參考藍牙核心文檔和協(xié)議棧源碼。
一些API使用問題
Android 4.3
此版本是首個支持BLE的Android版本,穩(wěn)定性一般,現(xiàn)在的系統(tǒng)分布情況,基本可以把最低支持版本提高的Android4.4了
Android 5.0
Samsung手機出現(xiàn)BluetoothAdapter.startLeScan()方法使用不當導致的Crash
正常調(diào)用過程startLeScan() -> stopLeScan() -> startLeScan() -> stopLeScan(),不會出現(xiàn)Crash
異常調(diào)用startLeScan() -> startLeScan()會出現(xiàn)Crash
Android 6.0
在Android 6.0版本,需要APP獲取位置權限才可以使用藍牙API,部分機型在未授權時,調(diào)用藍牙API會引起Crash
Android 6.0.1
Android6.0.1有個連接問題,是系統(tǒng)bug,影響連接問題。
Android 7.0
30s內(nèi)連續(xù)掃描次數(shù)不允許大于5次,否則會引起無法掃描到設備的問題,需要重啟才可以恢復正常。
并發(fā)執(zhí)行BluetoothGatt.readRemoterssi()會引發(fā)DeadObjectException,三星手機出現(xiàn)概率較高。
Android 8.1?
掃描方法BluetoothAdapter.startLeScan(UUID[] serviceUuids, BluetoothAdapter.LeScanCallback callback);
部分手機如果沒有指定serviceuuids值,手機鎖屏后,掃描回調(diào)會失敗,無法掃描到設備。Google親兒子Pixel系列必現(xiàn)。
待更新......
另外一些普遍問題
BluetoothDevice.getName() 獲取名字是有些不可靠的,因為有些情況下獲取Name是空,慎用此方法獲取的值來作為掃描過濾條件。
BLE設備的建立和斷開連接的操作,最好都放在主線程中:例如BluetoothDevice.connectGatt(),BluetoothGatt.connect(),BluetoothGatt.disconnect(),BluetoothGatt.discoverServices()等
BLE 應用異常耗電問題,在連接 BLE 設備的過程中,系統(tǒng)會持有這個?WakeLock,直到連接上或者主動斷開連接才會釋放。如果BLE設備不在范圍內(nèi),這個超時時間大約為30s,而這時你可能又要嘗試重新連接,這個WakeLock又被重新持有,這樣系統(tǒng)就永遠不能休眠了。
Android BLE藍牙的各種問題,只要做到如下幾點,大部分問題會得到解決:
原則一:startLeScan()和stopLeScan()一定要確保成對出現(xiàn)、順序調(diào)用。
否則會導致協(xié)議棧中mClientIf達到上限,掃描registerClient失敗,再也不能掃描到設備,此時onScanFailed會發(fā)生errorCode=2。
原則二:BluetoothDevice.connectGatt()、BluetoothGatt.disconnect()和BluetoothGatt.close()一定要順序調(diào)用。一些情況下可以直接越過disconnect()方法直接調(diào)用close()。
close()非常重要,對于一個執(zhí)行過連接方法的設備,不管是否連接成功,最后都要調(diào)用close(),讓系統(tǒng)底層回收掉資源,否則會有各種問題讓你崩潰。
對于連接成功的藍牙設備,想斷開時,可以先調(diào)用BluetoothGatt.disconnect(),等待onConnectionStateChange響應斷開后,再執(zhí)行close()。
如果是執(zhí)行連接方法時出現(xiàn)了無法恢復的錯誤,比如133、8、19、22、62等,可以直接調(diào)用close()。
原則三:讀/寫特征和描述、設置通知和指示等操作,要確保上一個執(zhí)行完成了,再執(zhí)行下一個調(diào)用。
Android源碼中使用了mDeviceBusy全局變量,同時調(diào)用兩個API,會導致后調(diào)用的直接失敗。
原則四:由于藍牙指令執(zhí)行出現(xiàn)異常的概率還是比較高的,所以保證每次藍牙操作的互斥性很必要、還有就是執(zhí)行異常發(fā)生時,需要進行必要的重試操作,經(jīng)過幾個月對上線版本的監(jiān)測,適當?shù)闹卦噷档蛨?zhí)行出錯率改善明顯。
附錄:API常見錯誤碼
GATT_ERROR? ? 0x85? ? //133任何不懼名字的錯誤都出現(xiàn)這個錯誤碼,出現(xiàn)了就認慫吧,重新連接吧。
GATT_CONN_TIMEOUT? ? 0x08? ? //8? 連接超時,大多數(shù)情況是設備離開可連接范圍,然后手機端連接超時斷開返回此錯誤碼。
GATT_CONN_TERMINATE_PEER_USER?????0x13? ? //19? 連接被對端設備終止,直白點就是手機去連接外圍設備,外圍設備任性不讓連接執(zhí)行了斷開。
GATT_CONN_TERMINATE_LOCAL_HOST? ? 0x16? ? //22? 連接被本地主機終止,可以解釋為手機連接外圍設備,但是連接過程中出現(xiàn)一些比如鑒權等問題,無法繼續(xù)保持連接,主動執(zhí)行了斷開操作。
GATT_CONN_FAIL_ESTABLISH? ? ? 03E? ? //62? 連接建立失敗。
避坑指南
基于Android藍牙接口封裝了一個庫,將一些會引發(fā)錯誤的地方規(guī)避掉了,以后再開發(fā)相關應用,可以省一些心力,少掉一些頭發(fā)??????。
項目特性:
1、封裝了藍牙掃描API,設置掃描模式非常方便,支持省電模式、前后臺掃描策略、掃描過濾等。還是支持周期掃描模式。
2、封裝了Notification、Read、Write、ReadRssi等常用操作,支持立即執(zhí)行和并發(fā)&緩存兩類API。立即執(zhí)行類API支持執(zhí)行超時設置。并發(fā)&緩存類API,會判斷當前是否有正在執(zhí)行的藍牙操作,自動緩存延遲執(zhí)行藍牙指令,有效降低了多設備連接時,指令執(zhí)行異常發(fā)生的概率。
3、支持連接異常重試操作,有效降低連接異常(比如GATT_ERROR等)導致的連接失敗概率。