5、外設(shè)模式的使用
5.1 App 作為外設(shè)被連接的實(shí)現(xiàn)
1档玻、啟動(dòng)一個(gè) Peripheral 管理對(duì)象
打開(kāi) peripheralManager,設(shè)置 peripheralManager 的委托背犯。
2脆烟、配置本地 Peripheral,設(shè)置服務(wù)志秃、特性怔球、描述、權(quán)限等等
創(chuàng)建 characteristics洽损,characteristics 的 description庞溜,創(chuàng)建 service,把 characteristics 添加到 service 中,再把 service 添加到 peripheralManager 中流码。
當(dāng) peripheral 成功打開(kāi)后又官,才可以配置 service 和 characteristics。這里創(chuàng)建的 service 和 characteristics 對(duì)象是 CBMutableCharacteristic 和 CBMutableService漫试。他們的區(qū)別就像 NSArray 和 NSMutableArray 區(qū)別類似六敬。我們先創(chuàng)建 characteristics 和 description,description 是 characteristics 的描述驾荣,描述分很多種外构,常用的就是 CBUUIDCharacteristicUserDescriptionString。
3播掷、開(kāi)啟廣播 advertising
添加發(fā)送廣播后悔調(diào)用代理的 peripheralManagerDidStartAdvertising:error: 方法审编。
4、設(shè)置處理訂閱歧匈、取消訂閱垒酬、讀 characteristic、寫 characteristic 的委托方法
5.2 作為 Peripheral 時(shí)的請(qǐng)求響應(yīng)
5.2.1 初始化 CBPeripheralManager
將設(shè)備作為 peripheral件炉,第一步就是初始化 CBPeripheralManager 對(duì)象勘究。可以通過(guò)調(diào)用 CBPeripheralManager 的initWithDelegate:queue:options:方法來(lái)進(jìn)行初始化:
上面的幾個(gè)參數(shù)中斟冕,將 self 設(shè)為代理來(lái)接收相關(guān)回調(diào)口糕,queue 為 nil 表示在主線程。
當(dāng)你調(diào)用上面這方法后磕蛇,便會(huì)回調(diào)peripheralManagerDidUpdateState:景描。所以在此之前,你需要先遵循CBPeripheralManagerDelegate秀撇。這個(gè)代理方法能獲取當(dāng)前 iOS 設(shè)備能否作為 peripheral伏伯。
5.2.2 配置 service 和 characteristic
就像之前講到的一樣,peripheral 數(shù)據(jù)庫(kù)是一個(gè)樹形結(jié)構(gòu)捌袜。
Bluetooth24
所以在創(chuàng)建 peripheral 的時(shí)候说搅,也要像這種樹形結(jié)構(gòu)一樣,將 service 和 characteristic 裝進(jìn)去虏等。在此之前弄唧,我們需要做的是學(xué)會(huì)如何標(biāo)識(shí) service 和 characteristic。
1霍衫、使用 UUID 來(lái)標(biāo)識(shí) service 和 characteristic
service 和 characteristic 都通過(guò) 128 位的 UUID 來(lái)進(jìn)行標(biāo)識(shí)候引,Core Bluetooth 將 UUID 封裝為了 CBUUID 。關(guān)于詳細(xì) UUID 的介紹敦跌,請(qǐng)參考上面的 4.3.1 CBUUID 講解澄干。
2逛揩、為自定義的 service 和 characteristic 創(chuàng)建 UUID
你的 service 或者 characteristic 的 UUID 并沒(méi)有公共的 UUID,這時(shí)你需要?jiǎng)?chuàng)建自己的 UUID麸俘。
使用命令行的uuidgen能很容易的生成 UUID辩稽。首先打開(kāi)終端,為你的每一個(gè) service 和 characteristic 創(chuàng)建 UUID从媚。在終端輸入uuidgen然后回車逞泄,具體如下:
可以通過(guò)UUIDWithString:方法,將 UUID 生成 CBUUID 對(duì)象拜效。
3喷众、構(gòu)建 service 和 characteristic 樹形結(jié)構(gòu)
在將 UUID 打包為 CBUUID 之后,就可以創(chuàng)建 CBMutableService 和 CBMutableCharacteristic 并把他們組成一個(gè)樹形結(jié)構(gòu)了紧憾。創(chuàng)建 CBMutableCharacteristic 對(duì)象可以通過(guò)該類的initWithType:properties:value:permissions:方法:
創(chuàng)建 characteristic 的時(shí)候到千,就為他設(shè)置了 properties 和 permissions。這兩個(gè)屬性分別定義了 characteristic 的可讀寫狀態(tài)和 central 連接后是否能訂閱赴穗。上面這種初始化方式父阻,代表著 characteristic 可讀。更多的選項(xiàng)望抽,可以去看看CBMutableCharacteristic Class Reference。
如果給 characteristic 設(shè)置了 value 參數(shù)履婉,那么這個(gè) value 會(huì)被緩存煤篙,并且 properties 和 permissions 會(huì)自動(dòng)設(shè)置為可讀。如果想要 characteristic 可寫毁腿,或者在其生命周期會(huì)改變它的值辑奈,那需要將 value 設(shè)置為 nil。這樣的話已烤,就會(huì)動(dòng)態(tài)的來(lái)處理 value 鸠窗。
現(xiàn)在已經(jīng)成功的創(chuàng)建了 characteristic,下一步就是創(chuàng)建一個(gè) service胯究,并將它們構(gòu)成樹形結(jié)構(gòu)稍计。調(diào)用 CBMutableService 的initWithType:primary:方法來(lái)初始化 service:
第二個(gè)參數(shù) primary 設(shè)置為 YES 表示該 service 為 primary service(主服務(wù)),與 secondary service(次服務(wù))相對(duì)裕循。primary service 描述了設(shè)備的主要功能臣嚣,并且能包含其他 service。secondary service 描述的是引用它的那個(gè) service 的相關(guān)信息剥哑。比如硅则,一個(gè)心率監(jiān)測(cè)器,primary service 描述的是當(dāng)前心率數(shù)據(jù)株婴,secondary service 描述描述的是當(dāng)前電量怎虫。
創(chuàng)建了 service 之后,就可以包含 characteristic 了:
5.2.3 發(fā)布 service 和 characteristic
構(gòu)建好樹形結(jié)構(gòu)之后,接下來(lái)便需要將這結(jié)構(gòu)加入設(shè)備的數(shù)據(jù)庫(kù)大审。這一操作 Core Bluetooth 已經(jīng)封裝好了蘸际,調(diào)用 CBPeripheralManager 的addService:方法即可:
當(dāng)調(diào)用以上方法時(shí),便會(huì)回調(diào) CBPeripheralDelegate 的peripheralManager:didAddService:error:回調(diào)饥努。當(dāng)有錯(cuò)誤捡鱼,或者當(dāng)前 service 不能發(fā)布的時(shí)候,可以在這個(gè)代理中來(lái)進(jìn)行檢測(cè):
當(dāng)你發(fā)布 service 之后酷愧,service 就會(huì)緩存下來(lái)驾诈,并且無(wú)法再修改。
5.2.4 廣播 service
搞定發(fā)布 service 和 characteristic 之后溶浴,就可以開(kāi)始給正在監(jiān)聽(tīng)的 central 發(fā)廣播了乍迄。可以通過(guò)調(diào)用 CBPeripheralManager 的startAdvertising:方法并傳入字典作為參數(shù)來(lái)進(jìn)行廣播:
上面的代碼中士败,key 只用到了 CBAdvertisementDataServiceUUIDsKey闯两,對(duì)應(yīng)的 value 是包含需要廣播的 service 的 CBUUID 類型數(shù)組。除此之外谅将,還有以下 key:
但是只有 CBAdvertisementDataLocalNameKey 和 CBAdvertisementDataServiceUUIDsKey 才是 peripheral Manager 支持的漾狼。
當(dāng)開(kāi)始廣播時(shí),peripheral Manager 會(huì)回調(diào)peripheralManagerDidStartAdvertising:error:方法饥臂。如果有錯(cuò)或者 service 無(wú)法進(jìn)行廣播逊躁,則可以在該該方法中檢測(cè):
因?yàn)榭臻g的限制,并且還可能有多個(gè) app 在同時(shí)發(fā)起廣播隅熙,所以數(shù)據(jù)廣播基于 best effort(即在接口發(fā)生擁塞時(shí)稽煤,立即丟包,直到業(yè)務(wù)量減星羝荨)酵熙。
廣播服務(wù)在程序掛起時(shí)依然可用。
5.2.5 響應(yīng) central 的讀寫操作
在連接到一個(gè)或多個(gè) central 之后驰坊,peripheral 有可能會(huì)收到讀寫請(qǐng)求匾二。此時(shí),你應(yīng)該根據(jù)請(qǐng)求作出相應(yīng)的響應(yīng)拳芙,接下來(lái)便會(huì)提到這方面的處理假勿。
1、讀取請(qǐng)求
當(dāng)收到讀請(qǐng)求時(shí)态鳖,會(huì)回調(diào)peripheralManager:didReceiveReadRequest:方法转培。該回調(diào)將請(qǐng)求封裝為了 CBATTRequest 對(duì)象,在該對(duì)象中浆竭,包含很多可用的屬性浸须。
其中一種用法是在收到讀請(qǐng)求時(shí)惨寿,可以通過(guò) CBATTRequest 的 characteristic 屬性來(lái)判斷當(dāng)前被讀的 characteristic 是哪一個(gè) characteristic:
匹配上 UUID 之后,接下來(lái)需要確保讀取數(shù)據(jù)的 offset(偏移量)不會(huì)超過(guò) characteristic 數(shù)據(jù)的總長(zhǎng)度:
假設(shè)偏移量驗(yàn)證通過(guò)删窒,下面需要截取 characteristic 中的數(shù)據(jù)裂垦,并賦值給request.value。注意肌索,offset 也要參與計(jì)算:
讀取完成后蕉拢,記著調(diào)用 CBPeripheralManager 的respondToRequest:withResult:方法,告訴 central 已經(jīng)讀取成功了:
如果 UUID 匹配不上诚亚,或者是因?yàn)槠渌驅(qū)е伦x取失敗晕换,那么也應(yīng)該調(diào)用respondToRequest:withResult:方法,并返回失敗原因站宗。官方提供了一個(gè)失敗原因枚舉闸准,可能有你需要的。
2梢灭、寫入請(qǐng)求
寫入請(qǐng)求和讀取請(qǐng)求一樣簡(jiǎn)單夷家。當(dāng) central 想要寫入一個(gè)或多個(gè) characteristic 時(shí),CBPeripheralManager 回調(diào)peripheralManager:didReceiveWriteRequests:敏释。該方法會(huì)獲得一個(gè) CBATTRequest 數(shù)組库快,包含所有寫入請(qǐng)求。當(dāng)確保一切驗(yàn)證沒(méi)問(wèn)題后(與讀取操作驗(yàn)證類似:UUID 與 offset)钥顽,便可以進(jìn)行寫入:
成功后义屏,同樣去調(diào)用respondToRequest:withResult:。但是和讀取操作不同的是耳鸯,讀取只有一個(gè) CBATTRequest,但是寫入是一個(gè) CBATTRequest 數(shù)組膀曾,所以這里直接傳入第一個(gè) request 就行:
因?yàn)槭盏降氖且粋€(gè)請(qǐng)求數(shù)組县爬,所以,當(dāng)他們其中有任何一個(gè)不滿足條件添谊,那就不必再處理下去了财喳,直接調(diào)用respondToRequest:withResult:方法返回相應(yīng)的錯(cuò)誤。
5.2.6 發(fā)送更新數(shù)據(jù)給訂閱了的 central
central 可能會(huì)訂閱了一個(gè)或多個(gè) characteristic斩狱,當(dāng)數(shù)據(jù)更新時(shí)耳高,需要給他們發(fā)送通知。下面就來(lái)詳細(xì)介紹下所踊。
當(dāng) central 訂閱 characteristic 的時(shí)候泌枪,會(huì)回調(diào) CBPeripheralManager 的peripheralManager:central:didSubscribeToCharacteristic:方法:
通過(guò)上面這個(gè)代理,可以用個(gè)數(shù)組來(lái)保存被訂閱的 characteristic秕岛,并在它們的數(shù)據(jù)更新時(shí)碌燕,調(diào)用 CBPeripheralManager 的updateValue:forCharacteristic:onSubscribedCentrals:方法來(lái)告訴 central 有新的數(shù)據(jù):
這個(gè)方法的最后一個(gè)參數(shù)能指定要通知的 central误证。如果參數(shù)為 nil,則表示想所有訂閱了的 central 發(fā)送通知修壕。
同時(shí)updateValue:forCharacteristic:onSubscribedCentrals:方法會(huì)返回一個(gè) BOOL 標(biāo)識(shí)是否發(fā)送成功愈捅。如果發(fā)送隊(duì)列任務(wù)是滿的,則會(huì)返回 NO慈鸠。當(dāng)有可用的空間時(shí)蓝谨,會(huì)回調(diào)peripheralManagerIsReadyToUpdateSubscribers:方法。所以你可以在這個(gè)回調(diào)用調(diào)用updateValue:forCharacteristic:onSubscribedCentrals:重新發(fā)送數(shù)據(jù)青团。
發(fā)送數(shù)據(jù)使用到的是通知譬巫,當(dāng)你更新訂閱的 central 時(shí),應(yīng)該調(diào)用一次updateValue:forCharacteristic:onSubscribedCentrals:壶冒。
因?yàn)?characteristic 數(shù)據(jù)大小的關(guān)系缕题,不是所有的更新都能發(fā)送成功,這種問(wèn)題應(yīng)該由 central 端來(lái)處理胖腾。調(diào)用 CBPeripheral 的readValueForCharacteristic:方法烟零,來(lái)主動(dòng)獲取數(shù)據(jù)。
5.3 請(qǐng)求響應(yīng) - 最佳實(shí)踐
5.3.1 關(guān)于廣播的思考
廣播是 peripheral 的一個(gè)重要操作咸作,接下來(lái)會(huì)講到廣播的正確姿勢(shì)锨阿。
1、注意廣播對(duì)數(shù)據(jù)大小的限制
正如前文提到過(guò)的那樣记罚,廣播是通過(guò)調(diào)用 CBPeripheralManager 的startAdvertising:方法發(fā)起的墅诡。當(dāng)你將要發(fā)送的數(shù)據(jù)打包成字典后,千萬(wàn)要記住數(shù)據(jù)大小是有限制的桐智。
即使廣播可以包含 peripheral 的很多信息末早,但是其實(shí)只需要廣播 peripheral 的名稱和 service 的 UUID 就足夠了。也就是構(gòu)建字典時(shí)说庭,填寫 CBAdvertisementDataLocalNameKey 和 CBAdvertisementDataServiceUUIDsKey 對(duì)應(yīng)的 value 即可然磷,如果使用其他 key,將會(huì)導(dǎo)致錯(cuò)誤刊驴。
當(dāng) app 運(yùn)行在前臺(tái)時(shí)姿搜,有 28 bytes 的空間可用于廣播。如果這 28 bytes 用完了捆憎,則會(huì)在掃描響應(yīng)時(shí)額外分配 10 bytes 的空間舅柜,但這空間只能用于被 CBAdvertisementDataLocalNameKey 修飾的 local name(即在startAdvertising:時(shí)傳入的數(shù)據(jù))。以上提到的空間躲惰,均不包含 2 bytes 的報(bào)文頭致份。被 CBAdvertisementDataServiceUUIDsKey 修飾的 service 的 UUID 數(shù)組數(shù)據(jù),均不會(huì)添加到特殊的 overflow 區(qū)域础拨。并且這些 service 只能被 iOS 設(shè)備發(fā)現(xiàn)知举。當(dāng)程序掛起后瞬沦,local name 和 UUID 都會(huì)被加入到 overflow 區(qū)。
為了保證在有限的空間中雇锡,正確的標(biāo)識(shí)設(shè)備和 service UUID逛钻,請(qǐng)正確構(gòu)建廣播的數(shù)據(jù)。
2锰提、只廣播必要的數(shù)據(jù)
當(dāng) peripheral 想要被發(fā)現(xiàn)時(shí)曙痘,它會(huì)向外界發(fā)送廣播,此時(shí)會(huì)用到設(shè)備的無(wú)線電(當(dāng)然還有電池)立肘。一旦連接成功边坤,central 便能直接從 peripheral 中讀取數(shù)據(jù)了,那么此時(shí)廣播的數(shù)據(jù)將不再有用谅年。所以茧痒,為了減少無(wú)線電的使用、提高手機(jī)性能融蹂、保護(hù)設(shè)備電池旺订,應(yīng)該在被連接后,及時(shí)關(guān)閉廣播超燃。停止廣播調(diào)用 CBPeripheralManager 的stopAdvertising方法即可区拳。
3、手動(dòng)開(kāi)啟廣播
其實(shí)什么時(shí)候應(yīng)該廣播意乓,多數(shù)情況下樱调,用戶比我們更清楚。比如届良,他們知道周圍沒(méi)有開(kāi)著的 BLE 設(shè)備笆凌,那他就不會(huì)把 peripheral 的廣播打開(kāi)。所以提供給用戶一個(gè)手動(dòng)開(kāi)啟廣播的 UI 更為合適士葫。
5.3.2 配置 characteristic
在創(chuàng)建 characteristic 的時(shí)候乞而,就為它設(shè)定了相應(yīng)的 properties、value 和 promissions为障。這些屬性決定了 central 如何和 characteristic 通信晦闰。properties 和 promissions 可能需要根據(jù) app 的需求來(lái)設(shè)置放祟,下來(lái)就來(lái)談?wù)勅绾闻渲?characteristic:
1鳍怨、讓 characteristic 支持通知
之前在 central 的時(shí)候提到過(guò),如果要讀取經(jīng)常變化的 characteristic 的數(shù)據(jù)跪妥,更推薦使用訂閱鞋喇。所以,如果可以眉撵,最好 characteristic 允許訂閱侦香。
如果像下面這樣初始化 characteristic 就是允許讀和訂閱:
2落塑、限制只能配對(duì)的 central 才能訪問(wèn)敏感信息
有些時(shí)候,可能有這樣的需求:需要 service 的一個(gè)或多個(gè) characteristic 的數(shù)據(jù)安全性罐韩。假如有一個(gè)社交媒體的 service憾赁,那么它的 characteristic 可能包含了用戶的姓名、郵箱等私人信息散吵,所以只讓信任的 central 才能訪問(wèn)這些數(shù)據(jù)是很有必要的龙考。
這可以通過(guò)設(shè)置相應(yīng)的 properties 和 promissions 來(lái)達(dá)到效果:
像上面這樣設(shè)置,便能只讓配對(duì)的 central 才能進(jìn)行訂閱矾睦。并且在連接過(guò)程中晦款,Core Bluetooth 還會(huì)自動(dòng)建立安全連接。
在嘗試配對(duì)時(shí)枚冗,兩端都會(huì)彈出警告框缓溅,central 端會(huì)提供 code,peripheral 端必須要輸入該 code 才能配對(duì)成功赁温。成功之后坛怪,peripheral 才會(huì)信任該 central,并允許讀寫數(shù)據(jù)束世。
6酝陈、后臺(tái)運(yùn)行藍(lán)牙服務(wù)
對(duì)于 iOS app 來(lái)說(shuō),知道現(xiàn)在是運(yùn)行在前臺(tái)和后臺(tái)是至關(guān)重要的毁涉。因?yàn)楫?dāng)程序掛起后沉帮,對(duì)資源的使用是相當(dāng)有限的。關(guān)于多任務(wù)的介紹贫堰,可以看app 開(kāi)發(fā)手冊(cè)穆壕。
默認(rèn)情況下,Core Bluetooth 是不會(huì)在后臺(tái)運(yùn)行的(無(wú)論是 central 還是 peripheral)其屏。但你也可以配置在 app 收到事件后喇勋,從掛起狀態(tài)喚醒。即使程序不是完全的支持后臺(tái)模式偎行,也可以要求在有重要事件時(shí)接收系統(tǒng)通知川背。
即使在以上兩種情況下(完全允許后臺(tái)和部分允許后臺(tái)),程序也有可能不會(huì)永遠(yuǎn)掛起蛤袒。在前臺(tái)程序需要更多內(nèi)存時(shí)熄云,被掛起的程序很有可能會(huì)被強(qiáng)制退出,那樣會(huì)斷開(kāi)所有的連接妙真。從 iOS 7 開(kāi)始缴允,能夠先保存狀態(tài)(無(wú)論是 central 還是 peripheral),并在重新打開(kāi) app 時(shí)還原這些狀態(tài)珍德。通過(guò)這一特性练般,就可以做長(zhǎng)時(shí)間操作了矗漾。
6.1 運(yùn)行在前臺(tái)的 app(Foreground-Only)
除非去申請(qǐng)后臺(tái)權(quán)限,否則 app 都是只在前臺(tái)運(yùn)行的薄料,程序在進(jìn)入后臺(tái)不久便會(huì)切換到掛起狀態(tài)敞贡。掛起后,程序?qū)o(wú)法再接收任何藍(lán)牙事件摄职。
對(duì)于 central 來(lái)說(shuō)嫡锌,掛起將無(wú)法再進(jìn)行掃描和搜索 peripheral。對(duì)于 peripheral 來(lái)說(shuō)琳钉,將無(wú)法再發(fā)起廣播势木,central 也無(wú)法再訪問(wèn)動(dòng)態(tài)變化的 characteristic 數(shù)據(jù),訪問(wèn)將返回 error歌懒。
根據(jù)不同情況啦桌,這種機(jī)制會(huì)影響程序在以下幾個(gè)方面的運(yùn)用。你正在讀取 peripheral 的數(shù)據(jù)及皂,結(jié)果程序被掛起了(可能是用戶切換到了另外一個(gè) app)甫男,此時(shí)連接會(huì)被斷開(kāi),但是要直到程序重新喚醒時(shí)验烧,你才知道被斷開(kāi)了板驳。
1、利用連接 Peripheral 時(shí)的選項(xiàng)
Foreground-Only app 在掛起的時(shí)候碍拆,便會(huì)加入到系統(tǒng)的一個(gè)隊(duì)列中若治,當(dāng)程序重新喚醒時(shí),系統(tǒng)便會(huì)通知程序感混。Core Bluetooth 會(huì)在程序中包含 central 時(shí)端幼,給用戶以提示。用戶可根據(jù)提示來(lái)判斷是否要喚醒該 app弧满。
可以利用 central 在連接 peripheral 時(shí)的方法connectPeripheral:options:中的 options 來(lái)觸發(fā)提示:
6.2 Core Bluetooth 后臺(tái)模式
如果你想讓你的 app 能在后臺(tái)運(yùn)行藍(lán)牙婆跑,那么必須在 info.plist 中打開(kāi)藍(lán)牙的后臺(tái)運(yùn)行模式。當(dāng)配置之后庭呜,收到相關(guān)事件便會(huì)從后臺(tái)喚醒滑进。這一機(jī)制對(duì)定期接收數(shù)據(jù)的 app 很有用,比如心率監(jiān)測(cè)器募谎。
下面會(huì)介紹兩種后臺(tái)模式扶关,一種是作為 central 的,一種是作為 peripheral 的近哟,如果 app 兩種角色都有驮审,那則需要開(kāi)啟兩種模式鲫寄。配置即是在 info.plist 中添加UIBackgroundModes key吉执,類型為數(shù)組疯淫,value 則根據(jù)你當(dāng)前角色來(lái)選擇:
bluetooth-central:即 Central。
bluetooth-peripheral:即 Peripheral戳玫。
這個(gè)配置在 Xcode 中熙掺,也可以在 Capabilities 中進(jìn)行配置,而不用直接面對(duì) key-value咕宿。如果要看到 key-value币绩,可以在 info.plist 中打開(kāi)查看。
1府阀、作為 Central 的后臺(tái)模式
如果在 info.plist 中配置了 UIBackgroundModes – bluetooth-central缆镣,那么系統(tǒng)則允許程序在后臺(tái)處理藍(lán)牙相關(guān)事件。在程序進(jìn)入后臺(tái)后试浙,依然能掃描董瞻、搜索 peripheral,并且還能進(jìn)行數(shù)據(jù)交互田巴。當(dāng) CBCentralManagerDelegate 和 CBPeripheralDelegate 的代理方法被調(diào)用時(shí)钠糊,系統(tǒng)將會(huì)喚醒程序。此時(shí)允許你去處理重要的事件壹哺,比如:連接的建立或斷開(kāi)抄伍,peripheral 發(fā)送了數(shù)據(jù),central manager 的狀態(tài)改變管宵。
雖然此時(shí)程序能在后臺(tái)運(yùn)行截珍,但是對(duì) peripheral 的掃描和在前臺(tái)時(shí)是不一樣的。實(shí)際情況是這樣的:
設(shè)置的 CBCentralManagerScanOptionAllowDuplicatesKey 將失效箩朴,并將發(fā)現(xiàn)的多個(gè) peripheral 廣播的事件合并為一個(gè)笛臣。
如果全部的 app 都在后臺(tái)搜索 peripheral,那么每次搜索的時(shí)間間隔會(huì)更大隧饼。這會(huì)導(dǎo)致搜索到 peripheral 的時(shí)間變長(zhǎng)沈堡。
這些相應(yīng)的調(diào)整會(huì)減少無(wú)線電使用,并提升續(xù)航能力燕雁。
2诞丽、作為 peripheral 的后臺(tái)模式
作為 peripheral 時(shí),如果需要支持后臺(tái)模式拐格,則在 info.plist 中配置 UIBackgroundModes – bluetooth-peripheral僧免。配置后,系統(tǒng)會(huì)在有讀寫請(qǐng)求和訂閱事件時(shí)捏浊,喚醒程序懂衩。
在后臺(tái),除了允許處理讀寫請(qǐng)求和訂閱事件外,Core Bluetooth 框架還允許 peripheral 發(fā)出廣播浊洞。同樣牵敷,廣播事件也有前后臺(tái)區(qū)別。在后臺(tái)發(fā)起時(shí)是這樣的:
CBAdvertisementDataLocalNameKey 將失效法希,在廣播時(shí)枷餐,廣播數(shù)據(jù)將不再包含 peripheral 的名字。
被 CBAdvertisementDataServiceUUIDsKey 修飾的 UUID 數(shù)組將會(huì)被放到 overflow 區(qū)域中苫亦,意味著只能被明確標(biāo)識(shí)了搜索 service UUID 的 iOS 設(shè)備找到毛肋。
如果所有 app 都在后臺(tái)發(fā)起廣播,那么發(fā)起頻率會(huì)降低屋剑。
6.3 巧妙的使用后臺(tái)模式
雖然程序支持一個(gè)或多個(gè) Core Bluetooth 服務(wù)在后臺(tái)運(yùn)行润匙,但也不要濫用。因?yàn)樗{(lán)牙服務(wù)會(huì)占用 iOS 設(shè)備的無(wú)線電資源唉匾,這也會(huì)間接影響到續(xù)航能力趁桃,所以盡可能少的去使用后臺(tái)模式。app 會(huì)喚醒程序并處理相關(guān)事務(wù)肄鸽,完成后又會(huì)快速回到掛起狀態(tài)卫病。
無(wú)論是 central 還是 peripheral,要支持后臺(tái)模式都應(yīng)該遵循以下幾點(diǎn):
程序應(yīng)該提供 UI典徘,讓用戶決定是否要在后臺(tái)運(yùn)行蟀苛。
一旦程序在后臺(tái)被喚醒,程序只有 10s 的時(shí)間來(lái)處理相關(guān)事務(wù)逮诲。所以應(yīng)該在程序再次掛起前處理完事件帜平。后臺(tái)運(yùn)行的太耗時(shí)的程序會(huì)被系統(tǒng)強(qiáng)制關(guān)閉進(jìn)程。
處理無(wú)關(guān)的事件不應(yīng)該喚醒程序梅鹦。
和后臺(tái)運(yùn)行的更多介紹裆甩,可以查看App Programming Guide for iOS。
6.4 處理常駐后臺(tái)任務(wù)
某些 app 可能需要 Core Bluetooth 常駐后臺(tái)齐唆,比如嗤栓,一款用 BLE 技術(shù)和門鎖通信的 app。當(dāng)用戶離開(kāi)時(shí)箍邮,自動(dòng)上鎖茉帅,回來(lái)時(shí),自動(dòng)開(kāi)鎖(即使程序運(yùn)行在后臺(tái))锭弊。當(dāng)用戶離開(kāi)時(shí)堪澎,可能已超出藍(lán)牙連接范圍,所以沒(méi)辦法給鎖通信味滞。此時(shí)可以調(diào)用 CBCentralManager 的connectPeripheral:options:方法樱蛤,因?yàn)樵摲椒](méi)有超時(shí)設(shè)置钮呀,所以,在用戶返回時(shí)昨凡,可以重新連接到鎖爽醋。
但是還有這樣的情形:用戶可能離開(kāi)家好幾天,并且在這期間土匀,程序已經(jīng)被完全退出了。那么用戶再次回家時(shí)形用,就不能自動(dòng)開(kāi)鎖就轧。對(duì)于這類 app 來(lái)說(shuō),常駐后臺(tái)操作就顯得尤為重要田度。
1妒御、狀態(tài)保存與恢復(fù)
因?yàn)闋顟B(tài)的保存和恢復(fù) Core Bluetooth 都為我們封裝好了,所以我們只需要選擇是否需要這個(gè)特性即可镇饺。系統(tǒng)會(huì)保存當(dāng)前 central manager 或 peripheral manager乎莉,并且繼續(xù)執(zhí)行藍(lán)牙相關(guān)事件(即使程序已經(jīng)不再運(yùn)行)。一旦事件執(zhí)行完畢奸笤,系統(tǒng)會(huì)在后臺(tái)重啟 app惋啃,這時(shí)你有機(jī)會(huì)去存儲(chǔ)當(dāng)前狀態(tài),并且處理一些事物监右。在之前提到的 “門鎖” 的例子中边灭,系統(tǒng)會(huì)監(jiān)視連接請(qǐng)求,并在centralManager:didConnectPeripheral:回調(diào)時(shí)健盒,重啟 app绒瘦,在用戶回家后,連接操作結(jié)束扣癣。
Core Bluetooth 的狀態(tài)保存與恢復(fù)在設(shè)備作為 central惰帽、peripheral 或者這兩種角色時(shí),都可用父虑。在設(shè)備作為 central 并添加了狀態(tài)保存與恢復(fù)支持后些举,如果 app 被強(qiáng)行關(guān)閉進(jìn)程筑舅,系統(tǒng)會(huì)自動(dòng)保存 central manager 的狀態(tài)(如果 app 有多個(gè) central manager,你可以選擇哪一個(gè)需要系統(tǒng)保存)。
對(duì)于 CBCentralManager膜毁,系統(tǒng)會(huì)保存以下信息:
central 準(zhǔn)備連接或已經(jīng)連接的 peripheral
central 需要掃描的 service(包括掃描時(shí),配置的 options)
central 訂閱的 characteristic
對(duì)于 peripheral 來(lái)說(shuō)躏升,情況也差不多溯革。系統(tǒng)對(duì) CBPeripheralManager 的處理方式如下:
peripheral 在廣播的數(shù)據(jù)
peripheral 存入的 service 和 characteristic 的樹形結(jié)構(gòu)
已經(jīng)被 central 訂閱了的 characteristic 的值
當(dāng)系統(tǒng)在后臺(tái)重新加載程序后(可能是因?yàn)檎业搅艘业?peripheral),你可以重新實(shí)例化 central manager 或 peripheral 并恢復(fù)他們的狀態(tài)膳殷。
2操骡、添加狀態(tài)存儲(chǔ)和恢復(fù)支持
狀態(tài)的存儲(chǔ)和恢復(fù)功能在 Core Bluetooth 中是可選的九火,添加支持可以通過(guò)以下幾個(gè)步驟:
(必須)在初始化 central manager 或 peripheral manager 時(shí),要選擇是否需要支持册招。會(huì)在文后的【3岔激、選擇支持存儲(chǔ)和恢復(fù)】中介紹。
(必須)在系統(tǒng)從后臺(tái)重新加載程序時(shí)是掰,重新初始化 central manager 或 peripheral manager虑鼎。會(huì)在文后的【4、重新初始化 central manager 和 peripheral manager】中介紹键痛。
(必須)實(shí)現(xiàn)恢復(fù)狀態(tài)相關(guān)的代理方法炫彩。會(huì)在文后的【5、實(shí)現(xiàn)恢復(fù)狀態(tài)的代理方法】中介紹絮短。
(可選)更新 central manager 或 peripheral manager 的初始化過(guò)程江兢。會(huì)在文后的【6、更新 manager 初始化過(guò)程】中介紹丁频。
3杉允、選擇支持存儲(chǔ)和恢復(fù)
如果要支持存儲(chǔ)和恢復(fù),則需要在初始化 manager 的時(shí)候給一個(gè) restoration identifier席里。restoration identifier 是 string 類型叔磷,并標(biāo)識(shí)了 app 中的 central manager 或 peripheral manager。這個(gè) string 很重要奖磁,它將會(huì)告訴 Core Bluetooth 需要存儲(chǔ)狀態(tài)世澜,畢竟 Core Bluetooth 恢復(fù)有 identifier 的對(duì)象。
例如署穗,在 central 端寥裂,要想支持該特性,可以在調(diào)用 CBCentralManager 的初始化方法時(shí)案疲,配置 CBCentralManagerOptionRestoreIdentifierKey:
雖然以上代碼沒(méi)有展示出來(lái)封恰,其實(shí)在 peripheral manager 中要設(shè)置 identifier 也是這樣的。只是在初始化時(shí)褐啡,將 key 改成了 CBPeripheralManagerOptionRestoreIdentifierKey诺舔。
因?yàn)槌绦蚩梢杂卸鄠€(gè) CBCentralManager 和 CBPeripheralManager,所以要確保每個(gè) identifier 都是唯一的备畦。
4低飒、重新初始化 central manager 和 peripheral manager
當(dāng)系統(tǒng)重新在后臺(tái)加載程序時(shí),首先需要做的即根據(jù)存儲(chǔ)的 identifier懂盐,重新初始化 central manager 或 peripheral manager褥赊。如果你只有一個(gè) manager,并且 manager 存在于 app 生命周期中莉恼,那這個(gè)步驟就不需要做什么了拌喉。
如果 app 中包含多個(gè) manager速那,或者 manager 不是在整個(gè) app 生命周期中都存在的,那 app 就必須要區(qū)分你要重新初始化哪個(gè) manager 了尿背。你可以通過(guò)從 app delegate 中的application:didFinishLaunchingWithOptions:中取出 key(UIApplicationLaunchOptionsBluetoothCentralsKey 或 UIApplicationLaunchOptionsBluetoothPeripheralsKey)中的 value(數(shù)組類型)來(lái)得到程序退出之前存儲(chǔ)的 manager identifier 列表:
拿到這個(gè)列表后端仰,就可以通過(guò)循環(huán)來(lái)重新初始化所有的 manager 了。
5田藐、實(shí)現(xiàn)恢復(fù)狀態(tài)的代理方法
在重新初始化 manager 之后荔烧,接下來(lái)需要同步 Core Bluetooth 存儲(chǔ)的他們的狀態(tài)。要想弄清楚在程序被退出時(shí)都在做些什么汽久,就需要正確的實(shí)現(xiàn)代理方法鹤竭。對(duì)于 central manager 來(lái)說(shuō),需要實(shí)現(xiàn)centralManager:willRestoreState:回窘;對(duì)于 peripheral manager 來(lái)說(shuō)诺擅,需要實(shí)現(xiàn)peripheralManager:willRestoreState:市袖。
注意:如果選擇存儲(chǔ)和恢復(fù)狀態(tài)啡直,當(dāng)系統(tǒng)在后臺(tái)重新加載程序時(shí),首先調(diào)用的方法是centralManager:willRestoreState:或peripheralManager:willRestoreState:苍碟。如果沒(méi)有選擇存儲(chǔ)的恢復(fù)狀態(tài)(或者喚醒時(shí)沒(méi)有什么內(nèi)容需要恢復(fù))酒觅,那么首先調(diào)用的方法是centralManagerDidUpdateState:或peripheralManagerDidUpdateState:。
無(wú)論是以上哪種代理方法微峰,最后一個(gè)參數(shù)都是一個(gè)包含程序退出前狀態(tài)的字典舷丹。字典中,可用的 key 蜓肆,central 端有:
peripheral 端有:
要恢復(fù) central manager 的狀態(tài)颜凯,可以用centralManager:willRestoreState:返回字典中的 key 來(lái)得到。假如說(shuō) central manager 有想要或者已經(jīng)連接的 peripheral仗扬,那么可以通過(guò) CBCentralManagerRestoredStatePeripheralsKey 對(duì)應(yīng)得到的 peripheral(CBPeripheral 對(duì)象)數(shù)組來(lái)得到症概。
具體要對(duì)拿到的 peripheral 數(shù)組做什么就要根據(jù)需求來(lái)了。如果這是個(gè) central manager 搜索到的 peripheral 數(shù)組早芭,那就可以存儲(chǔ)這個(gè)數(shù)組的引用彼城,并且開(kāi)始建立連接了(注意給這些 peripheral 設(shè)置代理,否則連接后不會(huì)走 peripheral 的代理方法)退个。
恢復(fù) peripheral manager 的狀態(tài)和 central manager 的方式類似募壕,就只是把代理方法換成了peripheralManager:willRestoreState:,并且使用對(duì)應(yīng)的 key 即可语盈。
6舱馅、更新 manager 初始化過(guò)程
在實(shí)現(xiàn)了全部的必須步驟后,你可能想要更新 manager 的初始化過(guò)程刀荒。雖然這是個(gè)可選的操作习柠,但是它對(duì)確保各種操作能正常進(jìn)行尤為重要匀谣。假如,你的應(yīng)用在 central 和 peripheral 做數(shù)據(jù)交互時(shí)资溃,被強(qiáng)制退出了武翎。即使 app 最后恢復(fù)狀態(tài)時(shí),找到了這個(gè) peripheral溶锭,那你也不知道 central 和這個(gè) peripheral 當(dāng)時(shí)的具體狀態(tài)宝恶。但其實(shí)我們?cè)诨謴?fù)時(shí),是想恢復(fù)到程序被強(qiáng)制退出前的那一步趴捅。
這個(gè)需求垫毙,可以在代理方法centralManagerDidUpdateState:中,通過(guò)發(fā)現(xiàn)恢復(fù)的 peripheral 是否之前已經(jīng)成功連接來(lái)實(shí)現(xiàn):
上面的代碼描述了拱绑,當(dāng)系統(tǒng)在完成搜索 service 之后才退出的程序综芥,可以通過(guò)調(diào)用discoverServices:方法來(lái)恢復(fù) peripheral 的數(shù)據(jù)。如果 app 成功搜索到 service猎拨,你可以是否能搜索到需要的 characteristic(或者已經(jīng)訂閱過(guò))膀藐。通過(guò)更新初始化過(guò)程,可以確保在正確的時(shí)間點(diǎn)红省,調(diào)用正確的方法额各。
7、第三方框架
iOS 藍(lán)牙開(kāi)發(fā)中常用的第三方框架
作者:xiaofu666
鏈接:http://www.reibang.com/p/42b7c318ee24
來(lái)源:簡(jiǎn)書
著作權(quán)歸作者所有吧恃。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)虾啦,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
轉(zhuǎn)載聲明痕寓,僅為個(gè)人學(xué)習(xí)使用傲醉,翻閱原作者的上一篇,發(fā)現(xiàn)不能閱讀了呻率,所以就把本篇轉(zhuǎn)載一下硬毕。