iOS--iOS app中藍牙的后臺處理

2018年12月28日更新
這一篇是我在17年初處理BLE后臺相關業務時翻譯的,最初的主要目的是覺得翻譯一遍能夠加深自己的理解。發在這里最早是沒人看的,第一條評論應該是罵我就知道抄抄抄,估計是看到了別的開發者翻譯的文章了吧。最近這一篇文章的評論數飆升,所以想在這里解釋下,當初為啥沒放原文鏈接。更重要的是對大家最感興趣的問題做個總結,結合我這一年多的開發經驗,聊一聊蘋果的藍牙后臺邊際,說說BLE app在中心端最舒服的開發姿勢。

  • 1.Apple定義的后臺任務是怎樣的?
    • 早期iOS系統無真后臺,后期因為考慮到特殊場景,比如說音樂定位,后臺下載等需求。蘋果提供了BackGround Mode為特殊應用提供后臺模式,藍牙也在此列。
  • 1.BLE Central后臺可以做到什么?做不到什么?
    • 通常我們實現BLE的中心時,可以通過連接外設的事件促發系統喚醒App以執行一些后臺操作。但是這里的時間很短,一個很常見的Case是接受外設上傳的數據并保存。永久化保存數據需要注意文件保護的坑,舊文有提過。
  • 3.centralManager:willRestoreState:application:didFinishLaunchingWithOptions:方法
    • 理論上這些方法會在外設發送數據時喚醒App,但是在新的12.X系統中發現,如果外設在App后臺時斷連,當系統重連外設時(綁定了ANCS),App并不能收到回調。這個有些意外。

此外App可以通過BackgroundTask來申請額外的后臺時間。

最后多說一句,BLE通信技術的核心是低功耗,在電池技術遲遲不能突破的大背景下,iOS系統的基礎也是以前臺為王,后臺的策略都是克制,廣大安卓定制系統也都遵循這個套路,App的后臺變成應用廠商和系統廠商之間的博弈。作為app開發,我覺得可以從業務上重新思考下產品形態,后臺雖好,也不宜貪杯


以下是原文,翻譯自Apple Doc.

iOS app中藍牙的后臺處理(Core Bluetooth Background Processing for iOS Apps)

對于iOS app來說,知道你的app是前臺還是后臺非常重要。由于iOS設備的系統資源有限,所以一個app在前臺和后臺時的表現一定是不一樣的(iOS系統為了保證用戶體驗,前臺應用具有資源的優先分配權,不過并不是無限的,你的app性能太差,照樣會被系統收了)。關于更多的后臺操作內容。請看這一章。
默認情況下,大多數常用的藍牙任務(不論是中心還是周邊的)在app后臺或懸掛時都是不可用的。也就是說,你可以聲明你的app支持藍牙后臺執行模式,這允許你的app在需要處理連接的那個藍牙設備的關聯事件時,系統可以把你的app從懸掛狀態喚醒。即使你的app不需要全范圍的后臺處理支持,但是當重要的事件發生時,系統還是會通知你的app。
即使你的app實現了一種甚至兩種后臺執行模式的支持,它也并不是一直運行的。在某些時候,系統需要殺掉你的app來為前臺app提供足夠的內存資源(例如你在玩某大型手游時,內存一般會相對吃緊,這時后臺應用自然就不太好過)--這會導致所有的行動和掛載的連接丟失(這里的掛載原文是pending connections,我理解是你的app和周邊設備的連接,因為你注冊了后臺模式,所以系統為你保留了資源維持這種連接,可以使用BLE的通信,但是當內存被釋放時,你的對象也就不見了)。在iOS7,Core Bluetooth框架支持保存中心管理者和周邊管理者(就是你的CBCenteralManager && CBPeripheralManager 對象)的狀態信息,并且支持在app 啟動的時候去恢復中心管理者的狀態。你可以使用這個功能去做一些藍牙設備相關的長期行為。

前臺APP(foreground-Only Apps)

