iOS BLE 開發小記[4] 如何實現 CoreBluetooth 後臺執行模式

muhlenXi發表於2019-04-22

歡迎訪問我的部落格 muhlenXi,該文章出自我的部落格,歡迎轉載,轉載請註明來源: muhlenxi.com/2017/05/03/…

導語:

在這一節,主要是 iOS APP 關於藍芽後臺處理方面的知識和經驗。

對於 iOS APP 來說,知道你的 APP 是執行在前臺還是執行在後臺很重要。一個 APP 在後臺執行狀態下的行為表現必須不同於前臺,因為 iOS 裝置的系統資源是有限的。關於 iOS 後臺執行處理的更多論述 請查閱 Background Execution.

預設情況下,當你的 APP 在 background(後臺執行)或者處於 suspended state(暫停狀態)時,無論是 Central 端還是 Peripheral 端,許多常見的 CoreBluetooth 任務是不能被執行的。也就是說,你可以宣告你的 APP 支援 CoreBluetooth 後臺執行模式來允許你的 APP 進入 suspended state 後依然能夠喚醒來處理一些藍芽的事件。即使你的 APP 不需要全面的後臺處理支援,但是當有重要事件發生的時候也需要系統給你發出彈框提醒。

即使你的 APP 不管支援哪一個 CoreBluetooth 後臺執行模式,它也不能一直在後臺執行。在某個時刻,系統會終止你的 APP 來為處於前臺的其他 APP 提供記憶體空間,從而導致一些活動或者連線丟失。比如,對於 iOS 7, CoreBluetooth 支援 Central Manager 和 Peripheral Manager 物件狀態資訊的儲存和在 APP 啟動時恢復該狀態資訊,你可以使用這個功能來支援藍芽裝置的長期操作。

只能前臺執行的 APP

除非你請求允許執行特定的後臺任務,否則對於大多數 iOS APP 來說,當你的 APP 進入 background state(後臺狀態)不久後就會處於 suspended state(暫停狀態)。當你的 APP 處於 suspended state 時是不能執行藍芽相關的任務,也不能感知到任何藍芽事件直到重新進入 foreground(前臺)。

在 Central 端,沒有宣告支援 CoreBluetooth 後臺執行模式的 APP,也就是隻能前臺執行的 APP,在 background 和 suspended 狀態時,是不能搜尋正在廣播資料的 Peripheral。在 Peripheral 端,則是不能進行廣播資料。此時如果一個 Central 嘗試獲取 Peripheral Characteristic 的值會收到一個錯誤。

根據使用情況,這種預設行為可能會以多種方式影響你的 APP,舉個例子,試想一下,當你正在與剛連線的 Peripheral 進行資料互動時,此時你的 APP 進入 suspended 狀態(當使用者切換到另一個 APP時)中,如果 Peripheral 此時失去連線,你是不能感知到已經發生 disconnection(斷開連線)事件了,直到你的 APP 重新進入 foreground 為止。

使用 Peripheral Connection options(選項)

只在前臺執行的 APP 處於 suspended 狀態時,系統會將發生的所有藍芽事件加入佇列,只有當 APP 重新進入前臺時,才會將事件傳遞給 APP,也就是說,當 Central 產生事件時,CoreBluetooth 通過彈框提醒的方式通知使用者。使用者可以通過這個提醒來決定是否將 APP 重新開啟來處理這個特定事件。

當呼叫 CBCentralManager 類的 connectPeripheral:options: 方法連線 Remote Peripheral 時,你可以利用下面這些 Peripheral 連線選項來設定彈框提醒。

  • CBConnectPeripheralOptionNotifyOnConnectionKey -- 如果建立了一個成功的連線,此時 APP 進入 suspended 狀態時,如果你想要系統為給定 Peripheral 顯示一個彈框,你可以在 options 選項中包含這個鍵。
  • CBConnectPeripheralOptionNotifyOnDisconnectionKey -- 如果發生了 disconnection 事件,此時 APP 進入 suspended 狀態時,如果你想要系統為給定 Peripheral 顯示一個彈框,你可以在 options 選項中包含這個鍵。
  • CBConnectPeripheralOptionNotifyOnNotificationKey -- 如果收到了 Peripheral 的通知,此時 APP 進入 suspended 狀態時,如果你想要系統顯示一個來自 Peripheral 的通知彈框,你可以在 options 選項中包含這個件。

