這是 CoreBluetooth 系列的最后一篇,其他文章可查看:
CoreBluetooth2 作為 Central 時(shí)的數(shù)據(jù)讀寫
CoreBluetooth3 作為 Central 時(shí)的數(shù)據(jù)讀寫(補(bǔ)充)
CoreBluetooth4 作為 Central 時(shí)的數(shù)據(jù)讀寫(最佳實(shí)踐)
CoreBluetooth5 作為 Central 時(shí)的數(shù)據(jù)讀寫(OTA 固件升級(jí)與文件傳輸)
CoreBluetooth6 作為 Peripheral 時(shí)的請(qǐng)求響應(yīng)
CoreBluetooth7 作為 Peripheral 時(shí)的請(qǐng)求響應(yīng)(最佳實(shí)踐)
對(duì)于 iOS app 來說夕凝,知道現(xiàn)在是運(yùn)行在前臺(tái)和后臺(tái)是至關(guān)重要的或链。因?yàn)楫?dāng)程序掛起后轧铁,對(duì)資源的使用是相當(dāng)有限的乎赴。關(guān)于多任務(wù)的介紹摔竿,可以看app 開發(fā)手冊(cè)间坐。
默認(rèn)情況下灾挨,Core Bluetooth 是不會(huì)在后臺(tái)運(yùn)行的(無論是 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ì)斷開所有的連接作谚。從 iOS 7 開始,能夠先保存狀態(tài)(無論是 central 還是 peripheral)庵芭,并在重新打開 app 時(shí)還原這些狀態(tài)妹懒。通過這一?特性,就可以做長(zhǎng)時(shí)間操作了双吆。
運(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法再接收任何藍(lán)牙事件。
對(duì)于 central 來說曹宴,掛起將無法再進(jìn)行掃描和搜索 peripheral搂橙。對(duì)于 peripheral 來說,將無法再發(fā)起廣播笛坦,central 也無法再訪問動(dòng)態(tài)變化的 characteristic 數(shù)據(jù)区转,訪問將返回 error。
根據(jù)不同情況版扩,這種機(jī)制會(huì)影響程序在以下幾個(gè)方面的運(yùn)用废离。你正在讀取 peripheral 的數(shù)據(jù),結(jié)果程序被掛起了(可能是用戶切換到了另外一個(gè) app)礁芦,此時(shí)連接會(huì)被斷開蜻韭,但是要直到程序重新喚醒時(shí),你才知道被斷開了柿扣。
利用連接 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ù)提示來判斷是否要喚醒該 app艰垂。
你可以利用 central 在連接 peripheral 時(shí)的方法connectPeripheral:options:中的options來觸發(fā)提示:
CBConnectPeripheralOptionNotifyOnConnectionKey—— 在連接成功后,程序被掛起埋虹,?給出系統(tǒng)提示猜憎。
CBConnectPeripheralOptionNotifyOnDisconnectionKey—— 在程序掛起,藍(lán)牙連接斷開時(shí)搔课,給出系統(tǒng)提示拉宗。
CBConnectPeripheralOptionNotifyOnNotificationKey—— 在程序掛起后,收到 peripheral 數(shù)據(jù)時(shí),給出系統(tǒng)提示旦事。
Core Bluetooth 后臺(tái)模式
如果你想讓你的 app 能在后臺(tái)運(yùn)行藍(lán)牙魁巩,那么必須在info.plist中打開藍(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 兩種角色都有集晚,那則需要開啟兩種模式。配置即是在info.plist中添加UIBackgroundModeskey区匣,類型為數(shù)組偷拔,value 則根據(jù)你當(dāng)前角色來選擇:
bluetooth-central—— 即 Central。
bluetooth-peripheral—— 即 Peripheral亏钩。
這個(gè)配置在 Xcode 中莲绰,可以在 Capabilities 中進(jìn)行配置,而不用直接面對(duì) key-value姑丑。如果要看到 key-value蛤签,可以在info.plist中打開查看。
作為 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í)允許你去處理重要的事件马昨,比如:連接的建立或斷開竞帽,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ì)減少無線電使用啦租,并提升續(xù)航能力。
作為 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ì)降低。
巧妙的使用后臺(tái)模式
雖然程序支持一個(gè)或多個(gè) Core Bluetooth 服務(wù)在后臺(tái)運(yùn)行婚脱,但也不要濫用今魔。因?yàn)樗{(lán)牙服務(wù)會(huì)占用 iOS 設(shè)備的無線電資源,這也會(huì)間接影響到續(xù)航能力障贸,所以盡可能少的去使用后臺(tái)模式错森。app 會(huì)喚醒程序并處理相關(guān)事務(wù),完成后又會(huì)快速回到掛起狀態(tài)篮洁。
無論是 central 還是 peripheral涩维,要支持后臺(tái)模式都應(yīng)該遵循以下幾點(diǎn):
程序應(yīng)該提供 UI,讓用戶決定是否要在后臺(tái)運(yùn)行袁波。
一旦程序在后臺(tái)被喚醒瓦阐,程序只有 10s 的時(shí)間來處理相關(guān)事務(wù)。所以應(yīng)該在程序再次掛起前處理完事件篷牌。后臺(tái)運(yùn)行的太耗時(shí)的程序會(huì)被系統(tǒng)強(qiáng)制關(guān)閉進(jìn)程睡蟋。
處理無關(guān)的事件不應(yīng)該喚醒程序。
和后臺(tái)運(yùn)行的更多介紹枷颊,可以查看App Programming Guide for iOS戳杀。
處理常駐后臺(tái)任務(wù)
某些 app 可能需要 Core Bluetooth 常駐后臺(tái)该面,比如,一款用 BLE 技術(shù)和門鎖通信的 app信卡。當(dāng)用戶離開時(shí)隔缀,自動(dòng)上鎖,回來時(shí)坐求,自動(dòng)開鎖(即使程序運(yùn)行在后臺(tái))蚕泽。當(dāng)用戶離開時(shí),可能已超出藍(lán)牙連接范圍桥嗤,所以沒辦法給鎖通信须妻。此時(shí)可以調(diào)用CBCentralManager的connectPeripheral:options:方法,因?yàn)樵摲椒]有超時(shí)設(shè)置泛领,所以荒吏,在用戶返回時(shí),可以重新連接到鎖渊鞋。
但是還有這樣的情形:用戶可能離開家好幾天绰更,并且在這期間,程序已經(jīng)被完全退出了锡宋。那么用戶再次回家時(shí)儡湾,就不能自動(dòng)開鎖。對(duì)于這類 app 來說执俩,常駐后臺(tái)操作就顯得尤為重要徐钠。
狀態(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)事件(及時(shí)程序已經(jīng)不再運(yùn)行)。一旦事件執(zhí)行完畢衡奥,系統(tǒng)會(huì)在后臺(tái)重啟 app爹袁,這是你有機(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 需要掃描的 service(包括掃描時(shí)惠赫,配置的 options)
central 準(zhǔn)備連接或已經(jīng)連接的 peripheral
central 訂閱的 characteristic
對(duì)于 peripheral 來說把鉴,情況也差不多。系統(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)庭砍。接下來會(huì)詳細(xì)介紹如何存儲(chǔ)和恢復(fù)狀態(tài)。
添加狀態(tài)存儲(chǔ)和恢復(fù)支持
狀態(tài)的存儲(chǔ)和恢復(fù)功能在 Core Bluetooth 中是可選的混埠,添加支持可以通過以下幾個(gè)步驟:
(必須)在初始化 central manager 或 peripheral manager 時(shí)怠缸,要選擇是否需要支持。會(huì)在文后的【選擇支持存儲(chǔ)和恢復(fù)】中介紹钳宪。
(必須)在系統(tǒng)從后臺(tái)重新加載程序時(shí)揭北,重新初始化 central manager 或 peripheral manager。會(huì)在文后的【重新初始化 central manager 和 peripheral manager】中介紹吏颖。
(必須)實(shí)現(xiàn)恢復(fù)狀態(tài)相關(guān)的代理方法搔体。會(huì)在文后的【 實(shí)現(xiàn)恢復(fù)狀態(tài)的代理方法】中介紹。
(可選)更新 central manager 或 peripheral manager 的初始化過程半醉。會(huì)在文后的【更新 manager 初始化過程】中介紹疚俱。
選擇支持存儲(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:
myCentralManager = [[CBCentralManager alloc] initWithDelegate:selfqueue:niloptions:@{CBCentralManagerOptionRestoreIdentifierKey :@"myCentralManagerIdentifier"}];
雖然以上代碼沒有展示出來翁授,其實(shí)在 peripheral manager 中要設(shè)置 identifier 也是這樣的茫打。只是在初始化時(shí),將 key 改成了CBPeripheralManagerOptionRestoreIdentifierKey。
因?yàn)槌绦蚩梢杂卸鄠€(gè)CBCentralManager和CBPeripheralManager露氮,所以要確保每個(gè) identifier 都是唯一的。
重新初始化 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 了职恳。你可以通過從 app delegate 中的application:didFinishLaunchingWithOptions:中取出 key(UIApplicationLaunchOptionsBluetoothCentralsKey或UIApplicationLaunchOptionsBluetoothPeripheralsKey) ?中的 value(數(shù)組類型)來得到程序退出之前存儲(chǔ)的 manager identifier 列表:
- (BOOL)application:(UIApplication*)applicationdidFinishLaunchingWithOptions:(NSDictionary*)launchOptions {NSArray*centralManagerIdentifiers = launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey];returnYES;}
拿到這個(gè)列表后,就可以通過循環(huán)來重新初始化所有的 manager 了方面。
實(shí)現(xiàn)恢復(fù)狀態(tài)的代理方法
在重新初始化 manager 之后放钦,接下來需要同步 Core Bluetooth 存儲(chǔ)的他們的狀態(tài)。要想弄清楚在程序被退出時(shí)都在做些什么恭金,就需要正確的實(shí)現(xiàn)代理方法最筒。對(duì)于 central manager 來說,需要實(shí)現(xiàn)centralManager:willRestoreState:蔚叨;對(duì)于 peripheral manager 來說床蜘,需要實(shí)現(xiàn)peripheralManager:willRestoreState:。
注意:如果選擇存儲(chǔ)和恢復(fù)狀態(tài)蔑水,當(dāng)系統(tǒng)在后臺(tái)重新加載程序時(shí)邢锯,首先調(diào)用的方法是centralManager:willRestoreState:或peripheralManager:willRestoreState:。如果沒有選擇存儲(chǔ)的恢復(fù)狀態(tài)(或者喚醒時(shí)沒有什么內(nèi)容需要恢復(fù))搀别,那么首先調(diào)用的方法是centralManagerDidUpdateState:或peripheralManagerDidUpdateState:丹擎。
無論是以上哪種代理方法,最后一個(gè)參數(shù)都是一個(gè)包含程序退出前狀態(tài)的字典歇父。字典中蒂培,可用的 key ,central 端有:
NSString*constCBCentralManagerRestoredStatePeripheralsKey;NSString*constCBCentralManagerRestoredStateScanServicesKey;NSString*constCBCentralManagerRestoredStateScanOptionsKey;
peripheral 端有:
NSString*constCBPeripheralManagerRestoredStateServicesKey;NSString*constCBPeripheralManagerRestoredStateAdvertisementDataKey;
要恢復(fù) central manager 的狀態(tài)榜苫,可以用centralManager:willRestoreState:返回字典中的 key 來得到护戳。假如說 central manager 有想要或者已經(jīng)連接的 peripheral,那么可以通過CBCentralManagerRestoredStatePeripheralsKey對(duì)應(yīng)得到的 peripheral (CBPeripheral對(duì)象)數(shù)組來得到垂睬。
- (void)centralManager:(CBCentralManager *)central? ? ? willRestoreState:(NSDictionary*)state {NSArray*peripherals = state[CBCentralManagerRestoredStatePeripheralsKey];}
具體要對(duì)拿到的 peripheral 數(shù)組做什么就要根據(jù)需求來了媳荒。如果這是個(gè) central manager 搜索到的 peripheral 數(shù)組,那就可以存儲(chǔ)這個(gè)數(shù)組的引用驹饺,并且開始建立連接了(注意給這些 peripheral 設(shè)置代理钳枕,否則連接后不會(huì)走 peripheral 的代理方法)。
恢復(fù) peripheral manager 的狀態(tài)和 central manager 的方式類似赏壹,就只是把代理方法換成了peripheralManager:willRestoreState:鱼炒,并且使用對(duì)應(yīng)的 key 即可。
更新 manager 初始化過程
在實(shí)現(xiàn)了全部的必須步驟后蝌借,你可能想要更新 manager 的初始化過程昔瞧。雖然這是個(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:中牲剃,通過發(fā)現(xiàn)恢復(fù)的 peripheral 是否之前已經(jīng)成功連接來實(shí)現(xiàn):
NSUIntegerserviceUUIDIndex = [peripheral.services indexOfObjectPassingTest:^BOOL(CBService *obj,NSUIntegerindex,BOOL*stop) {return[obj.UUID isEqual:myServiceUUIDString];}];if(serviceUUIDIndex ==NSNotFound) {? ? [peripheral discoverServices:@[myServiceUUIDString]];}
上面的代碼描述了,當(dāng)系統(tǒng)在完成搜索 service 之后才退出的程序雄可,可以通過調(diào)用discoverServices:方法來恢復(fù) peripheral 的數(shù)據(jù)凿傅。如果 app 成功搜索到 service,你可以是否能搜索到需要的 characteristic(或者已經(jīng)訂閱過)数苫。通過更新初始化過程聪舒,可以確保在正確的時(shí)間點(diǎn),調(diào)用正確的方法虐急。
最后
到這里箱残,對(duì) Core Bluetooth 的理解就暫告一段落,如果有什么問題或建議止吁,歡迎評(píng)論被辑。