這段時間在做低功耗藍牙(BLE)應用的開發(並不涉及藍牙協議棧)。總體感覺 Android BLE 還是不太穩定,開發起來也是各種痛苦。這裏記錄一些雜項和開發中遇到的問題及其解決方法,避免大家踩坑。本文說的問題有些沒有得到官方文檔的驗證,不過也有一些論壇帖子的支持,也可以算是有一定根據。
Android 從 4.3(API Level 18) 開始支持低功耗藍牙,但是隻支持作爲中心設備(Central)模式,這就意味着 Android 設備只能主動掃描和鏈接其他外圍設備(Peripheral)。從 Android 5.0(API Level 21) 開始兩種模式都支持。BLE 官方文檔在 這裏。
在
BluetoothAdapter.startLeScan()
的時候,在BluetoothAdapter.LeScanCallback.onLeScan()
中不能做太多事情,特別是周圍的BLE設備多的時候,非常容易導致出現如下錯誤:
E/GKILINUX(17741): ##### ERROR : GKIexception: GKIexception(): Task State Table E/GKILINUX(17741): #####
E/GKILINUX(17741): ##### ERROR : GKIexception: TASK ID [0] task name [BTU] state [1]
E/GKILINUX(17741): #####
E/GKILINUX(17741): ##### ERROR : GKIexception: TASK ID [1] task name [BTIF] state [1]
E/GKILINUX(17741): #####
E/GKILINUX(17741): ##### ERROR : GKIexception: TASK ID [2] task name [A2DP-MEDIA] state [1]
E/GKILINUX(17741): #####
E/GKILINUX(17741): ##### ERROR : GKIexception: GKIexception 65524 getbuf: out of buffers#####
E/GKILINUX(17741): ##### ERROR : GKIexception:
E/GKI_LINUX(17741): **********************
開發建議:在 onLeScan()
回調中只做儘量少的工作,可以把掃描到的設備,扔到另外一個線程中去處理,讓 onLeScan()
儘快返回
3、在使用 BluetoothDevice.connectGatt()
或者 BluetoothGatt.connect()
等建立 BluetoothGatt
連接的時候,在任何時刻都只能最多一個設備在嘗試建立連接。如果同時對多個藍牙設備發起建立 Gatt 連接請求。如果前面的設備連接失敗了,後面的設備請求會被永遠阻塞住,不會有任何連接回調。
開發建議:如果要對多個設備發起連接請求,最好是有一個同一個的設備連接管理,把發起連接請求序列化起來。前一個設備請求建立連接,後面請求在隊列中等待。如果連接成功了,就處理下一個連接請求。如果連接失敗了(例如出錯,或者連接超時失敗),就馬上調用 BluetoothGatt.disconnect()
來釋放建立連接請求,然後處理下一個設備連接請求。
4、對 BluetoothGatt 操作 (read/write)Characteristic()
, (read/write)Descriptor()
和 readRemoteRssi()
都是異步操作。需要特別注意的是,同時只能有一個操作(有些貼這說只能同時有一個 writeCharacteristic()
,這個我並沒有嚴格驗證),也就是等上一個操作回調(例如 onCharacteristicWrite()
)以後,再進行下一個操作。
開發建議:把這寫操作都封裝成同步操作,一個操作回調之前,阻塞主其他調用。
5、BLE 設備的建立和斷開連接的操作,例如 BluetoothDevice.connectGatt()
,BluetoothGatt.connect()
, BluetoothGatt.disconnect()
,BluetoothGatt.discoverServices()
等操作最好都放在主線程中,否則你會遇到很多意想不到的麻煩。
開發建議:對 BluetoothGatt
的連接和斷開請求,都通過發送消息到 Android 的主線程中,讓主線程來執行具體的操作。例如創建一個 new Handler(context.getMainLooper());
,把消息發送到這個 Handler
中
6、如果你在開發 BLE 應用的時候,有時候會發現系統的功耗明顯增加了,查看電量使用情況,藍牙功耗佔比非常高,好像低功耗是徒有虛名。使用 adb bugreport
獲取的了系統信息,分析發現一個名叫 BluetoothRemoteDevices
的 WakeLock
鎖持有時間非常長,導致系統進入不了休眠。分析源代碼發現,在連接 BLE 設備的過程中,系統會持有 (Aquire)這個 WakeLock
,直到連接上或者主動斷開連接(調用 disconnect()
)纔會釋放。如果BLE設備不在範圍內,這個超時時間大約爲30s,而這時你可能又要嘗試重新連接,這個 WakeLock
有被重新持有,這樣系統就永遠不能休眠了。
開發建議:對BLE設備連接,連接過程要儘量短,如果連接不上,不要盲目進行重連,否這你的電池會很快被消耗掉。這個情況,實際上對傳統藍牙設備連接也是一樣。
7、Android 作爲中心設備,最多隻能同時連接 6 個 BLE 外圍設備(可能不同的設備這個數字不一樣),超過 6 個,就會連接不上了。現在 BLE 設備越來越多,其實並不夠用,所以在開發的過程中,需要特別的謹慎使用。
開發建議:按照需要連接設備,如果設備使用完了,應該馬上釋放連接(調用BluetoothGatt.close()
),騰出系統資源給其他可能的設備連接。
8、發起藍牙Gatt連接 BluetoothDevice.connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback)
,這裏有一個參數autoConnect,如果爲 true
的話,系統就會發起一個後臺連接,等到系統發現了一個設備,就會自動連上,通常這個過程是非常慢的。爲 false
的話,就會直接連接,通常會比較快。同樣,BluetoothGatt.connect()
只能發起一個後臺連接,不是直接連接。所以這個地方需要小心。
public boolean connect() { try { mService.clientConnect(mClientIf, mDevice.getAddress(), false, mTransport); // autoConnect is inverse of "isDirect" return true; } catch (RemoteException e) { Log.e(TAG,"",e); return false; } }
開發建議:如果你需要快速連接(通常情況下我們都希望這樣),在 connectGatt()
的時候,傳入 autoConnect=false
的參數。如果需要調用 BluetoothGatt.connect()
連接,可一通過反射的方式,強制 mService.clientConnect()
發起直接連接,也就是傳入參數 isDirect=true。