Android BLE藍(lán)牙連接要注意的問(wèn)題

一、運(yùn)行時(shí)問(wèn)題

發(fā)起請(qǐng)求

手機(jī)和藍(lán)牙設(shè)備建立連接,不論是在哪個(gè)進(jìn)程,哪個(gè)線(xiàn)程發(fā)起的請(qǐng)求,最終都要丟到系統(tǒng)藍(lán)牙服務(wù)進(jìn)程中去處理。

看到有的文章說(shuō)在同一個(gè)進(jìn)程中,發(fā)起的連接和讀寫(xiě)等請(qǐng)求最好都在UI線(xiàn)程里,這種說(shuō)法我不認(rèn)同。原因有兩點(diǎn):

1,不論發(fā)起請(qǐng)求是在UI線(xiàn)程還是子線(xiàn)程,都只是封裝成數(shù)據(jù)結(jié)構(gòu)丟給系統(tǒng)藍(lán)牙服務(wù)進(jìn)程,這種跨進(jìn)程通信最終會(huì)調(diào)到系統(tǒng)藍(lán)牙服務(wù)進(jìn)程的Binder線(xiàn)程池中,然后再由底層的藍(lán)牙框架分發(fā)到不同的線(xiàn)程中去處理請(qǐng)求,因此上層調(diào)用方在哪個(gè)線(xiàn)程對(duì)底層來(lái)說(shuō)沒(méi)太大關(guān)系。

2,這種跨進(jìn)程調(diào)用最好不要放在UI線(xiàn)程里,我們知道跨進(jìn)程調(diào)用時(shí)調(diào)用方會(huì)被暫時(shí)掛起,直到處理方請(qǐng)求處理完畢。通常來(lái)說(shuō)處理方處理請(qǐng)求會(huì)是很快的,如果是耗時(shí)的請(qǐng)求就異步處理,這樣能保證調(diào)用方掛起的時(shí)間很短暫。然而當(dāng)系統(tǒng)負(fù)載很重時(shí),這種跨進(jìn)程通信的調(diào)度延時(shí)會(huì)增加,可能導(dǎo)致UI界面卡頓甚至ANR。

建議的做法是統(tǒng)一分派到一個(gè)子線(xiàn)程中去發(fā)起所有的請(qǐng)求,即便對(duì)于多個(gè)設(shè)備也是如此,沒(méi)有必要每個(gè)設(shè)備一個(gè)線(xiàn)程。

請(qǐng)求回調(diào)

當(dāng)系統(tǒng)藍(lán)牙服務(wù)處理請(qǐng)求之后,有狀態(tài)更新時(shí)會(huì)以回調(diào)的方式通知給調(diào)用方,比如連接上了,或連接斷開(kāi)了,或數(shù)據(jù)讀到了,或數(shù)據(jù)寫(xiě)成功了,或設(shè)備有數(shù)據(jù)推送過(guò)來(lái)了等等。

