歡迎訪問我的博客 muhlenXi,該文章出自我的博客,歡迎轉載,轉載請注明來源: http://muhlenxi.com/2017/05/03/iOS-Bluetooth-Low-Energy-Develop-Chapter4。
導語:
在這一節,主要是 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
,如圖所示
關于如何配置 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 Options 和 Peripheral_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
結束語
歡迎在本文下面留言一起交流心得...