除非你申請了執行指定的后臺任務,大多數的app在進入后臺模式之后很快就會被掛起(按下home鍵,大約5秒,app進入suspended狀態)。當app處于掛起狀態時,無法處理任何藍牙相關的任務直到你的app進入前臺(被用戶喚醒)。
前臺app(是指沒有申請藍牙后臺模式權限的app)處于后臺模式(backgroud state)或者懸掛模式(suspended state)時,在中心這邊,無法搜索和發現周邊在廣告的設備。作為周邊時,無法廣告,此時中心想要通過公開服務的特征去訪問它的話都會報錯。
由于上述原因,你的app可能被一些默認行為影響。舉個例子來說,假設你正在和你的藍牙設備通信,這時連接是正常的,你能夠得到設備給你上報的數據。想象下現在你的app被掛起了(原因可能是用戶這時打開了另一個app)。如果這時和藍牙設備的連接斷了,你的app就不會收到斷連的事件通知直到它再次進入前臺時。

利用周邊連接選項(Take Advantage of Peripheral Connection Options)

前臺app在被掛起時,所以的藍牙事件會被系統放進一個隊列里,當你的app再次活躍時發給你 。這就是說,Core Bluetooth提供了一種中心角色的事件觸發用戶提醒機制。用戶可以通過這些提示來決定要不要在某些時候把app從后臺拉到前臺來。
在你的終端設備調用connectPeripheral:options:方法連接遠程周邊設備時,你可以使用下面的這些key來設置options參數。

  • CBConnectPeripheralOptionNotifyOnConnectionKey:如果想要系統在指定的周邊設備在app掛起狀態期間連接成功時顯示一個alter提示,就使用這個key值。
  • CBConnectPeripheralOptionNotifyOnDisconnectionKey:如果想要系統在指定的周邊設備在app掛起狀態期間斷開連接時顯示一個alter提示,就使用這個key值。
  • CBConnectPeripheralOptionNotifyOnNotificationKey:如果想要系統在指定的周邊設備在app掛起狀態期間收到任何通知(這里的notifications應該是指訂閱的內容)時顯示一個alter提示,就使用這個key值。

想要了解更多周邊連接的options,請查閱* Peripheral Connection Options*內容,地址在。

藍牙后臺執行模式(Core Bluetooth Background Execution Modes)

如果你的app需要在后臺執行一些藍牙相關的任務,你必須在信息屬性列表(Info.plist)文件中聲明后臺執行模式的支持。當你的app聲明了它,系統會把你的app從懸掛狀態喚醒以處理一些藍牙相關事件。這個支持對app來說很重要,它可以用來和BLE設備在特定時間間隔里交付一些數據,就像個心率監控。
這里有兩種模式可以供app選擇,一種是作為中心角色是實現,另一種是作為周邊角色。如果你的app把他兩都實現了,那你就該把兩種后臺模式都實現了。核心藍牙后臺模式通過載Info.plist中添加UIBackgroudModes關鍵字到一個數組中來聲明。數組中的關鍵包闊一下字符串:

  • bluetooth-central 使用核心藍牙框架和周邊BLE設備通信
  • bluetooth-peripheral 使用核心藍牙框架共享數據

**注意:** xcode的可視界面的屬性列表里,為了可讀性考慮,很多key被用易讀字符串替代了,如果想知道實際的key在info.plist里的名字,按住Control鍵并單擊編輯器窗口中的任意鍵,并在上下文窗口中點擊Show Raw Keys/Values按鈕(我喜歡Open As/Source Code)。
關于怎么設置info.plist內容的詳細信息,請看Xcode Help

藍牙中心后臺執行模式