關於 Peripheral 連線引數的更多資訊可以查閱 Peripheral Connection Options 常量。

CoreBluetooth 後臺執行模式

如果你的 APP 在後臺執行狀態下也需要執行藍芽任務,你必須在 Info.plist (Infomation property list) 檔案中宣告你的 APP 支援 CoreBluetooth 後臺執行模式。當你宣告後,系統會喚醒 suspended 狀態中的 APP 來處理藍芽事件。這對於和每隔一段時間就傳遞資料的 BLE 裝置進行互動的 APP 來說很重要,比如一個心率監測器。

APP 可能會宣告的 CoreBluetooth 後臺執行模式有兩種。一種是 APP 扮演了 Central 的角色,另一種則是 APP 扮演了 Peripheral 的角色。如果你的 APP 同時扮演了這兩種角色,你可以同時宣告這兩種後臺執行模式。宣告 CoreBluetooth 後臺執行模式的方式就是在 Info.plist 檔案中加入一個 UIBackgroundModes 的 Key,Value 則是包含以下提到的兩種字串的陣列:

  • bluetooth-central -- APP 使用 CoreBluetooth 框架與其他 BLE 裝置進行通訊。
  • bluetooth-peripheral -- APP 使用 CoreBluetooth 框架來分享資料。

提示:Xcode 的屬性列表編輯器預設顯示的 Key 是人類易讀的字元,而不是實際的 Key 的名字,要在 Info.plist 檔案中顯示實際 Key 名,按住 Control鍵並單擊編輯器視窗中的任意 Key,並在彈出的上下文視窗中啟用 Show Raw Keys/Values 項。比如 UIBackgroundModes 的易讀 Key 名則是 Required background modes,如圖所示

Key

關於如何配置 Info.plist 檔案內容的更多資訊請查閱 Xcode help

bluetooth-central 後臺執行模式

當扮演一個 Central 角色的 APP 在 Info.plist 檔案中包含了 UIBackgroundModes bluetooth-central 鍵值對時,CoreBluetooth 框架允許你的 APP 在後臺執行時執行藍芽任務。當你的 APP 在後臺執行時,你仍舊可以搜尋和連線 Peripheral,然後與之進行資料互動。此外,當 CBCentralManagerDelegate 或者 CBPeripheralDelegate 的代理方法進行回撥時,系統會喚醒你的 APP 允許你來處理這些 Central 端的重要事件。比如,成功建立了連線,連線中斷,Peripheral 傳送了一個更新值的通知,或者 Central Manager 的狀態發生改變。

儘管你的 APP 在後臺執行時可以執行許多藍芽任務,但是你要注意這些,在後臺執行狀態下,當你搜尋 Peripheral 的操作是不同於前臺執行狀態的。特別是當你 APP 在後臺執行狀態下搜尋裝置:

  • 搜尋引數 CBCentralManagerScanOptionAllowDuplicatesKey將會被忽略,多個廣播資料的 Peripheral 發現事件將合併成一個發現事件。
  • 如果所有的都在後臺執行中搜尋 Peripheral,則 Central 搜尋廣播資料包的時間間隔將會增加,你發現一個廣播資料的 Peripheral 將會需要很長時間。

這種策略對降低 Radio 的使用頻率和提高 iOS 裝置的續航時間有幫助。

bluetooth-peripheral 後臺執行模式

Peripheral 想要在後臺執行藍芽任務,你必須在 Info.plist 檔案中包含了 UIBackgroundModes bluetooth-peripheral 鍵值對,這樣系統會喚醒你的 APP 來處理讀、寫和訂閱事件。

除了允許你的 APP 喚醒來處理讀、寫和來自 Central 的訂閱請求外,CoreBluetooth 框架還允許你的 APP 在後臺執行狀態下廣播資料。也就是說,你應該注意到,在後臺狀態下廣播資料與在前臺狀態下的操作是不同的。特別是後臺狀態下廣播資料:

  • 廣播的 CBAdvertisementDataLocalNameKey Key 將被忽略,Peripheral 的 local name 也不會廣播。
  • 廣播中 CBAdvertisementDataServiceUUIDsKey 的值的所有服務 UUID 都被放置在 “overflow” 區域中。只有當 iOS 裝置進行顯式搜尋才會被發現。
  • 如果所有 APP 都在後臺執行狀態下廣播資料,你的 Peripheral 廣播資料包的頻率將會降低。

