回去查看 iOS - Bluetooth 藍(lán)牙介紹(上)
5、外設(shè)模式的使用
5.1 App 作為外設(shè)被連接的實現(xiàn)
-
1、啟動一個 Peripheral 管理對象
- 打開 peripheralManager,設(shè)置 peripheralManager 的委托黄虱。
-
2芬位、配置本地 Peripheral,設(shè)置服務(wù)茎用、特性揪阶、描述昌抠、權(quán)限等等
創(chuàng)建 characteristics,characteristics 的 description鲁僚,創(chuàng)建 service炊苫,把 characteristics 添加到 service 中,再把 service 添加到 peripheralManager 中冰沙。
當(dāng) peripheral 成功打開后侨艾,才可以配置 service 和 characteristics。這里創(chuàng)建的 service 和 characteristics 對象是 CBMutableCharacteristic 和 CBMutableService拓挥。他們的區(qū)別就像 NSArray 和 NSMutableArray 區(qū)別類似唠梨。我們先創(chuàng)建 characteristics 和 description,description 是 characteristics 的描述侥啤,描述分很多種当叭,常用的就是 CBUUIDCharacteristicUserDescriptionString茬故。
-
3、開啟廣播 advertising
- 添加發(fā)送廣播后悔調(diào)用代理的 peripheralManagerDidStartAdvertising:error: 方法蚁鳖。
4磺芭、設(shè)置處理訂閱、取消訂閱醉箕、讀 characteristic钾腺、寫 characteristic 的委托方法
5.2 作為 Peripheral 時的請求響應(yīng)
5.2.1 初始化 CBPeripheralManager
將設(shè)備作為 peripheral,第一步就是初始化 CBPeripheralManager 對象讥裤》虐簦可以通過調(diào)用 CBPeripheralManager 的
initWithDelegate:queue:options:
方法來進(jìn)行初始化:上面的幾個參數(shù)中,將 self 設(shè)為代理來接收相關(guān)回調(diào)坞琴,queue 為 nil 表示在主線程哨查。
當(dāng)你調(diào)用上面這方法后,便會回調(diào)
peripheralManagerDidUpdateState:
剧辐。所以在此之前,你需要先遵循CBPeripheralManagerDelegate
邮府。這個代理方法能獲取當(dāng)前 iOS 設(shè)備能否作為 peripheral荧关。
5.2.2 配置 service 和 characteristic
-
就像之前講到的一樣,peripheral 數(shù)據(jù)庫是一個樹形結(jié)構(gòu)褂傀。
所以在創(chuàng)建 peripheral 的時候忍啤,也要像這種樹形結(jié)構(gòu)一樣,將 service 和 characteristic 裝進(jìn)去仙辟。在此之前同波,我們需要做的是學(xué)會如何標(biāo)識 service 和 characteristic。
-
1叠国、使用 UUID 來標(biāo)識 service 和 characteristic
- service 和 characteristic 都通過 128 位的 UUID 來進(jìn)行標(biāo)識未檩,Core Bluetooth 將 UUID 封裝為了 CBUUID 。關(guān)于詳細(xì) UUID 的介紹粟焊,請參考上面的 4.3.1 CBUUID 講解冤狡。
-
2、為自定義的 service 和 characteristic 創(chuàng)建 UUID
你的 service 或者 characteristic 的 UUID 并沒有公共的 UUID项棠,這時你需要創(chuàng)建自己的 UUID悲雳。
使用命令行的
uuidgen
能很容易的生成 UUID。首先打開終端香追,為你的每一個 service 和 characteristic 創(chuàng)建 UUID合瓢。在終端輸入uuidgen
然后回車,具體如下:可以通過
UUIDWithString:
方法透典,將 UUID 生成 CBUUID 對象晴楔。
-
3顿苇、構(gòu)建 service 和 characteristic 樹形結(jié)構(gòu)
在將 UUID 打包為 CBUUID 之后,就可以創(chuàng)建 CBMutableService 和 CBMutableCharacteristic 并把他們組成一個樹形結(jié)構(gòu)了滥崩。創(chuàng)建 CBMutableCharacteristic 對象可以通過該類的
initWithType:properties:value:permissions:
方法:創(chuàng)建 characteristic 的時候岖圈,就為他設(shè)置了 properties 和 permissions。這兩個屬性分別定義了 characteristic 的可讀寫狀態(tài)和 central 連接后是否能訂閱钙皮。上面這種初始化方式蜂科,代表著 characteristic 可讀。更多的選項短条,可以去看看 CBMutableCharacteristic Class Reference导匣。
如果給 characteristic 設(shè)置了 value 參數(shù),那么這個 value 會被緩存茸时,并且 properties 和 permissions 會自動設(shè)置為可讀贡定。如果想要 characteristic 可寫,或者在其生命周期會改變它的值可都,那需要將 value 設(shè)置為 nil缓待。這樣的話,就會動態(tài)的來處理 value 渠牲。
現(xiàn)在已經(jīng)成功的創(chuàng)建了 characteristic旋炒,下一步就是創(chuàng)建一個 service,并將它們構(gòu)成樹形結(jié)構(gòu)签杈。調(diào)用 CBMutableService 的
initWithType:primary:
方法來初始化 service:第二個參數(shù) primary 設(shè)置為 YES 表示該 service 為 primary service(主服務(wù))瘫镇,與 secondary service(次服務(wù))相對。primary service 描述了設(shè)備的主要功能答姥,并且能包含其他 service铣除。secondary service 描述的是引用它的那個 service 的相關(guān)信息。比如鹦付,一個心率監(jiān)測器尚粘,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)之后弯囊,接下來便需要將這結(jié)構(gòu)加入設(shè)備的數(shù)據(jù)庫黍少。這一操作 Core Bluetooth 已經(jīng)封裝好了病曾,調(diào)用 CBPeripheralManager 的
addService:
方法即可:當(dāng)調(diào)用以上方法時,便會回調(diào) CBPeripheralDelegate 的
peripheralManager:didAddService:error:
回調(diào)钳降。當(dāng)有錯誤厚宰,或者當(dāng)前 service 不能發(fā)布的時候,可以在這個代理中來進(jìn)行檢測:當(dāng)你發(fā)布 service 之后,service 就會緩存下來铲觉,并且無法再修改澈蝙。
5.2.4 廣播 service
搞定發(fā)布 service 和 characteristic 之后,就可以開始給正在監(jiān)聽的 central 發(fā)廣播了撵幽〉朴可以通過調(diào)用 CBPeripheralManager 的
startAdvertising:
方法并傳入字典作為參數(shù)來進(jìn)行廣播:上面的代碼中,key 只用到了 CBAdvertisementDataServiceUUIDsKey盐杂,對應(yīng)的 value 是包含需要廣播的 service 的 CBUUID 類型數(shù)組逗载。除此之外,還有以下 key:
但是只有 CBAdvertisementDataLocalNameKey 和 CBAdvertisementDataServiceUUIDsKey 才是 peripheral Manager 支持的链烈。
當(dāng)開始廣播時厉斟,peripheral Manager 會回調(diào)
peripheralManagerDidStartAdvertising:error:
方法。如果有錯或者 service 無法進(jìn)行廣播强衡,則可以在該該方法中檢測:因為空間的限制擦秽,并且還可能有多個 app 在同時發(fā)起廣播,所以數(shù)據(jù)廣播基于 best effort(即在接口發(fā)生擁塞時漩勤,立即丟包感挥,直到業(yè)務(wù)量減小)越败。
廣播服務(wù)在程序掛起時依然可用链快。
5.2.5 響應(yīng) central 的讀寫操作
在連接到一個或多個 central 之后,peripheral 有可能會收到讀寫請求眉尸。此時,你應(yīng)該根據(jù)請求作出相應(yīng)的響應(yīng)巨双,接下來便會提到這方面的處理噪猾。
-
1、讀取請求
當(dāng)收到讀請求時筑累,會回調(diào)
peripheralManager:didReceiveReadRequest:
方法袱蜡。該回調(diào)將請求封裝為了 CBATTRequest 對象,在該對象中慢宗,包含很多可用的屬性坪蚁。其中一種用法是在收到讀請求時,可以通過 CBATTRequest 的 characteristic 屬性來判斷當(dāng)前被讀的 characteristic 是哪一個 characteristic:
匹配上 UUID 之后镜沽,接下來需要確保讀取數(shù)據(jù)的 offset(偏移量)不會超過 characteristic 數(shù)據(jù)的總長度:
假設(shè)偏移量驗證通過敏晤,下面需要截取 characteristic 中的數(shù)據(jù),并賦值給
request.value
缅茉。注意嘴脾,offset 也要參與計算:讀取完成后,記著調(diào)用 CBPeripheralManager 的
respondToRequest:withResult:
方法,告訴 central 已經(jīng)讀取成功了:如果 UUID 匹配不上译打,或者是因為其他原因?qū)е伦x取失敗耗拓,那么也應(yīng)該調(diào)用
respondToRequest:withResult:
方法,并返回失敗原因奏司。官方提供了一個失敗原因枚舉乔询,可能有你需要的。
-
2韵洋、寫入請求
寫入請求和讀取請求一樣簡單竿刁。當(dāng) central 想要寫入一個或多個 characteristic 時,CBPeripheralManager 回調(diào)
peripheralManager:didReceiveWriteRequests:
麻献。該方法會獲得一個 CBATTRequest 數(shù)組们妥,包含所有寫入請求。當(dāng)確保一切驗證沒問題后(與讀取操作驗證類似:UUID 與 offset)勉吻,便可以進(jìn)行寫入:成功后监婶,同樣去調(diào)用
respondToRequest:withResult:
。但是和讀取操作不同的是齿桃,讀取只有一個 CBATTRequest惑惶,但是寫入是一個 CBATTRequest 數(shù)組,所以這里直接傳入第一個 request 就行:因為收到的是一個請求數(shù)組短纵,所以带污,當(dāng)他們其中有任何一個不滿足條件,那就不必再處理下去了香到,直接調(diào)用
respondToRequest:withResult:
方法返回相應(yīng)的錯誤鱼冀。
5.2.6 發(fā)送更新數(shù)據(jù)給訂閱了的 central
central 可能會訂閱了一個或多個 characteristic,當(dāng)數(shù)據(jù)更新時悠就,需要給他們發(fā)送通知千绪。下面就來詳細(xì)介紹下。
當(dāng) central 訂閱 characteristic 的時候梗脾,會回調(diào) CBPeripheralManager 的
peripheralManager:central:didSubscribeToCharacteristic:
方法:通過上面這個代理荸型,可以用個數(shù)組來保存被訂閱的 characteristic,并在它們的數(shù)據(jù)更新時炸茧,調(diào)用 CBPeripheralManager 的
updateValue:forCharacteristic:onSubscribedCentrals:
方法來告訴 central 有新的數(shù)據(jù):這個方法的最后一個參數(shù)能指定要通知的 central瑞妇。如果參數(shù)為 nil,則表示想所有訂閱了的 central 發(fā)送通知梭冠。
同時
updateValue:forCharacteristic:onSubscribedCentrals:
方法會返回一個 BOOL 標(biāo)識是否發(fā)送成功辕狰。如果發(fā)送隊列任務(wù)是滿的,則會返回 NO妈嘹。當(dāng)有可用的空間時柳琢,會回調(diào)peripheralManagerIsReadyToUpdateSubscribers:
方法。所以你可以在這個回調(diào)用調(diào)用updateValue:forCharacteristic:onSubscribedCentrals:
重新發(fā)送數(shù)據(jù)。發(fā)送數(shù)據(jù)使用到的是通知柬脸,當(dāng)你更新訂閱的 central 時他去,應(yīng)該調(diào)用一次
updateValue:forCharacteristic:onSubscribedCentrals:
。因為 characteristic 數(shù)據(jù)大小的關(guān)系倒堕,不是所有的更新都能發(fā)送成功灾测,這種問題應(yīng)該由 central 端來處理。調(diào)用 CBPeripheral 的
readValueForCharacteristic:
方法垦巴,來主動獲取數(shù)據(jù)媳搪。
5.3 請求響應(yīng) - 最佳實踐
5.3.1 關(guān)于廣播的思考
廣播是 peripheral 的一個重要操作,接下來會講到廣播的正確姿勢骤宣。
-
1秦爆、注意廣播對數(shù)據(jù)大小的限制
正如前文提到過的那樣,廣播是通過調(diào)用 CBPeripheralManager 的
startAdvertising:
方法發(fā)起的憔披。當(dāng)你將要發(fā)送的數(shù)據(jù)打包成字典后等限,千萬要記住數(shù)據(jù)大小是有限制的。即使廣播可以包含 peripheral 的很多信息芬膝,但是其實只需要廣播 peripheral 的名稱和 service 的 UUID 就足夠了望门。也就是構(gòu)建字典時,填寫 CBAdvertisementDataLocalNameKey 和 CBAdvertisementDataServiceUUIDsKey 對應(yīng)的 value 即可锰霜,如果使用其他 key筹误,將會導(dǎo)致錯誤。
當(dāng) app 運(yùn)行在前臺時癣缅,有 28 bytes 的空間可用于廣播厨剪。如果這 28 bytes 用完了,則會在掃描響應(yīng)時額外分配 10 bytes 的空間友存,但這空間只能用于被 CBAdvertisementDataLocalNameKey 修飾的 local name(即在
startAdvertising:
時傳入的數(shù)據(jù))丽惶。以上提到的空間,均不包含 2 bytes 的報文頭爬立。被 CBAdvertisementDataServiceUUIDsKey 修飾的 service 的 UUID 數(shù)組數(shù)據(jù),均不會添加到特殊的 overflow 區(qū)域万哪。并且這些 service 只能被 iOS 設(shè)備發(fā)現(xiàn)侠驯。當(dāng)程序掛起后,local name 和 UUID 都會被加入到 overflow 區(qū)奕巍。為了保證在有限的空間中吟策,正確的標(biāo)識設(shè)備和 service UUID,請正確構(gòu)建廣播的數(shù)據(jù)的止。
-
2檩坚、只廣播必要的數(shù)據(jù)
- 當(dāng) peripheral 想要被發(fā)現(xiàn)時,它會向外界發(fā)送廣播,此時會用到設(shè)備的無線電(當(dāng)然還有電池)匾委。一旦連接成功拖叙,central 便能直接從 peripheral 中讀取數(shù)據(jù)了,那么此時廣播的數(shù)據(jù)將不再有用赂乐。所以薯鳍,為了減少無線電的使用、提高手機(jī)性能挨措、保護(hù)設(shè)備電池挖滤,應(yīng)該在被連接后,及時關(guān)閉廣播浅役。停止廣播調(diào)用 CBPeripheralManager 的
stopAdvertising
方法即可斩松。
- 當(dāng) peripheral 想要被發(fā)現(xiàn)時,它會向外界發(fā)送廣播,此時會用到設(shè)備的無線電(當(dāng)然還有電池)匾委。一旦連接成功拖叙,central 便能直接從 peripheral 中讀取數(shù)據(jù)了,那么此時廣播的數(shù)據(jù)將不再有用赂乐。所以薯鳍,為了減少無線電的使用、提高手機(jī)性能挨措、保護(hù)設(shè)備電池挖滤,應(yīng)該在被連接后,及時關(guān)閉廣播浅役。停止廣播調(diào)用 CBPeripheralManager 的
-
3、手動開啟廣播
- 其實什么時候應(yīng)該廣播觉既,多數(shù)情況下惧盹,用戶比我們更清楚。比如奋救,他們知道周圍沒有開著的 BLE 設(shè)備岭参,那他就不會把 peripheral 的廣播打開。所以提供給用戶一個手動開啟廣播的 UI 更為合適尝艘。
5.3.2 配置 characteristic
在創(chuàng)建 characteristic 的時候演侯,就為它設(shè)定了相應(yīng)的 properties、value 和 promissions背亥。這些屬性決定了 central 如何和 characteristic 通信秒际。properties 和 promissions 可能需要根據(jù) app 的需求來設(shè)置,下來就來談?wù)勅绾闻渲?characteristic:
-
1狡汉、讓 characteristic 支持通知
之前在 central 的時候提到過娄徊,如果要讀取經(jīng)常變化的 characteristic 的數(shù)據(jù),更推薦使用訂閱盾戴。所以寄锐,如果可以,最好 characteristic 允許訂閱尖啡。
如果像下面這樣初始化 characteristic 就是允許讀和訂閱:
-
2橄仆、限制只能配對的 central 才能訪問敏感信息
有些時候,可能有這樣的需求:需要 service 的一個或多個 characteristic 的數(shù)據(jù)安全性衅斩。假如有一個社交媒體的 service盆顾,那么它的 characteristic 可能包含了用戶的姓名、郵箱等私人信息畏梆,所以只讓信任的 central 才能訪問這些數(shù)據(jù)是很有必要的您宪。
這可以通過設(shè)置相應(yīng)的 properties 和 promissions 來達(dá)到效果:
像上面這樣設(shè)置奈懒,便能只讓配對的 central 才能進(jìn)行訂閱。并且在連接過程中宪巨,Core Bluetooth 還會自動建立安全連接磷杏。
在嘗試配對時,兩端都會彈出警告框揖铜,central 端會提供 code茴丰,peripheral 端必須要輸入該 code 才能配對成功。成功之后天吓,peripheral 才會信任該 central贿肩,并允許讀寫數(shù)據(jù)。
6龄寞、后臺運(yùn)行藍(lán)牙服務(wù)
對于 iOS app 來說汰规,知道現(xiàn)在是運(yùn)行在前臺和后臺是至關(guān)重要的。因為當(dāng)程序掛起后物邑,對資源的使用是相當(dāng)有限的溜哮。關(guān)于多任務(wù)的介紹,可以看 app 開發(fā)手冊色解。
默認(rèn)情況下茂嗓,Core Bluetooth 是不會在后臺運(yùn)行的(無論是 central 還是 peripheral)。但你也可以配置在 app 收到事件后科阎,從掛起狀態(tài)喚醒述吸。即使程序不是完全的支持后臺模式,也可以要求在有重要事件時接收系統(tǒng)通知锣笨。
即使在以上兩種情況下(完全允許后臺和部分允許后臺)蝌矛,程序也有可能不會永遠(yuǎn)掛起。在前臺程序需要更多內(nèi)存時错英,被掛起的程序很有可能會被強(qiáng)制退出入撒,那樣會斷開所有的連接。從 iOS 7 開始椭岩,能夠先保存狀態(tài)(無論是 central 還是 peripheral)茅逮,并在重新打開 app 時還原這些狀態(tài)。通過這一特性判哥,就可以做長時間操作了氮唯。
6.1 運(yùn)行在前臺的 app(Foreground-Only)
除非去申請后臺權(quán)限,否則 app 都是只在前臺運(yùn)行的姨伟,程序在進(jìn)入后臺不久便會切換到掛起狀態(tài)。掛起后豆励,程序?qū)o法再接收任何藍(lán)牙事件夺荒。
對于 central 來說瞒渠,掛起將無法再進(jìn)行掃描和搜索 peripheral。對于 peripheral 來說技扼,將無法再發(fā)起廣播伍玖,central 也無法再訪問動態(tài)變化的 characteristic 數(shù)據(jù),訪問將返回 error剿吻。
根據(jù)不同情況窍箍,這種機(jī)制會影響程序在以下幾個方面的運(yùn)用。你正在讀取 peripheral 的數(shù)據(jù)丽旅,結(jié)果程序被掛起了(可能是用戶切換到了另外一個 app)椰棘,此時連接會被斷開,但是要直到程序重新喚醒時榄笙,你才知道被斷開了邪狞。
-
1、利用連接 Peripheral 時的選項
Foreground-Only app 在掛起的時候茅撞,便會加入到系統(tǒng)的一個隊列中帆卓,當(dāng)程序重新喚醒時,系統(tǒng)便會通知程序米丘。Core Bluetooth 會在程序中包含 central 時剑令,給用戶以提示。用戶可根據(jù)提示來判斷是否要喚醒該 app拄查。
可以利用 central 在連接 peripheral 時的方法
connectPeripheral:options:
中的 options 來觸發(fā)提示:
6.2 Core Bluetooth 后臺模式
如果你想讓你的 app 能在后臺運(yùn)行藍(lán)牙吁津,那么必須在 info.plist 中打開藍(lán)牙的后臺運(yùn)行模式。當(dāng)配置之后靶累,收到相關(guān)事件便會從后臺喚醒腺毫。這一機(jī)制對定期接收數(shù)據(jù)的 app 很有用,比如心率監(jiān)測器挣柬。
-
下面會介紹兩種后臺模式潮酒,一種是作為 central 的,一種是作為 peripheral 的邪蛔,如果 app 兩種角色都有急黎,那則需要開啟兩種模式。配置即是在 info.plist 中添加
UIBackgroundModes key
侧到,類型為數(shù)組勃教,value 則根據(jù)你當(dāng)前角色來選擇:-
bluetooth-central
:即 Central。 -
bluetooth-peripheral
:即 Peripheral匠抗。
-
這個配置在 Xcode 中故源,也可以在 Capabilities 中進(jìn)行配置,而不用直接面對 key-value汞贸。如果要看到 key-value绳军,可以在 info.plist 中打開查看印机。
-
1、作為 Central 的后臺模式
如果在 info.plist 中配置了 UIBackgroundModes – bluetooth-central门驾,那么系統(tǒng)則允許程序在后臺處理藍(lán)牙相關(guān)事件射赛。在程序進(jìn)入后臺后,依然能掃描奶是、搜索 peripheral楣责,并且還能進(jìn)行數(shù)據(jù)交互。當(dāng) CBCentralManagerDelegate 和 CBPeripheralDelegate 的代理方法被調(diào)用時聂沙,系統(tǒng)將會喚醒程序秆麸。此時允許你去處理重要的事件,比如:連接的建立或斷開逐纬,peripheral 發(fā)送了數(shù)據(jù)蛔屹,central manager 的狀態(tài)改變。
-
雖然此時程序能在后臺運(yùn)行豁生,但是對 peripheral 的掃描和在前臺時是不一樣的兔毒。實際情況是這樣的:
設(shè)置的 CBCentralManagerScanOptionAllowDuplicatesKey 將失效,并將發(fā)現(xiàn)的多個 peripheral 廣播的事件合并為一個甸箱。
如果全部的 app 都在后臺搜索 peripheral育叁,那么每次搜索的時間間隔會更大。這會導(dǎo)致搜索到 peripheral 的時間變長芍殖。
這些相應(yīng)的調(diào)整會減少無線電使用豪嗽,并提升續(xù)航能力。
-
2豌骏、作為 peripheral 的后臺模式
作為 peripheral 時龟梦,如果需要支持后臺模式,則在 info.plist 中配置 UIBackgroundModes – bluetooth-peripheral窃躲。配置后计贰,系統(tǒng)會在有讀寫請求和訂閱事件時,喚醒程序蒂窒。
-
在后臺躁倒,除了允許處理讀寫請求和訂閱事件外,Core Bluetooth 框架還允許 peripheral 發(fā)出廣播洒琢。同樣秧秉,廣播事件也有前后臺區(qū)別。在后臺發(fā)起時是這樣的:
- CBAdvertisementDataLocalNameKey 將失效衰抑,在廣播時象迎,廣播數(shù)據(jù)將不再包含 peripheral 的名字。
- 被 CBAdvertisementDataServiceUUIDsKey 修飾的 UUID 數(shù)組將會被放到 overflow 區(qū)域中呛踊,意味著只能被明確標(biāo)識了搜索 service UUID 的 iOS 設(shè)備找到砾淌。
- 如果所有 app 都在后臺發(fā)起廣播完丽,那么發(fā)起頻率會降低。
6.3 巧妙的使用后臺模式
雖然程序支持一個或多個 Core Bluetooth 服務(wù)在后臺運(yùn)行拇舀,但也不要濫用。因為藍(lán)牙服務(wù)會占用 iOS 設(shè)備的無線電資源蜻底,這也會間接影響到續(xù)航能力骄崩,所以盡可能少的去使用后臺模式。app 會喚醒程序并處理相關(guān)事務(wù)薄辅,完成后又會快速回到掛起狀態(tài)要拂。
-
無論是 central 還是 peripheral,要支持后臺模式都應(yīng)該遵循以下幾點:
- 程序應(yīng)該提供 UI站楚,讓用戶決定是否要在后臺運(yùn)行脱惰。
- 一旦程序在后臺被喚醒,程序只有 10s 的時間來處理相關(guān)事務(wù)窿春。所以應(yīng)該在程序再次掛起前處理完事件拉一。后臺運(yùn)行的太耗時的程序會被系統(tǒng)強(qiáng)制關(guān)閉進(jìn)程。
- 處理無關(guān)的事件不應(yīng)該喚醒程序旧乞。
和后臺運(yùn)行的更多介紹蔚润,可以查看 App Programming Guide for iOS。
6.4 處理常駐后臺任務(wù)
某些 app 可能需要 Core Bluetooth 常駐后臺尺栖,比如嫡纠,一款用 BLE 技術(shù)和門鎖通信的 app。當(dāng)用戶離開時延赌,自動上鎖除盏,回來時,自動開鎖(即使程序運(yùn)行在后臺)挫以。當(dāng)用戶離開時者蠕,可能已超出藍(lán)牙連接范圍,所以沒辦法給鎖通信屡贺。此時可以調(diào)用 CBCentralManager 的
connectPeripheral:options:
方法蠢棱,因為該方法沒有超時設(shè)置,所以甩栈,在用戶返回時泻仙,可以重新連接到鎖。但是還有這樣的情形:用戶可能離開家好幾天量没,并且在這期間玉转,程序已經(jīng)被完全退出了。那么用戶再次回家時殴蹄,就不能自動開鎖究抓。對于這類 app 來說猾担,常駐后臺操作就顯得尤為重要。
-
1刺下、狀態(tài)保存與恢復(fù)
因為狀態(tài)的保存和恢復(fù) Core Bluetooth 都為我們封裝好了绑嘹,所以我們只需要選擇是否需要這個特性即可。系統(tǒng)會保存當(dāng)前 central manager 或 peripheral manager橘茉,并且繼續(xù)執(zhí)行藍(lán)牙相關(guān)事件(即使程序已經(jīng)不再運(yùn)行)工腋。一旦事件執(zhí)行完畢,系統(tǒng)會在后臺重啟 app畅卓,這時你有機(jī)會去存儲當(dāng)前狀態(tài)擅腰,并且處理一些事物。在之前提到的 “門鎖” 的例子中翁潘,系統(tǒng)會監(jiān)視連接請求趁冈,并在
centralManager:didConnectPeripheral:
回調(diào)時,重啟 app拜马,在用戶回家后渗勘,連接操作結(jié)束。Core Bluetooth 的狀態(tài)保存與恢復(fù)在設(shè)備作為 central一膨、peripheral 或者這兩種角色時呀邢,都可用。在設(shè)備作為 central 并添加了狀態(tài)保存與恢復(fù)支持后豹绪,如果 app 被強(qiáng)行關(guān)閉進(jìn)程价淌,系統(tǒng)會自動保存 central manager 的狀態(tài)(如果 app 有多個 central manager,你可以選擇哪一個需要系統(tǒng)保存)瞒津。
-
對于 CBCentralManager蝉衣,系統(tǒng)會保存以下信息:
- central 準(zhǔn)備連接或已經(jīng)連接的 peripheral
- central 需要掃描的 service(包括掃描時,配置的 options)
- central 訂閱的 characteristic
-
對于 peripheral 來說巷蚪,情況也差不多病毡。系統(tǒng)對 CBPeripheralManager 的處理方式如下:
- peripheral 在廣播的數(shù)據(jù)
- peripheral 存入的 service 和 characteristic 的樹形結(jié)構(gòu)
- 已經(jīng)被 central 訂閱了的 characteristic 的值
當(dāng)系統(tǒng)在后臺重新加載程序后(可能是因為找到了要找的 peripheral),你可以重新實例化 central manager 或 peripheral 并恢復(fù)他們的狀態(tài)屁柏。
-
2啦膜、添加狀態(tài)存儲和恢復(fù)支持
-
狀態(tài)的存儲和恢復(fù)功能在 Core Bluetooth 中是可選的,添加支持可以通過以下幾個步驟:
- (必須)在初始化 central manager 或 peripheral manager 時淌喻,要選擇是否需要支持僧家。會在文后的【3、選擇支持存儲和恢復(fù)】中介紹裸删。
- (必須)在系統(tǒng)從后臺重新加載程序時八拱,重新初始化 central manager 或 peripheral manager。會在文后的【4、重新初始化 central manager 和 peripheral manager】中介紹肌稻。
- (必須)實現(xiàn)恢復(fù)狀態(tài)相關(guān)的代理方法清蚀。會在文后的【5、實現(xiàn)恢復(fù)狀態(tài)的代理方法】中介紹爹谭。
- (可選)更新 central manager 或 peripheral manager 的初始化過程枷邪。會在文后的【6、更新 manager 初始化過程】中介紹诺凡。
-
-
3齿风、選擇支持存儲和恢復(fù)
如果要支持存儲和恢復(fù),則需要在初始化 manager 的時候給一個 restoration identifier绑洛。restoration identifier 是 string 類型,并標(biāo)識了 app 中的 central manager 或 peripheral manager童本。這個 string 很重要真屯,它將會告訴 Core Bluetooth 需要存儲狀態(tài),畢竟 Core Bluetooth 恢復(fù)有 identifier 的對象穷娱。
例如绑蔫,在 central 端,要想支持該特性泵额,可以在調(diào)用 CBCentralManager 的初始化方法時配深,配置 CBCentralManagerOptionRestoreIdentifierKey:
雖然以上代碼沒有展示出來,其實在 peripheral manager 中要設(shè)置 identifier 也是這樣的嫁盲。只是在初始化時篓叶,將 key 改成了 CBPeripheralManagerOptionRestoreIdentifierKey。
因為程序可以有多個 CBCentralManager 和 CBPeripheralManager羞秤,所以要確保每個 identifier 都是唯一的缸托。
-
4、重新初始化 central manager 和 peripheral manager
當(dāng)系統(tǒng)重新在后臺加載程序時瘾蛋,首先需要做的即根據(jù)存儲的 identifier俐镐,重新初始化 central manager 或 peripheral manager。如果你只有一個 manager哺哼,并且 manager 存在于 app 生命周期中佩抹,那這個步驟就不需要做什么了。
如果 app 中包含多個 manager取董,或者 manager 不是在整個 app 生命周期中都存在的棍苹,那 app 就必須要區(qū)分你要重新初始化哪個 manager 了。你可以通過從 app delegate 中的
application:didFinishLaunchingWithOptions:
中取出 key(UIApplicationLaunchOptionsBluetoothCentralsKey 或 UIApplicationLaunchOptionsBluetoothPeripheralsKey)中的 value(數(shù)組類型)來得到程序退出之前存儲的 manager identifier 列表:拿到這個列表后甲葬,就可以通過循環(huán)來重新初始化所有的 manager 了廊勃。
-
5、實現(xiàn)恢復(fù)狀態(tài)的代理方法
在重新初始化 manager 之后,接下來需要同步 Core Bluetooth 存儲的他們的狀態(tài)坡垫。要想弄清楚在程序被退出時都在做些什么梭灿,就需要正確的實現(xiàn)代理方法。對于 central manager 來說冰悠,需要實現(xiàn)
centralManager:willRestoreState:
堡妒;對于 peripheral manager 來說,需要實現(xiàn)peripheralManager:willRestoreState:
溉卓。注意:如果選擇存儲和恢復(fù)狀態(tài)皮迟,當(dāng)系統(tǒng)在后臺重新加載程序時,首先調(diào)用的方法是
centralManager:willRestoreState:
或peripheralManager:willRestoreState:
桑寨。如果沒有選擇存儲的恢復(fù)狀態(tài)(或者喚醒時沒有什么內(nèi)容需要恢復(fù))伏尼,那么首先調(diào)用的方法是centralManagerDidUpdateState:
或peripheralManagerDidUpdateState:
。無論是以上哪種代理方法尉尾,最后一個參數(shù)都是一個包含程序退出前狀態(tài)的字典爆阶。字典中,可用的 key 沙咏,central 端有:
peripheral 端有:
要恢復(fù) central manager 的狀態(tài)辨图,可以用
centralManager:willRestoreState:
返回字典中的 key 來得到。假如說 central manager 有想要或者已經(jīng)連接的 peripheral肢藐,那么可以通過 CBCentralManagerRestoredStatePeripheralsKey 對應(yīng)得到的 peripheral(CBPeripheral 對象)數(shù)組來得到故河。具體要對拿到的 peripheral 數(shù)組做什么就要根據(jù)需求來了。如果這是個 central manager 搜索到的 peripheral 數(shù)組吆豹,那就可以存儲這個數(shù)組的引用鱼的,并且開始建立連接了(注意給這些 peripheral 設(shè)置代理,否則連接后不會走 peripheral 的代理方法)痘煤。
恢復(fù) peripheral manager 的狀態(tài)和 central manager 的方式類似鸳吸,就只是把代理方法換成了
peripheralManager:willRestoreState:
,并且使用對應(yīng)的 key 即可速勇。
-
6晌砾、更新 manager 初始化過程
在實現(xiàn)了全部的必須步驟后,你可能想要更新 manager 的初始化過程烦磁。雖然這是個可選的操作养匈,但是它對確保各種操作能正常進(jìn)行尤為重要。假如都伪,你的應(yīng)用在 central 和 peripheral 做數(shù)據(jù)交互時呕乎,被強(qiáng)制退出了。即使 app 最后恢復(fù)狀態(tài)時陨晶,找到了這個 peripheral猬仁,那你也不知道 central 和這個 peripheral 當(dāng)時的具體狀態(tài)帝璧。但其實我們在恢復(fù)時,是想恢復(fù)到程序被強(qiáng)制退出前的那一步湿刽。
這個需求的烁,可以在代理方法
centralManagerDidUpdateState:
中,通過發(fā)現(xiàn)恢復(fù)的 peripheral 是否之前已經(jīng)成功連接來實現(xiàn):上面的代碼描述了诈闺,當(dāng)系統(tǒng)在完成搜索 service 之后才退出的程序渴庆,可以通過調(diào)用
discoverServices:
方法來恢復(fù) peripheral 的數(shù)據(jù)。如果 app 成功搜索到 service雅镊,你可以是否能搜索到需要的 characteristic(或者已經(jīng)訂閱過)襟雷。通過更新初始化過程,可以確保在正確的時間點仁烹,調(diào)用正確的方法耸弄。
7、第三方框架
-
iOS 藍(lán)牙開發(fā)中常用的第三方框架