當一個app在UIBackgroundModes中添加了bluetooth-central關鍵字在Info.plist以實現中心角色。核心藍牙框架允許你的app在后臺運行以執行一些中心藍牙相關的任務。當你的app在后臺時,你仍然可以搜索連接周邊設備,和周邊設備通信,交換數據。另外,系統會在任何 CBCentralManagerDelegate 或 CBPeripheralDelegate 的回調方法被調用時喚醒你的app,允許你的app處理重要的中心角色事件,比方說連接突然斷了,周邊角色上報數據了,中心管理者的狀態發生改變。
雖然你可以在后臺執行很多藍牙相關任務,但是請記住,后臺操作和app在前臺始終還是不同的,尤其是你的app在后臺搜索設備的時候。

  • CBCentralManagerScanOptionAllowDuplicatesKey 的搜索選項將被忽略,多次掃描的結果會被合并為同一個事件
  • 如果所有的app都在后臺掃描,你的中心設備的搜索事件就會增長。簡單說,就是你得花很長時間才能搜索到廣播的設備。
    這些改變幫助最小化使用廣播功能以節約手機的電池電量。

藍牙周邊后臺執行模式

想要作為一個周邊角色在后臺工作,你需要在Info.plist文件中添加bluetooth-periphral到UIBackgroundModes關鍵字下。當你這么做了,系統會在你的app需要讀,寫,訂閱事件的時候喚醒它。
除了可以在后臺喚醒app處理連接的中心的讀寫訂閱。藍牙中心庫還可以允許你的app在后臺的時候廣播。但是你需要了解app在后臺的廣播和在前臺的廣播狀態不太一樣。特別的,當你的app在后臺廣播時。

  • CBAdvertisementDataLocalNameKey 廣告鍵是被忽略的,而且local name也不會被廣播的
  • 所以 CBAdvertisementDataServiceUUIDsKey中的服務UUID被放在一個“溢出”區,它們只能被明確搜索的iOS設備搜索到。
  • 如果所有app都在后臺廣播,你的app的包廣播頻率會變少。

合理使用后臺執行模式

雖然聲明一種甚至兩種藍牙后臺執行模式可以處理一些必需的場景,你也應該控制后臺執行的情況。因為在后臺執行時需要使用iOS設備的無線通信,這一部分對電池電量消耗比較大。在后臺做盡量少的事,被任何時間喚醒的時候都應該盡快處理完然后再次進入懸掛狀態。
任何app不管你聲明的是哪種后臺模式,都應該遵循這個使用守則。

  • 必須要有用戶界面可供用戶開始or停止藍牙通信
  • 被喚醒時,app大概有10秒時間可以處理任務,理想狀況下,它最好能在10秒內處理完,然后再次被掛起。app在后臺逗留太長時間會被系統限制甚至殺死。
  • app的后臺模式不應該被用來處理非藍牙相關任務
    更多關于app后臺行為的信息,請看資料Being a Responsible Background App章節。

后臺執行較長時間任務

有一些app可能需要在后臺較長時間使用核心藍牙模塊。舉個例子,你可能需要開發一款家居安全的app在iOS設備上,它可以和你的門鎖(實現了BLE協議的)通信。這個app可以在用戶離開家的時候自動鎖門,用戶回家時自動開門-所有這些情況app都在后臺。當用戶離開家,iOS設備會超出門鎖的通信范圍,連接就會斷開。這時,app只需調用CBCentralManager類的connectPeripheral:options:方法,而且由于連接請求不會超時,當用戶回家時,設備將重新連接,門鎖將會打開。
現在想象下用戶經常會離開家好幾天。如果你的app在用戶離開家時就被terminated,這個app就不能在用戶回家時重新連上這個鎖了,用戶就進不了門啦。像這樣的app,被允許執行長時間的行為,像監控行為和保持連接。

狀態的保存和恢復

由于狀態的保存和恢復是內置在CoreBluetooth框架之中的,你的app可以選擇這個功能,請求系統保存app的中心或者周邊管理者的狀態,用來在某些行為下執行一些藍牙相關任務,即使你的app已經不在運行了。當一個這樣的任務完成時,系統重啟你的app到后臺狀態并且給它一個機會保存它的狀態以處理適當的事件。像上面提到的那個家居安全的app,當用戶回家連接請求完成時,系統會管理連接請求,重啟app并調用委托的回調函數centralManager:didConnectPeripheral:。
CoreBluetooth支持app實現中心或者周邊角色的狀態保存和恢復,一起實現也可以。當你的app實現中心角色并且添加支持了狀態的保存和恢復,當系統計劃終止你的app以釋放資源時它會保存你的中心管理者對象的狀態(如果你的app有多個中心管理者,你可以選擇你希望系統保存的那個)。特別的,系統會為中心管理者跟蹤以下信息:

  • 中心管理者已搜索到的服務(任何搜索指定選項開始之后的)
  • 所以中心管理者準備連接或者已經連接的周邊
  • 中心管理者已經訂閱的特征