要注意的是這些回調(diào)都不是在UI線(xiàn)程,而是在Binder線(xiàn)程池中。一定要注意到這一點(diǎn),因?yàn)檎{(diào)用方和回調(diào)方不在同一個(gè)線(xiàn)程,可能存在數(shù)據(jù)不一致的問(wèn)題,要么上鎖,要么post到同一個(gè)線(xiàn)程再處理。

二、異步調(diào)用問(wèn)題

藍(lán)牙的所有請(qǐng)求基本都是異步調(diào)用的,對(duì)同一個(gè)設(shè)備,其所有通信過(guò)程只能串行,不能并行。如不能同時(shí)發(fā)起讀和寫(xiě)的異步調(diào)用,不能同時(shí)發(fā)起多個(gè)character的異步讀調(diào)用等。必須等到上一個(gè)操作的回調(diào)之后再發(fā)起下一個(gè)操作。因此我們需要維護(hù)一個(gè)任務(wù)隊(duì)列,自動(dòng)將同一個(gè)設(shè)備的所有操作串行化。對(duì)于多設(shè)備的情況,每個(gè)設(shè)備維護(hù)一個(gè)任務(wù)隊(duì)列。不過(guò)多個(gè)設(shè)備都共享一個(gè)請(qǐng)求分派子線(xiàn)程。

例外的是closeGatt,這個(gè)無(wú)需放在隊(duì)列中,當(dāng)要斷開(kāi)設(shè)備連接時(shí),先清空任務(wù)隊(duì)列,再closeGatt。

另外還需對(duì)每個(gè)調(diào)用做超時(shí)檢查,有時(shí)候藍(lán)牙協(xié)議棧異??赡苁詹坏交卣{(diào),如果不做超時(shí)檢查,后面的所有操作都被堵塞了。對(duì)于這些超時(shí)的任務(wù),通常是連接出了問(wèn)題,建議的做法是直接給gatt關(guān)掉,下次重新連接的時(shí)候重開(kāi)一個(gè)gatt。

三、緩存問(wèn)題

可能有人想過(guò)設(shè)備的service既然不變,為什么每次連接都要discover service,不如緩存起來(lái),設(shè)備重連時(shí)直接用,但實(shí)際情況不能這樣做。service不能緩存,雖然uuid什么的都沒(méi)變,但是這些service都會(huì)和gatt關(guān)聯(lián)的,如果gatt變了,那service就報(bào)廢了,對(duì)這些service和character做任何讀寫(xiě)操作都會(huì)出錯(cuò)。所以建議每次連接上時(shí)都去discover service,不要緩存。

當(dāng)設(shè)備固件升級(jí)后,character可能發(fā)生了變化,而系統(tǒng)藍(lán)牙協(xié)議棧是不知道的,下次discover service的時(shí)候還是返回的舊的緩存,這樣讀寫(xiě)character可能會(huì)失敗。解決辦法是固件升級(jí)后,斷開(kāi)連接再重開(kāi)一個(gè)gatt,并馬上刷新一下該設(shè)備的緩存。當(dāng)然,重啟藍(lán)牙也會(huì)刷新緩存,不過(guò)會(huì)影響到所有設(shè)備。另外有時(shí)候discover service服務(wù)發(fā)現(xiàn)的不全,或者根本發(fā)現(xiàn)不了服務(wù),也可以考慮清除一下緩存。?

關(guān)于緩存的清除可以參考

how-to-programmatically-force-bluetooth-low-energy-service-discovery-on-android

四、連接釋放

設(shè)備的GATT句柄在不用時(shí)要及時(shí)關(guān)閉,不然會(huì)造成連接泄露,而系統(tǒng)支持的連接句柄數(shù)是有限的,如果沒(méi)記錯(cuò)的話(huà)是32個(gè),當(dāng)達(dá)到上限后其它設(shè)備就連不了了。注意這是系統(tǒng)全局性的,如果你所在APP占用了過(guò)多GATT句柄,其它的APP進(jìn)程就可能無(wú)法成功申請(qǐng)GATT句柄了。因?yàn)椴徽撜{(diào)用方在哪個(gè)進(jìn)程,最終都是由系統(tǒng)服務(wù)進(jìn)程來(lái)分配GATT句柄的。

當(dāng)設(shè)備斷開(kāi)連接時(shí),最好closeGatt,而不是diconnect。不要下次復(fù)用之前的gatt來(lái)reconnect,因?yàn)橛械氖謾C(jī)上重連可能會(huì)存在問(wèn)題,比如重連后死活發(fā)現(xiàn)不了service。這種情況下,最好只要斷開(kāi)連接就close gatt,下次連接時(shí)打開(kāi)全新的gatt,這樣就可以發(fā)現(xiàn)service了。

每個(gè)GATT背后都有一套對(duì)應(yīng)的狀態(tài)機(jī),各種異常基本上除了連接本身之外都是狀態(tài)機(jī)出了問(wèn)題,因此直接關(guān)掉當(dāng)前GATT,重開(kāi)一個(gè)GATT就能解決大部分問(wèn)題。就如同解決大部分軟件異常,最直接的辦法就是重啟一樣。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容