簡體   English   中英

關於在android上創建自動連接BLE設備的服務的問題

[英]Questions on creating a service that connects automatically to a BLE device on android

我正在實現一項服務,該服務使用autoconnectbluetoothGatt連接功能連接到設備並在連接時對其進行監控。

我假設設備已經綁定(同事負責該部分),因此自動連接應該沒有任何問題

我的代碼如下:

//the callback is for the class I have created that actually does the connection
class BTService: Service(), CoroutineScope, BTConnection.Callback {
    private val btReceiver by lazy { BluetoothStateReceiver(this::btStateChange) } //receiver for bt adapter changes

    private var connection:BTConnection? = null
    private var readJob:Job? = null

    override fun onCreate() {
        buildNotificationChannels()
        registerReceiver(btReceiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)) //since I can't register this receiver in AndroidManifest any more I did it here
    }

    private fun btStateChange(enabled: Boolean) {
        if (enabled)
            startConnecting()
        else
            stopConnection()
    }

    private fun startConnecting() {
        
        val address = prefs.address //get the current saved address
        val current = connection //get the current connection

        //try to stop the current connection if it is different than the one we want to set up
        if (current != null && !current.address.equals(address, true))
            current.stop()

        if (address.isNullOrBlank())
            return
        //then we create a new connection if needed
        val new = if (current == null || !current.address.equals(address, true)) {
            Injections.buildConnection(application, address, this)
        } else {
            current
        }
        connection = new
        new.connect()
    }

    //this is one of the callbacks from BTConnection.Callback
    override fun connected(address: String) {
        if (address != connection?.address) return
        val cn = connection ?: return
        showConnectionNotification()
        val notification = buildForegroundNotification()
        startForeground(FOREGROUND_ID, notification)
        readJob?.cancel()
        readJob = launch {
             cn.dataFlow //this is a flow that will be emmitting read data
             .cancellable() 
             .flowOn(Dispatchers.IO)
             .buffer()
             .onEach(this@BTService::parseData)
             .flowOn(Dispatchers.Default)
        }
    }


    private suspend fun parseData(bytes:ByteArray) { //this is where the parsing and storage etc happens
}

private fun stopConnection() {
    val cn = connection
    connection = null
    cn?.stop()
}
 
override fun disconnected(address: String) { //another callback from the connection class
    showDisconnectNotification()
    stopForeground(true)
}

我停止連接的代碼是

fun stop() {
    canceled = true
    if (connected)
        gatt?.disconnect()
    launch(Dispatchers.IO) {
        delay(1000)
        gatt?.close()
        gatt = null
    }
}

我的代碼基於(並受到影響)我讀過的這篇非常好的文章:

https://medium.com/@martijn.van.welie/making-android-ble-work-part-2-47a3cdaade07

我還為啟動事件創建了一個接收器,它將調用

 context.startService(Intent(context, BTService::class.java))

只是為了確保至少創建一次服務並注冊 bt 接收器

我的問題是:

a) 我的服務是否有可能在不處於前台模式時被破壞? 即當設備不在附近並且 bluetoothGat.connect 在自動連接時暫停? 是否足以讓我從 onStartCommand() 返回 START_STICKY 以確保即使我的服務被破壞它也會重新啟動?

b)如果有這種情況,有沒有辦法至少重新創建服務,以便至少注冊 btReceiver?

c) 在 autoconnect = true 的情況下,何時應在 bluetoothGatt 上調用close() 僅在創建新連接時(在我調用 Injections.buildConnection 的示例中)? 當藍牙適配器被禁用時,我是否也調用它? 或者如果用戶關閉藍牙適配器然后再次打開,我可以重復使用相同的連接和 bluetoothGatt 嗎?

d) 有沒有辦法找出自動連接是否失敗並且不會重試? 有沒有辦法實際測試和重現這種效果? 上面提到的文章說,當外圍設備的電池幾乎沒電時,或者當您處於藍牙范圍的邊緣時,可能會發生這種情況

提前感謝您提供的任何幫助

ab) 如果您的應用程序沒有處於前台的活動或服務,系統可能隨時將其終止。 掛起或活動的 BLE 連接不會影響系統何時終止應用程序的觀點。 (但在掃描廣告方面,情況完全不同。)

確保自動連接保持活躍的一般方法是讓前台服務始終運行。 因此,如果您想要掛起的連接,請不要在設備當前未連接時停止它。 使用 Job Scheduler、WorkManagers 等是沒有意義的,因為擁有前台服務應該足以讓應用程序進程保持活動狀態,並且只要應用程序存在,掛起/活動連接就會保持活動狀態。 該應用程序在等待掛起的 BLE 連接時根本不使用任何 cpu%。 然而,眾所周知,一些中國手機制造商不遵循 Android 文檔,有時會殺死應用程序,即使它們正在運行前台服務。