實現周邊角色的app也可以利用狀態的保存和恢復,系統為周邊管理者跟蹤以下信息:

  • 周邊正在廣告的數據
  • 周邊管理者公開在設備數據庫的服務和特征
  • 已經訂閱某些特征數據的中心

當你的app在后臺被系統重啟(比方說你的app搜索的那個周邊設備被發現了),你可以重新啟動應用程序的中央和周邊管理器并恢復其狀態。接下來的小節詳細討論怎樣在你的app中使用狀態恢復與保存

添加狀態的保存和恢復

狀態的恢復與保存是CoreBluetooth的一個可選功能,添加它需要你的app做一些工作,你可以通過下面步驟做到:

  • 1.(必須的)在初始化中心或者周邊管理者的時候選擇是否需要支持狀態的保存和恢復,這個步驟在“選擇狀態的保存和恢復”部分有詳細介紹。
  • 2.(必須的)在系統重啟app時恢復你的中心或周邊角色,這個步驟的詳情在“恢復你的中心和周邊管理者”部分。
  • 3.(必須的)實現適當的保存委托方法。這個步驟在“實現合適的保存委托方法”中有介紹。
  • 4.(可選的)更新你的中心和周邊管理者的初始化過程。這個步驟在“更新的你初始化過程”中有介紹。
選擇狀態的保存和恢復

在app中加入狀態的保存和恢復功能的方式很簡單,只需要在初始化中心|周邊管理者的時候提供一個唯一的恢復字符串。一個“恢復標識”就是一個為Corebluetooth和你的app標記中心|周邊管理者的字符串。這個字符串的值只有你的代碼知道,但是這個字符串的存在高速CoreBluetooth它需要保存這個被標記對象的狀態。CoreBluetooth也僅僅會保存這些有“恢復標示”的對象。
舉例來說,對于一個只使用一個CBCentralManager實例對象以實現中心角色的app來說,想要添加狀態的保存和恢復功能,只要在初始化時指定CBCentralManagerOptionRestoreIdentifierKey選項,并為中心管理者提供一個字符串作為“恢復標識”就可以了:
myCentralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:@{ CBCentralManagerOptionRestoreIdentifierKey: @"myCentralManagerIdentifier" }];
雖然上面的例子并沒有指出,但是在app里實現周邊管理者的保存和恢復也是類似的操作,實現選項的關鍵字是:CBPeripheralManagerOptionRestoreIdentifierKey,初始化時提供一個字符串作為“恢復標識”就好。
注意:由于一個app可以擁有多個周邊|中心管理者的實例,一定要保證“恢復標識”的唯一性,這樣系統才能在需要的時候區分開來

恢復你的中心和周邊管理者

當你的app在后臺被系統重啟時,你的第一件事就是根據“恢復標識”恢復適當的中心和周邊管理者就像他們第一次創建時一樣。如果你的app只使用了一個中心|周邊管理者,并且這個管理者的生命周期和你的app差不多長,這里你就不需要做其他事了。
如果你的app使用了不止一個中心|周邊管理者,又或者app使用的管理者生命周期沒有app那么長,你的app需要知道哪個管理者在系統重啟app時被恢復了。在實現你的app委托方法application:didFinishLaunchingWithOptions:時使用適當的啟動選項鍵(UIApplicationLaunchOptionsBluetoothCentralsKey||UIApplicationLaunchOptionsBluetoothPeripheralsKey),你可以訪問系統在終止應用程序時為其保留的管理對象的所有恢復標識符列表。
舉個例子,當你的app被系統重啟時,你可以檢索系統為你的應用程序保留的中央管理器對象的所有恢復標識符,像這樣:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSArray *centralManagerIdentifiers = launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey]; ...
在拿到恢復標示符之后,只需要遍歷并恢復適當的中央管理者。
注意:在你的app重啟時,系統僅為正在執行某些藍牙相關任務(而該應用不再運行)中央|周邊管理器提供恢復標識符。這些啟動選項鍵在UIApplicationDelegate協議相關中有詳細介紹.

