去年一直做物聯(lián)網(wǎng)這一塊七蜘。開始接手了一個(gè)Wifi的項(xiàng)目挥等,之后從零開始負(fù)責(zé)了一個(gè)BLE項(xiàng)目挂绰。這里有一個(gè)簡(jiǎn)單的DemoGithub - BLE Demo可供參考翰蠢。運(yùn)行起來可以搜索附近的藍(lán)牙設(shè)備并展示獲取的一些基本信息项乒,點(diǎn)擊鏈接查看各項(xiàng)服務(wù)以及字段。在實(shí)際的開發(fā)當(dāng)中梁沧,確實(shí)遇到了不少的問題檀何。這篇文章我想結(jié)合之前Wifi項(xiàng)目的經(jīng)驗(yàn),總結(jié)一下廷支。
BLE項(xiàng)目中使用的mesh網(wǎng)絡(luò)是網(wǎng)狀自組網(wǎng)絡(luò)频鉴。移動(dòng)端嘗試掃描周圍的設(shè)備,嘗試和這個(gè)網(wǎng)絡(luò)中的任意一臺(tái)藍(lán)牙設(shè)備進(jìn)行連接恋拍,直連的藍(lán)牙設(shè)備類似于一個(gè)路由垛孔。之后移動(dòng)端和網(wǎng)絡(luò)中的每一臺(tái)設(shè)備的通訊都需要通過這臺(tái)直連的設(shè)備。
我們?nèi)粘5膽?yīng)用開發(fā)是站在應(yīng)用層的層面施敢,在iOS的應(yīng)用里調(diào)用系統(tǒng)API或者使用網(wǎng)絡(luò)庫進(jìn)行通訊周荐,這些都是已經(jīng)高度封裝好的,無需過多的操心底層傳輸?shù)膯栴}僵娃。應(yīng)用可以從回調(diào)里收集到足夠的信息來處理和應(yīng)付當(dāng)前的網(wǎng)絡(luò)情況概作。而mesh網(wǎng)絡(luò)的情況類似于OSI模型里的網(wǎng)絡(luò)層,在沒有上層協(xié)議的支持下默怨,mesh網(wǎng)絡(luò)的傳輸是不可靠的讯榕。數(shù)據(jù)的傳輸量也非常的有限。頻繁的數(shù)據(jù)傳輸(比如從設(shè)備獲取一些數(shù)據(jù)量比較大的信息需要多次連續(xù)的傳輸)很可能造成mesh網(wǎng)絡(luò)的網(wǎng)絡(luò)風(fēng)暴先壕。這些問題都需要在我們的應(yīng)用里處理瘩扼。
在BLE項(xiàng)目中谆甜,整個(gè)項(xiàng)目被分成三層垃僚。底層是網(wǎng)絡(luò)層,負(fù)責(zé)和mesh網(wǎng)絡(luò)的通訊规辱;上層是我們的業(yè)務(wù)邏輯代碼谆棺;中間層是Model層負(fù)責(zé)數(shù)據(jù)的處理以及轉(zhuǎn)發(fā)。開始并沒有想到設(shè)計(jì)中間層,只是希望網(wǎng)絡(luò)層在收發(fā)消息以后通過回調(diào)直接和業(yè)務(wù)邏輯代碼進(jìn)行交互改淑,但隨著功能的增多碍岔,開始出現(xiàn)了問題。
網(wǎng)絡(luò)層承擔(dān)的任務(wù)太重朵夏。
首先網(wǎng)絡(luò)層需要和藍(lán)牙設(shè)備進(jìn)行通訊蔼啦。在與藍(lán)牙設(shè)備建立連接以及通訊的各個(gè)回調(diào)方法中,我們需要執(zhí)行檢查設(shè)備仰猖,自動(dòng)連接捏肢,設(shè)備驗(yàn)證以及等操作。發(fā)現(xiàn)設(shè)備之后還需要網(wǎng)絡(luò)層查詢?cè)O(shè)備信息進(jìn)行本地緩存饥侵。在通訊的過程中鸵赫,網(wǎng)絡(luò)層還需要處理數(shù)據(jù)進(jìn)行分發(fā)起到一個(gè)路由的作用。自上而下看躏升,上層下發(fā)的每一類命令都需要網(wǎng)絡(luò)層拼接發(fā)送辩棒。網(wǎng)絡(luò)層慢慢變的非常臃腫,過多的任務(wù)也讓原先網(wǎng)絡(luò)層通訊的職能顯得不明確膨疏。數(shù)據(jù)未經(jīng)處理直接轉(zhuǎn)發(fā)
當(dāng)我們接收到設(shè)備返回給我們的數(shù)據(jù)之后一睁,我們通過回調(diào)傳給業(yè)務(wù)邏輯代碼進(jìn)行處理。網(wǎng)絡(luò)層不知道業(yè)務(wù)邏輯層如何處理成肘,只能每次把數(shù)據(jù)直接丟給上層卖局。
在項(xiàng)目中,有一些界面會(huì)顯示一個(gè)設(shè)備的列表双霍,會(huì)根據(jù)我們接收的信息來即時(shí)刷新設(shè)備的狀態(tài)砚偶。正常情況下沒有任何問題,但在一些批量操作或者特定情況下洒闸,我們會(huì)收到大量的設(shè)備狀態(tài)信息染坯。這時(shí)前端的tableView會(huì)不停的調(diào)用刷新操作,造成界面的卡頓丘逸。實(shí)際上很多冗余的信息不需要刷新单鹿,我們可以嘗試進(jìn)行過濾并且做一些緩沖的操作來避免波峰對(duì)我們項(xiàng)目的沖擊。
- 代碼難以復(fù)用
在BLE項(xiàng)目中深纲,因?yàn)槲覀冃枰@示給客戶的是當(dāng)前環(huán)境下可操作的藍(lán)牙設(shè)備仲锄。也就是說只支持本地不支持遠(yuǎn)程,所以我們本身不需要緩存運(yùn)行時(shí)搜索到的設(shè)備湃鹊。(實(shí)際我們還是有做緩存儒喊,將搜索到的設(shè)備的mac地址以及設(shè)備類型等固定不變的信息緩存到本地,在搜索到設(shè)備的時(shí)候先匹配本地币呵,避免一些不必要的請(qǐng)求操作怀愧。)設(shè)備模型的生命周期是和App的生命周期保持一致的。項(xiàng)目中多處都需要使用當(dāng)前環(huán)境的藍(lán)牙設(shè)備列表,我們需要在每一個(gè)地方都處理網(wǎng)絡(luò)層返回的設(shè)備信息并進(jìn)行處理芯义。隨著項(xiàng)目的發(fā)展哈垢,重復(fù)代碼越來越多,并且我們難以保證各個(gè)地方藍(lán)牙設(shè)備列表的一致性扛拨。
為了解決這些問題并明確每一層的任務(wù)耘分,我嘗試在網(wǎng)絡(luò)層和業(yè)務(wù)邏輯層中間添加一個(gè)中間件。對(duì)于網(wǎng)絡(luò)層它是上層處理業(yè)務(wù)邏輯的對(duì)象绑警,對(duì)于業(yè)務(wù)邏輯代碼它是底層數(shù)據(jù)通訊的管理者陶贼。網(wǎng)絡(luò)層只關(guān)心和藍(lán)牙設(shè)備之間的驗(yàn)證以及通訊,業(yè)務(wù)邏輯層只關(guān)心事件的流程處理待秃。我們將之前網(wǎng)絡(luò)層數(shù)據(jù)的處理轉(zhuǎn)發(fā)拜秧,業(yè)務(wù)邏輯層的設(shè)備模型管理剝離出來,交給中間層處理章郁。
分層結(jié)束枉氮,考慮每一層之間的通訊。自上向下來看:
網(wǎng)絡(luò)層暴露命令發(fā)送接口給Model層暖庄,只接收拼接好的命令而不關(guān)心命令的內(nèi)容聊替。具體的命令由Model層拼接,單個(gè)設(shè)備的操作命令綁定到模型對(duì)象上作為實(shí)例方法培廓,關(guān)于mesh網(wǎng)絡(luò)所有設(shè)備的操作我們綁定到Model層的管理類上作為類方法惹悄。業(yè)務(wù)邏輯層根據(jù)需要調(diào)用Model層的方法即可。
自下向上肩钠,網(wǎng)絡(luò)層接收mesh網(wǎng)絡(luò)的消息泣港,通過回調(diào)層層上拋。在iOS當(dāng)中价匠,有三種回調(diào)的方式:block当纱,delegate以及notification。在這個(gè)項(xiàng)目當(dāng)中踩窖,網(wǎng)絡(luò)層和Model層之間采用的是delegate坡氯,Model層和業(yè)務(wù)邏輯層采用的是notification。
日常開發(fā)當(dāng)中洋腮,我最喜歡也是最常用的是block來處理回調(diào)箫柳,因?yàn)樗浅5妮p,非常簡(jiǎn)單的就嵌入了邏輯代碼當(dāng)中啥供,便于閱讀悯恍。非常簡(jiǎn)單的就可以訪問上下文。作為一個(gè)匿名函數(shù)滤灯,獨(dú)立的詞法作用域可以保存現(xiàn)場(chǎng)便于處理坪稽。但非常遺憾在這個(gè)項(xiàng)目里它并不合適。
我們說了非常多的block的優(yōu)點(diǎn)鳞骤,但它的缺點(diǎn)也非常的明顯窒百。因?yàn)閎lock太輕了,所以稍微復(fù)雜一點(diǎn)的情況就會(huì)顯得力不從心豫尽。在業(yè)務(wù)邏輯層和Model層之間有非常多的交互場(chǎng)景篙梢,如果為每一種場(chǎng)景都去設(shè)置一個(gè)block就會(huì)顯得非常瑣碎和難以管理美旧。Model層和網(wǎng)絡(luò)層之間回調(diào)的處理比較簡(jiǎn)單渤滞,主要就是將收到的消息簡(jiǎn)單處理上拋給Model層處理,但是我們要考慮到一點(diǎn)的是榴嗅,block非常容易造成retain cycle妄呕。除了非常常見的strong/weak self的處理,還有一點(diǎn)需要注意的是嗽测,因?yàn)槲覀冮L(zhǎng)期需要這個(gè)block所以它是一直被持有不被釋放的绪励。一旦造成內(nèi)存泄漏,非常難以察覺出來唠粥。
我們可以參考蘋果的做法疏魏,block和delegate在Cocoa中都有大量實(shí)例,比如簡(jiǎn)單的UIView的animation類方法晤愧,一些界面跳轉(zhuǎn)的completion使用的是block大莫,UITableView卻是使用的delegate。對(duì)比一下官份,臨時(shí)性一次性的只厘,或者說長(zhǎng)期持有但有一個(gè)明確的完成時(shí)間的,我們使用block舅巷;但是需要頻繁調(diào)用的我們最好還是采用delegate懈凹。
在剩下的兩種回調(diào)的方式里,比起delegate悄谐,notification的使用太過隨意介评,你可以在任意位置收發(fā)notification。而且注冊(cè)通知使用完以后你還必須手動(dòng)去取消注冊(cè)爬舰。但是notification有一點(diǎn)比delegate強(qiáng)那就是它具有類似廣播的特性能夠一對(duì)多们陆。
實(shí)際上block也可以實(shí)現(xiàn)一對(duì)多,只是會(huì)比較的麻煩情屹。在SDWebImage中用一個(gè)字典按照url生成的標(biāo)識(shí)符來保存block的數(shù)組坪仇,執(zhí)行完某個(gè)任務(wù)后根據(jù)標(biāo)識(shí)符取出數(shù)組依次執(zhí)行block即可。
回到項(xiàng)目中垃你,在網(wǎng)絡(luò)層和Model層之間這是一對(duì)一的關(guān)系椅文。非常適合delegate的發(fā)揮喂很。我們通過Protocol來定義通訊過程中的各個(gè)部分,處理起來非常的有條理皆刺。還有一個(gè)很重要的原因就是網(wǎng)絡(luò)層和Model層之間處理的數(shù)據(jù)都是基本類型少辣,而notification只能傳遞ObjectType類型。但是在Model層和業(yè)務(wù)邏輯層之間羡蛾,常常一個(gè)模型參數(shù)的變化需要多個(gè)地方去響應(yīng)漓帅,這個(gè)時(shí)候我們使用nitofication就會(huì)比較的合適了。
當(dāng)然痴怨,notification本身的一些缺點(diǎn)我們可以通過規(guī)范代碼來改善忙干。在本項(xiàng)目中:
- 所有notificationName都統(tǒng)一在一個(gè)文件中進(jìn)行宏定義。
- 一個(gè)notificationName為get和post定義成兩套宏浪藻,按照get/post+事件名稱的規(guī)則來命名捐迫。
- 相同事件的響應(yīng)函數(shù)名稱保持一致。按照get+事件名稱+notification的規(guī)則命名
這樣規(guī)范以后爱葵,notification相關(guān)的內(nèi)容管理起來就會(huì)方便很多弓乙。
在BLE的項(xiàng)目中,app一直和藍(lán)牙設(shè)備保持著密切的通訊钧惧。除了app向藍(lán)牙設(shè)備請(qǐng)求之外暇韧,藍(lán)牙設(shè)備自身在狀態(tài)變化的時(shí)候也會(huì)主動(dòng)推給app消息。網(wǎng)絡(luò)層實(shí)際只承擔(dān)簡(jiǎn)單的收發(fā)作用浓瞪,至于具體的什么內(nèi)容懈玻,不關(guān)心也不知道。這一切都丟給了Model層來打理乾颁。
之前接手的Wifi項(xiàng)目涂乌,出現(xiàn)了一個(gè)什么問題呢。在Wifi項(xiàng)目中所有收發(fā)的消息都通過notification發(fā)出去了英岭,在notification的信息里只是簡(jiǎn)單的區(qū)分了是哪種協(xié)議收到的消息湾盒,但不關(guān)心消息的類型,全部廣播出去诅妹。需要接收消息的地方注冊(cè)notification接收了消息以后根據(jù)消息的內(nèi)容(字典)再去判斷是不是我需要的內(nèi)容罚勾。
問題很大:
通訊的消息很多,消息類型有很多種吭狡。很多時(shí)候某個(gè)界面只是關(guān)心特定的消息類型尖殃,但是卻被迫要去處理每一條返回的消息。 這里的處理不僅僅是簡(jiǎn)單的判斷划煮,需要你取出字典里的內(nèi)容進(jìn)行比對(duì)送丰,每一條關(guān)鍵的數(shù)據(jù)都需要進(jìn)行合法判定。消息類型判定的邏輯如果發(fā)生了變化你需要在每一個(gè)notification里去修改弛秋。
無法過濾這些消息器躏。比如設(shè)備會(huì)定時(shí)上報(bào)自己的狀態(tài)俐载,假設(shè)我的界面顯示設(shè)備是打開的狀態(tài),如果最新設(shè)備上報(bào)的狀態(tài)仍然是開登失。我們實(shí)際可以直接處理掉這條消息遏佣,只在設(shè)備狀態(tài)變化的時(shí)候才去通知我們的業(yè)務(wù)邏輯做處理
可能會(huì)影響未來業(yè)務(wù)的邏輯判斷。整個(gè)項(xiàng)目實(shí)際上是不斷在增加新的業(yè)務(wù)需求壁畸。回調(diào)里的一些操作可能會(huì)因?yàn)樾聵I(yè)務(wù)的增加而在開發(fā)者不明了的情況下被觸發(fā)茅茂,造成一些稀奇古怪的問題捏萍。比如重連,登錄名和密碼被切換空闲。
除了這幾點(diǎn)BLE項(xiàng)目還有一個(gè)比較麻煩的地方是令杈,mesh網(wǎng)絡(luò)內(nèi)各個(gè)設(shè)備之間會(huì)定期進(jìn)行通信確認(rèn)設(shè)備的在線狀態(tài),但是外部如果正在請(qǐng)求數(shù)據(jù)會(huì)影響到內(nèi)部的通訊碴倾,超過一定時(shí)間會(huì)判定某個(gè)設(shè)備下線逗噩,但是下線狀態(tài)會(huì)立刻被刷新回來。反映到app里就是顯示設(shè)備開關(guān)的按鈕會(huì)出現(xiàn)閃爍的情況跌榔。我們希望能夠在應(yīng)用內(nèi)添加判斷把這種情況給處理掉异雁。
網(wǎng)絡(luò)層在接收消息之后,會(huì)將數(shù)據(jù)通過回調(diào)傳遞給Model層僧须。大概的流程是:Model層會(huì)先根據(jù)消息里的標(biāo)志位區(qū)分消息的任務(wù)類型纲刀。之后對(duì)消息進(jìn)行清洗。在這一步所有不合法的消息都會(huì)被丟棄担平,合法的信息將被解析處理成模型(信息是一個(gè)char類型的數(shù)組)示绊,確保各項(xiàng)數(shù)據(jù)無誤以后再通過notification分發(fā)出去。
Model層在消息接收的地方添加了一個(gè)時(shí)延暂论,類似TCP協(xié)議里的ack捎帶操作面褐。一些狀態(tài)類型的消息在接收以后我們會(huì)延遲一段時(shí)間等待是否有其他的狀態(tài)信息上報(bào),確認(rèn)沒有之后再進(jìn)行下一步操作取胎,如果有新的消息則重置延遲時(shí)間展哭。上報(bào)狀態(tài)消息的設(shè)備如果相同則保存最后一條狀態(tài)消息,設(shè)備不同就打包這些設(shè)備模型等到延遲結(jié)束一起丟給業(yè)務(wù)邏輯層處理闻蛀,這樣做可以有效避免mesh網(wǎng)絡(luò)自身通訊不暢引起的問題以及減輕前端UI部分的壓力摄杂。
如果同時(shí)有多個(gè)設(shè)備的狀態(tài)信息上報(bào)過來可以一次刷新表格處理完而不用短時(shí)間內(nèi)多次調(diào)用單行刷新的操作。類似微信的收消息循榆,如果單條短訊就一條一條提示析恢,但是如果同時(shí)來了多條短訊比如刷表情就會(huì)一起提示有‘xx'個(gè)未讀消息。
合法的消息被處理成模型之后進(jìn)行分發(fā)秧饮,Model層在這一塊做了一些處理映挂。首先泽篮,我們將返回類型大致分為幾種。通知分發(fā)的時(shí)候走不同的notificationName柑船。這里區(qū)分的標(biāo)準(zhǔn)是各種模型在項(xiàng)目中涉及的界面和服務(wù)層級(jí)帽撑。服務(wù)層級(jí)可能描述的不是準(zhǔn)確。做一個(gè)簡(jiǎn)單的類比鞍时,在Python中l(wèi)ogger模塊有一個(gè)level的參數(shù)亏拉,你可以設(shè)定不同的level來區(qū)分log的級(jí)別也就是重要程度。同樣在BLE項(xiàng)目中不同的消息我們關(guān)心的程度會(huì)不一樣逆巍。開關(guān)之類的是基礎(chǔ)消息頻繁而簡(jiǎn)單及塘;設(shè)備類型之類的查詢消息簡(jiǎn)單且相關(guān)的查詢需求基本都是集中使用;定時(shí)器的查詢信息查詢量大且服務(wù)比較重要獨(dú)占一個(gè)模塊锐极。我們依據(jù)各種消息的特點(diǎn)進(jìn)行劃分層級(jí)區(qū)別分發(fā)笙僚,但是并沒有區(qū)分的太細(xì)。這樣做的目的是為了讓各個(gè)notification能夠盡可能獨(dú)立不會(huì)打擾到不相關(guān)的部分灵再,又不會(huì)分的太散導(dǎo)致notification過多難以管理肋层,某些地方可能會(huì)出現(xiàn)需要注冊(cè)多個(gè)通知去接收所需的消息。在notification分完層級(jí)之后翎迁,在每一個(gè)notification中添加一個(gè)int類型的掩碼來具體指明每一個(gè)notification所傳遞的類型栋猖。每一位標(biāo)示一個(gè)消息類型,注冊(cè)了notification的地方通過和掩碼的位運(yùn)算就可以快速確定每條notification中的模型所代表的內(nèi)容汪榔。
mesh網(wǎng)絡(luò)中有多個(gè)通道掂铐,為了保證開關(guān)之類的基礎(chǔ)消息能夠即時(shí)傳遞,留給其他服務(wù)的資源是非常有限的揍异。在應(yīng)用里我們?nèi)绻矔r(shí)交互大量的數(shù)據(jù)會(huì)造成非常嚴(yán)重的丟包和網(wǎng)絡(luò)狀況的不穩(wěn)定全陨。在硬件條件無法改善的情況下,我們必須在應(yīng)用里做相關(guān)的處理來改善情況衷掷。并且涉及到一些關(guān)鍵消息的傳輸辱姨,可靠性的支持必須由我們應(yīng)用來實(shí)現(xiàn)。
在BLE項(xiàng)目中發(fā)送的消息可以分為兩類戚嗅。一類是發(fā)送出去不關(guān)心結(jié)果沒有回調(diào)的消息雨涛,比如開關(guān)之類;另一類是需要從BLE設(shè)備處獲取信息的查詢命令懦胞,比如獲取鬧鐘替久,查詢BLE設(shè)備信息之類的命令。
有關(guān)設(shè)備的基礎(chǔ)命令我們直接發(fā)送躏尉,指定好設(shè)備和消息類型由Model層打包命令丟給網(wǎng)絡(luò)層發(fā)送就好蚯根。因?yàn)橹耙呀?jīng)說過mesh網(wǎng)絡(luò)已經(jīng)為這些服務(wù)預(yù)留了足夠的資源,那么我們可以直接發(fā)送不做任何延時(shí)之類的處理胀糜。組的開關(guān)也無需擔(dān)心颅拦,因?yàn)楹鸵话沩?xiàng)目的不同是蒂誉,BLE組功能是將地址位設(shè)為0xffff其余部分和單條開關(guān)消息一致。消息在mesh網(wǎng)絡(luò)中傳遞距帅,設(shè)備對(duì)地址進(jìn)行匹配判斷自己是否在組內(nèi)來進(jìn)行響應(yīng)即可右锨,而不是逐個(gè)通知增大了消息量。而后一種的查詢命令碌秸,就復(fù)雜了很多绍移。因?yàn)閙esh網(wǎng)絡(luò)預(yù)留的資源不多而且需要使用大量的數(shù)據(jù)交互,同時(shí)我們還要求BLE設(shè)備返回消息給我們讥电。文章的開始提到過mesh網(wǎng)絡(luò)類似網(wǎng)絡(luò)層蹂窖,對(duì)應(yīng)的協(xié)議是IP協(xié)議。而我們?nèi)粘i_發(fā)所接觸的TCP/IP協(xié)議簇以及基于此的HTTP協(xié)議的可靠性支持都是由IP協(xié)議的上層協(xié)議TCP協(xié)議實(shí)現(xiàn)的允趟。針對(duì)BLE項(xiàng)目中需要明確結(jié)果的查詢需求如何處理恼策?
先簡(jiǎn)單列舉一下實(shí)際遇到的麻煩鸦致。
丟包率高潮剪,數(shù)據(jù)傳輸不可靠。簡(jiǎn)單來說就是通信壓力過大mesh網(wǎng)絡(luò)無法負(fù)擔(dān)分唾,一般來說如果是非直連的BLE設(shè)備都會(huì)丟百分之十至三十的包抗碰,甚至BLE設(shè)備癱瘓?jiān)斐蒻esh網(wǎng)絡(luò)短時(shí)間無法通信的情況
數(shù)據(jù)量如果比較大,無法一個(gè)包發(fā)完绽乔,需要移動(dòng)端依次請(qǐng)求讓BLE設(shè)備逐個(gè)發(fā)送(類似分片)弧蝇。我們的設(shè)備需要處理發(fā)送和接收的邏輯來保證數(shù)據(jù)的正確性。
BLE設(shè)備會(huì)有多個(gè)版本折砸,對(duì)于部分消息類型協(xié)議有所不同看疗。
解決問題之前需要明確的一點(diǎn)是,什么是可靠性睦授。查詢的結(jié)果每一條都有明確的回復(fù)两芳,這個(gè)回復(fù)可以是BLE設(shè)備正確的返回,也可以是出現(xiàn)錯(cuò)誤我們得到的狀態(tài)碼去枷。大概類似于所謂的生要見人死要見尸怖辆,我們必須知道每一條命令發(fā)送出去的結(jié)果。
對(duì)比真實(shí)的網(wǎng)絡(luò)删顶,mesh網(wǎng)絡(luò)的情況還是簡(jiǎn)單的很多竖螃。在HTTP請(qǐng)求中,response的status code可以提供給client本次request的結(jié)果逗余。status code種類非常多特咆,涵蓋了幾乎所有網(wǎng)絡(luò)中可能出現(xiàn)的情況,情況非常的多和復(fù)雜录粱。但實(shí)際在BLE項(xiàng)目中坚弱,出現(xiàn)問題的時(shí)候比如超時(shí)蜀备,權(quán)限不夠被拒絕,數(shù)據(jù)包過大之類荒叶,BLE設(shè)備和mesh網(wǎng)絡(luò)不會(huì)通知移動(dòng)端碾阁。我們可能遇到的情況實(shí)際只有發(fā)送成功正確收到返回消息和等待超時(shí)沒有返回兩種。
實(shí)際還有一種可能是我們多個(gè)消息發(fā)送出去同時(shí)返回多個(gè)結(jié)果些楣,造成接收的錯(cuò)亂脂凶。但后面我們會(huì)提到這種情況不會(huì)發(fā)生,因?yàn)榭紤]到mesh網(wǎng)絡(luò)的承擔(dān)能力以及本身設(shè)備不支持我們查詢一組數(shù)據(jù)中特定位置的數(shù)據(jù)愁茁,我們必須使用串行隊(duì)列依次發(fā)送消息蚕钦。實(shí)現(xiàn)類似TCP協(xié)議中的Negla算法。
為了避免出現(xiàn)丟包的情況鹅很,我們應(yīng)當(dāng)避免短時(shí)間內(nèi)較大數(shù)據(jù)量的傳輸嘶居。但是如果需求確實(shí)要進(jìn)行大量的數(shù)據(jù)交互,在硬件條件無法改善的環(huán)境下促煮,我們考慮的是限制傳輸?shù)乃俾蕘頊p輕mesh網(wǎng)絡(luò)的壓力邮屁。
具體的實(shí)現(xiàn)是一個(gè)NSOperationQueue來做一個(gè)串行隊(duì)列,它的最大允許并行數(shù)設(shè)置為1菠齿。必須等待上一挑命令執(zhí)行完成才會(huì)執(zhí)行下一條的命令佑吝。這樣做的目的一來是為了減輕通訊的壓力;二來如果同時(shí)返回多條消息可能出現(xiàn)亂序(一條消息返回的路徑并不確定绳匀。后發(fā)出的消息可能通過較短的路徑先到達(dá)目的端芋忿。而且我們的設(shè)備不支持查詢特定index的數(shù)據(jù)只能一次查詢?nèi)浚苿?dòng)端無法正確識(shí)別疾棵。
關(guān)于傳輸可靠性的實(shí)現(xiàn)也是基于這個(gè)串行隊(duì)列的方法:每一條命令發(fā)送出去的時(shí)候都是可以明確返回?cái)?shù)據(jù)的格式戈钢,移動(dòng)端在發(fā)送一條命令之后只有接收到對(duì)應(yīng)的消息才會(huì)確認(rèn)當(dāng)前命令已經(jīng)完成。第一次發(fā)出查詢命令以后會(huì)等待1s是尔,如果沒有返回則2S后發(fā)送第二條命令殉了;等待1s,接收到數(shù)據(jù)繼續(xù)查詢嗜历,沒有返回則5s后嘗試查詢最后一次宣渗,還沒有正確接收認(rèn)定當(dāng)前通訊狀況不佳,這條命令沒有成功返回我們有理由相信其他命令在當(dāng)前網(wǎng)絡(luò)環(huán)境下也是無法正確收到返回的梨州,所以程序返回超時(shí)錯(cuò)誤痕囱,提示用戶當(dāng)前網(wǎng)絡(luò)狀況不佳。每一次間隔時(shí)間的延長(zhǎng)是考慮到如果沒有成功接收到返回消息暴匠,程序認(rèn)為當(dāng)前網(wǎng)絡(luò)環(huán)境不佳鞍恢,多等待一些時(shí)間允許mesh網(wǎng)絡(luò)進(jìn)行調(diào)整和處理擁塞的消息。
關(guān)于和mesh網(wǎng)絡(luò)通訊不暢的情況,還有一種可能是用戶在使用程序的同時(shí)位置發(fā)生了改變帮掉,離開始連接的設(shè)備相隔了一定的距離從而出現(xiàn)了假死弦悉。實(shí)際網(wǎng)絡(luò)層會(huì)有一個(gè)計(jì)數(shù)器,出現(xiàn)超時(shí)情況之后計(jì)數(shù)器會(huì)累加蟆炊。計(jì)數(shù)到上限值時(shí)做一個(gè)檢查操作稽莉,重新掃描附近的設(shè)備對(duì)比RSSI。如果當(dāng)前連接的設(shè)備信號(hào)強(qiáng)度較弱涩搓,則嘗試切換到信號(hào)良好的設(shè)備上污秆。
在拿到數(shù)據(jù)之后,記錄型的數(shù)據(jù)我們需要做好數(shù)據(jù)的本地化存儲(chǔ)昧甘。之后每次查詢這類數(shù)據(jù)先嘗試從本地獲取良拼,如果沒有再向設(shè)備請(qǐng)求。以此減少查詢的數(shù)據(jù)量充边。
上面是從整體的概念上去解釋了整個(gè)項(xiàng)目的規(guī)劃設(shè)計(jì)庸推。實(shí)際項(xiàng)目中,使用了Category來將一些管理類劃分成多個(gè)部分浇冰。組合優(yōu)于繼承贬媒,特別是在BLE項(xiàng)目中新業(yè)務(wù)新功能不斷增加的環(huán)境下。我們?cè)谥髦蠈?shí)現(xiàn)基礎(chǔ)功能湖饱,根據(jù)不同業(yè)務(wù)再將相關(guān)業(yè)務(wù)代碼剝離出去掖蛤。方便我們后續(xù)的維護(hù)以及拓展杀捻。