謹慎使用後臺執行模式

對於一般使用者情況,可能不需要宣告 APP 支援其中一個或兩個 CoreBluetooth 後臺執行模式,你應該對後臺程式負責,因為執行藍芽的任務需要使用使用者裝置的 onboard radio(板載廣播),使用廣播會影響使用者裝置的續航時間,應儘量減少在後臺執行藍芽任務。APP 被藍芽時間喚醒後應迅速處理該事件然後儘可能快速的進入 suspended 狀態。

宣告支援 CoreBluetooth 後臺執行模式的 APP 必須遵守一些基本準則:

  • APP 應該提供一個基於會話的介面來允許使用者決定什麼時候開始和結束髮送藍芽事件。
  • 一個 APP 被喚醒時有 10秒 來完成一個任務,理想地,APP 應該儘快的完成任務然後再次進入 suspended 狀態。APP 在後臺執行時間太長會被系統限制或殺死。
  • APP 不應該一直處於喚醒狀態來處理與系統喚醒無關的額外任務。

在後臺執行狀態下,你的 APP 應該如何操作的詳情請查閱 Being a Responsible Background App.

在後臺執行長期操作

一些 APP 可能需要使用 CoreBluetooth 框架來在後臺執行狀態下執行長期操作,比如,你想要為 iOS 裝置開發一個 安全家庭 APP 來與配備 BLE 技術的門鎖進行通訊。APP 與門鎖互動來自動鎖門(當使用者離開家的時候)和開鎖(當使用者回家的時候),整個期間 APP 一直在後臺執行狀態下。當使用者離家的時候,iOS 裝置終會超出門鎖的有效範圍,導致與門鎖的連線中斷。在這種情況下,APP 可以通過呼叫 CBCentralManager 類的 connectPeripheral:options:該方法來建立連線,因為連線請求不會超時,這樣當使用者到家的時候就會與門鎖重新連線。

現在試想一種情況,當使用者離家有一些天了,如果此時 APP 被系統給終止了。這樣當使用者回家的時候,APP 將不能與門鎖重新連線,使用者也不能開啟門鎖了。類似這樣的 APP,使用 CoreBluetooth 來執行長期操作至關重要,例如監視活動和掛起連線。

狀態儲存和恢復

因為狀態儲存和恢復已經內建於 CoreBluetooth,你的 APP 可以選擇加入這個功能來要求系統儲存 APP 的 Central Manager 和 Peripheral Manager 的狀態,並繼續代表他們執行藍芽任務,即使你的 APP 不在執行,當這些任務完成後,系統會重新讓你的 APP 進入後臺執行狀態並給予一定的時間來儲存狀態和處理一些藍芽事件。對於上述描述的安全家庭 APP 這種情況來說,系統將會監聽連線請求,當使用者回家的時候,系統將會重新啟動 APP 來處理 centralManager:didConnectPeripheral: 代理回撥和完成連線請求。

CoreBluetooth 對 扮演 Central 角色,Peripheral 角色,或者同時都扮演的 APP 都提供狀態儲存和恢復支援。當扮演 Central 角色的 APP 增加狀態儲存和恢復時,當系統釋放記憶體空間需要終止你的 APP 時,會儲存你的 Central Manager 的狀態,如果你的 APP 擁有多個 Central Manager,你可以選擇你想要系統跟蹤的 Central Manager,尤其,對於給定的 CBCentralManager 物件,系統會跟蹤:

  • Central Manger 搜尋的 Service(和一些一開始指定搜尋引數選項的)
  • Central Manager 嘗試連線或者已經連線的 Peripheral
  • Central Manager 訂閱的 Characteristic

扮演 Peripheral 角色的 APP 同樣可以使用狀態儲存與恢復。對於 CBPeripheralManager 物件,系統會跟蹤:

  • Peripheral Manager 廣播的資料
  • Peripheral Manager 釋出到裝置庫中的 Services 和 Characteristics
  • 被 Central 訂閱的 Characteristic 的值

當你的 APP 被系統置入後臺執行狀態時,你可以重新例項化 APP 的 Central Manager 和 Peripheral Manager 並恢復他們的狀態。下面的章節會詳細的描述如何在你的 APP 中使用狀態儲存和恢復。

新增狀態儲存和恢復支援

CoreBluetooth 的狀態儲存和恢復是選擇性加入的功能,需要以下步驟來讓 APP 這一功能生效,你可以參考一下的步驟來給你的 APP 增加狀態儲存和恢復功能:

  • 1、(必須的)當你 allocate 和 initialize 一個 Central Manager 或 Peripheral Manager 物件時需要加入狀態儲存和恢復。這一步在 選擇加入狀態與恢復 小節有詳細描述。
  • 2、(必須的)當系統啟動 APP 的時候需要重新例項化一些 Central Manager 或 Peripheral Manager 物件。這一步在 重新例項化你的 Central Manager 和 Peripheral Manager 小節有詳細描述。
  • 3、(必須的) 實現恰當的代理方法。這一步在 實現恰當的 Restoration Delegate 方法 小節有詳細描述。
  • 4、(可選的)更新你的 Central Manager 或 Peripheral Manager 的初始化程式。這一步在 更新你的初始化程式 小節中有詳細描述。
選擇加入狀態儲存與恢復

選擇加入狀態儲存和恢復功能後,當你 allocate 和 initialize 一個 Central Manager 或者 Peripheral Manager 的時候,你需要提供一個唯一的恢復識別符號。恢復識別符號是一個標識 Central Manager 或者 Peripheral Manager 的字串,字串的值對你的程式碼很重要,它將會告訴 CoreBluetooth 需要儲存使用該標記的物件,CoreBluetooth 只儲存這些擁有恢復識別符號的物件。

舉個例子,為你的 APP (只有一個 CBCentralManager 物件的 Central )選擇加入狀態儲存和恢復,當你初始化 Central Manager 的時候需要指定 options(初始化選項),選項是一個以 CBCentralManagerOptionRestoreIdentifierKey 為 key,Value 是 恢復識別符號 的字典。示例程式碼如下:

myCentralManager =
    [[CBCentralManager alloc] initWithDelegate:self queue:nil
     options:@{ CBCentralManagerOptionRestoreIdentifierKey:
     @"myCentralManagerIdentifier" }];
複製程式碼

雖然上述例子沒有證明這一點,當你給使用 Peripheral Manager 物件的 APP 選擇加入狀態儲存與恢復時應使用類似的方式:在你初始化 Peripheral Manager 的時候指定 options(初始化選項),選項是一個以 CBCentralManagerOptionRestoreIdentifierKey 為 key,Value 是 恢復識別符號 的字典。

提示:因為一個 APP 可能擁有多個 CBCentralManager 和 CBPeripheralManager 物件的例項,你應該確保他們的恢復識別符號是唯一的,這樣系統就能正確地區分它們。

重新例項化你的 Central Manager 和 Peripheral Manager

當你的 APP 被系統從後臺啟動時,你需要做的第一件事就是用狀態恢復識別符號重新例項化合適的 Central Manager 和 Peripheral Manager,恢復識別符號要跟它們第一次被建立的一樣。如果你的 APP 僅僅用到了一個 Central Manager 或者 Peripheral Manager,並且該 Manager 在你的 APP 生命週期中還存活著,則你就不需要做這一步。

如果你的 APP 使用超過一個以上的 Central Manager 或者 Peripheral Manager,或者使用的 Manager 在 APP 的生命週期中已經死亡了。當你的 APP 被系統啟動時,它需要知道要恢復哪一個 Manager 。你可以通過 APP 終止時儲存的 Manager 物件的恢復識別符號列表,使用合適的 launch option(啟動選項)引數(UIApplicationLaunchOptionsBluetoothCentralsKey 或者 UIApplicationLaunchOptionsBluetoothPeripheralsKey)在 application:didFinishLaunchingWithOptions: 代理方法回撥中來獲取恢復識別符號陣列。

舉個例子,當系統啟動 APP 時,你可以恢復所有 Central Manager 的恢復識別符號,示例程式碼如下:

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
 
    NSArray *centralManagerIdentifiers =
        launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey];
    // ...
}
複製程式碼