實現合適的保存委托方法

在你的應用程序中重新配置適當的中央和外圍管理器后,通過將其狀態與藍牙系統的狀態同步來恢復它們。為了使你的應用程序能夠以系統所代表的方式(當它不運行時)加快速度,你必須實現相應的恢復委托方法。對于中心管理者,實現centralManager:willRestoreState:方法;對于周邊管理者,實現peripheralManager:willRestoreState:方法

重要:對于一個實現了藍牙狀態保存和恢復功能選項的app而言,這些方法是在被后臺重啟以執行藍牙相關任務是最先被調用的:(centralManager:willRestoreState: periphetalManager:willRestoreState: ),對于不選擇進行狀態保存的應用程序(或者如果啟動時沒有恢復任何內容),則首先調用centralManagerDidUpdateState:peripheralManagerDidUpdateState:方法。

上面的兩種委托方法,最后一個參數都是一個包含管理者在app上一次被終止時保存的信息的字典。有關字典可用鍵的列表,Central Manager State Restoration Options相關常量在CBCentralManagerDelegate Protocol Reference 中,Peripheral_Manager_State_Restoration_Options相關常量在CBPeripheralManagerDelegate Protocol Reference.
為了恢復CBCentralManager對象的狀態,使用centralManager:willRestoreState:方法提供的參數字典中包含的鍵。舉例來說,如果你的中心管理者對象在上一次被終止時有任何行為或者沒完成的連接,系統會代表你的app繼續監控完成它。就像下面這樣,你可以使用CBCentralManagerRestoredStatePeripheralsKey字典關鍵字以得到所有這個中心已連接或者想要連接的周邊的列表:
- (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary *)state { NSArray *peripherals = state[CBCentralManagerRestoredStatePeripheralsKey]; ...
在上述示例中,使用已恢復的外圍設備列表進行的操作取決于用例。例如,如果您的應用程序保留了中央管理員發現的外圍設備列表,則可能需要將恢復的外圍設備添加到該列表中以保持對其的引用。就像Connecting to a Peripheral Device After You’ve Discovered It小節所描述的,設置一個外設代理,以確保它接收到相應的回調。
您可以通過使用外設管理器在CBPeripheralManager:willRestoreState:方法中提供的字典的鍵,以類似的方式恢復CBPeripheralManager對象。

更新的你初始化過程

在實施了前面三個必須的步驟之后,您可能需要查看更新中央和外圍管理器的初始化過程。通過這個可選步驟,確保您的應用程序運行順利,這一點很重要。例如,你的應用程序在探索連接的周邊設備的數據的過程中可能已被終止。當你的應用程序使用此外設進行恢復時,它將不知道它在終止時的發現過程有多遠。您將需要確保您從發現過程中離開的地方開始。
例如,在centralManagerDidUpdateState:委托方法中初始化您的應用程序時,您可以了解您是否已成功發現已還原外設的特定服務(在應用程序終止之前),如下所示:
NSUInteger serviceUUIDIndex = [peripheral.services indexOfObjectPassingTest:^BOOL(CBService *obj, NSUInteger index, BOOL *stop) { return [obj.UUID isEqual:myServiceUUIDString]; }]; if (serviceUUIDIndex == NSNotFound) { [peripheral discoverServices:@[myServiceUUIDString]]; ...
像上面例子所示:如果系統在完成發現服務之前終止了應用程序,請通過調用discoverServices:開始探索恢復的外圍設備的數據。 如果你的應用程序成功發現服務,你可以檢查是否發現了相應的特征(以及是否已訂閱它們)。 通過以這種方式更新初始化過程,你將確保在正確的時間調用正確的方法。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容