c) 每個 BluetoothGatt object 代表並指代同一部手機上運行的藍牙進程內的 object。 默認情況下,系統總共允許 32 個這樣的對象(我上次檢查過)。 為了釋放這些寶貴的資源,您調用close() 如果您忘記了,您將有泄漏,這意味着您的應用程序或其他一些應用程序可能無法創建 BluetoothGatt object。 (當應用程序進程退出時,它們的 BluetoothGatt 對象會自動關閉)。 API 的設計有點奇怪,既有disconnect方法又有close方法。 但無論如何, disconnect方法會優雅地啟動連接斷開,然后您將收到一個onConnectionStateChange回調,告訴您何時完成斷開連接。 但是,您必須調用close以釋放資源,或者如果您想重新連接,則調用connect ,或者您可以稍后采取行動。 在已連接的 BluetoothGatt object 上調用close也會斷開連接,但由於 object 同時被破壞,您不會收到任何回調。

由於所有 BluetoothGatt 對象都代表藍牙進程中的對象,因此當您關閉藍牙時,這些對象將“死亡”或停止工作,因為這涉及關閉藍牙進程。 這意味着您需要在重新啟動藍牙時重新創建所有 BluetoothGatt 對象。 您可以對舊對象調用close ,但由於它們已死,它不會做任何事情。 由於文檔沒有說明這一點,因此如果將來行為發生變化,我建議您無論如何都調用close以確保安全。

d) 要檢測connectGatt調用是否失敗並且不會重試,您可以監聽onConnectionStateChange回調。 如果這給出了錯誤代碼,例如 257,通常意味着系統已達到最大連接數,或某些資源的最大數量。 您可以通過簡單地啟動與一堆不同藍牙設備地址的掛起連接來測試這一點。

我不相信如果外圍設備電池電量不足或處於“藍牙范圍的邊緣”,新的連接嘗試將被中止的說法。 我很高興看到發生這種情況的 Android 藍牙源代碼的精確點,因為我真的相信這根本不是真的。

首先,如果您打算將您的應用程序分發到 Google Play 商店,如果我沒記錯的話,您需要定位最低 api 級別 29,因此您應該使用 JobService 和 JobScheduler 或WorkManager ,而不是 Service。 這是為了支持 Oreo(26) 以后的背景限制。

a)如果您正確實施了我上面提到的兩個選項中的任何一個,您可以編寫一個正確的服務,除非您停止它,否則它不會終止。 以下是 JobService 上的一些資源:( resource1 ,resource2 , resource3 )

b) 您可以根據您的 JobService 的 onStartJob() 方法重新注冊,這將重新創建您的應用程序。

c) 每次使用完外設 ble 設備后,都需要關閉與它的 gatt 連接。 這是 BluetoothGatt class 的片段

/**
 * Close this Bluetooth GATT client.
 *
 * Application should call this method as early as possible after it is done with
 * this GATT client.
 */
public void close() {

此外,從 BluetoothAdapter class javadoc 中,您可以看到禁用 ble 時所有連接都正常終止。

 /**
 * Turn off the local Bluetooth adapter—do not use without explicit
 * user action to turn off Bluetooth.
 * <p>This gracefully shuts down all Bluetooth connections, stops Bluetooth
 * system services, and powers down the underlying Bluetooth hardware.
 * <p class="caution"><strong>Bluetooth should never be disabled without
 * direct user consent</strong>. The {@link #disable()} method is
 * provided only for applications that include a user interface for changing
 * system settings, such as a "power manager" app.</p>
 * <p>This is an asynchronous call: it will return immediately, and
 * clients should listen for {@link #ACTION_STATE_CHANGED}
 * to be notified of subsequent adapter state changes. If this call returns
 * true, then the adapter state will immediately transition from {@link
 * #STATE_ON} to {@link #STATE_TURNING_OFF}, and some time
 * later transition to either {@link #STATE_OFF} or {@link
 * #STATE_ON}. If this call returns false then there was an
 * immediate problem that will prevent the adapter from being turned off -
 * such as the adapter already being turned off.
 *
 * @return true to indicate adapter shutdown has begun, or false on immediate error
 */
@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
public boolean disable() {

d) 我不確定會觸發什么回調。 為了重現,您提到的兩個項目似乎是可以嘗試的有效案例。

我希望這可以幫助您完善您的項目!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM