Android BLE 幽靈連線

最近開發 Android APP 內連接 BLE 設備時,經過一些測試才發現一個問題:當機器端強制斷線,在 App 中重新連上 BLE 之後,接下來設備傳過來的封包都會收到雙份。如果再斷一次重連,甚至會收到三份…

忽然想到,前陣子才在 facebook 看到一篇類似的討論,其中有個回答滿有趣:

所以可能是之前斷開的連結,又偷偷連上,偷偷收資料了對吧 😡

於是,找到 StackOverflow 上這篇 Android BLE (Bluetooth Low Energy) Connect/Disconnect/Reconnect的回答,建議用這樣的邏輯:

1
BluetoothDevice Device = stuff();
2
BluetoothGatt Gatt = null;
3
4
if (connecting)
5
   Gatt = Device.ConnectGatt(...);
6
else if (disconnecting temporarily)
7
   Gatt.Disconnect();
8
else if (reconnecting after a temporary disconnection)
9
   Gatt.Connect();
10
else if (disconnecting permanently)
11
{
12
   Gatt.Disconnect();
13
   Gatt.Close();
14
   Gatt = null;
15
}

參考以上的邏輯和一些實驗,有以下結論:

  1. device.ConnectGatt() 會產生新的 gatt object。
  2. 只有 gatt.close() 才能完整把連線關掉 (gatt.disconnect() 不行)。

於是我們決定在 BluetoothGattCallback 實作中,在 OnConnectionStateChanged() 裡只要發現 state 變為 STATE_DISCONNECTED 就 call gatt.close()。因為不管是 App 主動斷線,或是 BLE 設備端強制斷線,都會觸發 OnConnectionStateChanged(),所以這樣可以確保任何斷線都關閉 gatt。之後如果想重連,再等重新 scan 後,去連上新的 device。

1
class Gatt : BluetoothGattCallback() {
2
3
    override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
4
        when (newState) {
5
            BluetoothProfile.STATE_CONNECTED -> {
6
                // ...
7
                gatt?.discoverServices()
8
            }
9
            BluetoothProfile.STATE_CONNECTING -> {
10
                // ...
11
            }
12
            BluetoothProfile.STATE_DISCONNECTED -> {
13
                // ...
14
                gatt?.close()
15
            }
16
        }
17
    }
18
}

順利解決!