當你獲得恢復識別符號列表後,遍歷這個陣列來重新例項化合適的 Central Manager 物件。

提示:當 APP 啟動時,系統只會提供要執行一些藍芽任務(此時 APP 沒有在執行)的 Central Manager 和 Peripheral Manager 的恢復識別符號。關於啟動選項引數 Keys 的詳情可以參考 UIApplicationDelegate Protocol Reference. .

實現恰當的 Restoration Delegate 方法

當你恢復合適的 Central Manager 和 Peripheral Manager 後,然後將他們的狀態與藍芽系統的狀態同步。為了使你的 APP 達到系統處理的速度,你必須實現恰當的 Restoration Delegate 方法,對 Central Manager ,需要實現 centralManager:willRestoreState: 代理方法,而對於 Peripheral Manager,則需要實現 peripheralManager:willRestoreState: 代理方法。

重要提示:對於選擇新增 CoreBluetooth 的狀態儲存與恢復的 APP,當 APP 從後臺啟動來完成一些藍芽任務時,會第一個呼叫 centralManager:willRestoreState:peripheralManager:willRestoreState: 代理方法,對於沒有加入狀態與恢復的 APP,會第一個呼叫 centralManagerDidUpdateState:peripheralManagerDidUpdateState: 代理方法。

以上提到的這些代理方法,最後一個引數是一個字典,該字典包含 APP 終止時系統儲存的 Manager 的資訊。字典中可用的 Keys,可以查閱 Central Manager State Restoration OptionsPeripheral_Manager_State_Restoration_Options 常量。

centralManager:willRestoreState: 代理方法提供的字典的 Key 來恢復 CBCentralManager 物件的狀態。舉個例子,如果你的 Central Manager 擁有啟用的或者中斷的連線(APP 終止時),系統會繼續監視 APP 的行為,如下所示,你可以通過 CBCentralManagerRestoredStatePeripheralsKey Key 去得到 Central Manager 連線的或者嘗試連線的 Peripheral 列表(用 CBPeripheral 物件表示)。

- (void)centralManager:(CBCentralManager *)central
      willRestoreState:(NSDictionary *)state {
 
    NSArray *peripherals =
        state[CBCentralManagerRestoredStatePeripheralsKey];
    // ...
}
複製程式碼

當你獲得 Peripheral 列表後然後幹什麼取決於使用情況。舉個例子,如果 APP 有一個 Central Manager 搜尋的 Peripheral 列表,你可能想把恢復的 Peripheral 加入該列表中。此時確保要設定 Peripheral 的 Delegate 以保證能收到合適的代理回撥。

你可以通過類似的方法利用 peripheralManager:willRestoreState: 代理方法提供的字典的 keys 來恢復 CBPeripheralManager 的狀態。

更新你的初始化程式

當你實現前面必須的步驟後,你可能想要檢視 Central Manager 和 Peripheral Manager 的初始化程式。儘管這是一個可選的步驟,但對確保 APP 穩定執行很重要。舉個例子,當正與連線的 Peripheral 資料傳輸到一半時,APP 終止了。當你的 APP 恢復這個 Peripheral 時,你不知道 APP 終止時資料處理到哪一步,你想要確定從哪裡繼續開始處理。

舉個例子,當在 centralManagerDidUpdateState: 代理方法中初始化 APP 時,如果你能夠成功的從一個恢復的 Peripheral中找出指定的 Service(在 APP 終止之前),就像這樣

NSUInteger serviceUUIDIndex =
    [peripheral.services indexOfObjectPassingTest:^BOOL(CBService *obj,
    NSUInteger index, BOOL *stop) {
        return [obj.UUID isEqual:myServiceUUIDString];
    }];
 
if (serviceUUIDIndex == NSNotFound) {
    [peripheral discoverServices:@[myServiceUUIDString]];
   // ...
}
複製程式碼

以上示例中,如果系統在你呼叫 discoverServices: 方法完成 Servic 搜尋之前終止。如果 APP 搜尋 Service 成功,你可以稍後檢視是否發現合適的 Characteristic(一直訂閱的) ,用這種方式來更新初始化程式,你需要在正確的時間呼叫正確的方法。

參考文獻

1、Core Bluetooth Background Processing for iOS Apps

結束語

歡迎在本文下面留言一起交流心得...

相關文章