Core Bluetooth 中iOS应用程序的后台处理
对于iOS应用程序,知道你的应用程序是在前台或后台运行是很重要的。一个应用程序在后台比在前台必须表现得更不同,因为在iOS设备上系统的资源是有限制的。在iOS中关于多重任务的全部讨论,请看在App Programming Guide for iOS中的App States and Multitasking
默认情况下,很多普遍的Core Bluetooth任务--在中央端和外设端两者中--当你的应用程序处于后台或悬浮状态时都是无效的。也就是说,你可以宣布你的应用程序来支持Core Bluetooth后台执行模式来允许你的应用程序从悬浮状态唤醒来处理一个确定的蓝牙相关的事件。甚至如果你的应用程序不需要后台进程支持的所有范围,它仍然可以在重要的事件发生时由系统给出一个警告。
即使你的应用程序支持一个或两个Core Bluetooth后台执行模式,它也不会一直运行。在有些时候,例如,系统可能需要为处于前台的应用程序而终止你的应用程序来释放资源-导致任何活动或等待连接丢失。对于iOS7,Core Bluetooth支持为中央管理者对象和外设管理者对象保存状态信息,并在应用程序启动时恢复状态。你可以用这个特征来支持一个长期的活动与蓝牙设备交互。
应用程序只处于前台时
对于大多数iOS应用程序,除非你请求允许执行指定后台任务,你的应用程序在进入后台之后会很快转变为悬浮状态。当在悬浮状态下,你的应用程序不能执行蓝牙相关任务,也不会知道任何蓝牙相关的事件直到它在前台唤醒。
在中央端,只处于前台的应用程序--没有宣布支持任何一种Core Bluetooth后台执行模式--在处于后台或悬浮状态时不可以扫描和发现正在广播的外设。在外设端,不能够广播,并且任何中央尝试访问一个应用程序发布服务的动态特征值会接受到一个错误。
根据用例,这种默认的行为可能在很多方面会影响你的应用程序。作为一个例子,想象你正在与一个你当前连接的外设交互数据。现在想象你的应用程序进入悬浮状态(因为,比如,用户切换到另一个应用程序)。如果当你的应用程序是悬浮的时候连接的外设丢失,你将意识不到任何断开连接的事件直到你的应用程序在前台唤醒。
利用外设连接的选项
当一个只允许前台模式的应用程序处于悬浮状态时所有与蓝牙相关的发生事件会被系统放在队列中,然后只有当应用程序处于再次处于前台时将它们传递给应用程序并恢复。也就是说,Core Bluetooth提供了一个方式当确定的中央角色事件发生时来提醒用户。用户可以使用这些提醒来决定是否要根据一个特定的事件将应用程序带回前台。
你可以利用这些提醒在调用CBCentralManager
类的connectPeripheral:options:
方法来连接远程外设时通过下面外设连接选项中的一个。
-
CBConnectPeripheralOptionNotifyOnConnectionKey
--包含这个键表示如果你想要在连接成功后应用程序处于悬浮状态时系统给你的一个给定的外设显示一个警告。 -
CBConnectPeripheralOptionNotifyOnDisconnectionKey
--包含这个键表示如果你想要在断开连接应用程序处于后台时系统给你的一个给定的外设显示一个断开连接的警告。 -
CBConnectPeripheralOptionNotifyOnNotificationKey
--包含这个键表示如果你想要在应用程序处于悬浮状态时系统从给定的外设接受到通知时显示一个警告。
关于外设连接选项的更多信息,请看Peripheral Connection Options常量,详细在CBCentralManager Class Reference中。
Core Bluetooth后台执行模式
如果你的应用程序需要在后台运行来执行确定的蓝牙相关任务,必须在Information property list(Info.plist)
文件中指定它支持Core Bluetooth后台执行模式。当你的应用程序制定了这个,系统从悬浮状态唤醒来允许它执行蓝牙相关任务,这个支持对于应用程序在与蓝牙低功耗设备交互时在有规律的间隔时间里传递数据是很重要的,比如心率监测器。
有两个应用程序可以指定的Core Bluetooth后台执行模式--一个是为应用程序实现中央角色,另一个是为应用程序事件外设角色。如果你的应用程序实现了它们两个角色,可以同时指定它支持两个后台执行模式。这个Core Bluetooth后台执行模式是通过在你的Info.plist
文件中添加UIBackgroundModes
键来添加,并且设置这个键的值到一个数组容器中如下面的字符串:
-
bluetooth-central
--使用Core Bluetooth框架与蓝牙低功耗外设交互的应用程序。 -
bluetooth-peripheral
--使用Core Bluetooth框架分享数据的应用程序。
注意:在Xcode中这个属性列表默认为很多键显示人类可读的字符串来替代一个真实的键名。在
Info.plist
文件中显示一个真实的键名,在编辑窗口控制点击任何一个键并在上下文窗口显示键值对的条目。
关于如何配置你的Info.plist
文件中的内容更多信息,请看Property List Editor Help。
蓝牙-中央后台执行模式
当一个应用程序实现了中央角色包括在它的Info.plist
文件中添加了UIBackgroundModes
键和对应的bluetooth-central
值,这个Core Bluetooth框架会允许你的 应用程序在后台运行执行确定的蓝牙相关任务。当你的应用程序处于后台时仍然可以发现和连接外设,并探索和与外设数据交互。另外,当任何CBCentralManagerDelegate
或CBPeripheralDelegate
代理方法被调用时系统会唤醒你的应用程序,允许你的应用程序来处理重要的中央角色事件,例如当连接被建立或减弱时,当一个外设发送更新特征值时,当中央管理者状态改变时。
虽然你可以在你的应用程序处于后台时执行很多蓝牙相关任务,但请记住你的应用程序处于后台时的扫描外设操作是与你的应用程序处于前台时是不同的。特别的,当你的应用程序在后台正在扫描设备时:
-
CBCentralManagerScanOptionAllowDuplicatiesKey
键选项将被忽视,并且多个发现正在广播的外设会合并成单个的发现事件。 - 如果所有的应用程序在后台时正在扫描外设,你的中央设备扫描正在广播包的时间间隔会增多。因此,它可能需要更长的时间来发现正在广播的外设。
这些改变会有助于在你的iOS设备上最小化无线的使用和提供电池的寿命。
蓝牙-外设后台执行模式
为了在后台执行确定的外设角色任务,你必须在你的Info.plist
文件中添加UIBackgroundModes
键对应的值bluetooth-peripheral
。当在应用程序的Info.plist
文件中包含这对键值时,系统会唤醒你的应用程序来处理读,写和订阅事件。
除了允许你的应用程序被唤醒来处理来自连接的中央的读,写和订阅请求之外,Core Bluetooth
框架还允许你的应用程序在处于后台时发出广播。也就是说,你需要意识到当你的应用程序处于后台操作广播时是与你的应用程序处于前台模式时是不同的。特别的,当你的应用程序在后台广播时:
-
CBAdvertisementDataLocalNameKey
广播键是被忽略的,设备的本地名不会被广播。 - 所有服务的UUIDs包含
CBAdvertisementDataServiceUUIDsKey
广播键对应的值会被一个特定的溢出区域代替。它们只能被指定扫描它们的iOS设备发现。 - 如果所有的应用程序在后台时广播,你的外设发送广播包的频率会减少。
明智地使用后台执行模式
虽然在一个特殊使用情况下指定你的应用程序支持一个或两个Core Bluetooth后台执行模式可能是需要的,但你应该负责任地执行后台程序。因为执行大量的蓝牙相关的任务需要iOS设备的无线使用是活跃的-但相反无线的使用不利于iOS设备的电池寿命--尽量最小化处于后台工作的次数。为任何蓝牙相关的事件唤醒应用程序应该尽可能快的处理它们并返回,以至于应用程序可以再次被挂起。
任何应用程序制定支持Core Bluetooth后台执行模式必须遵守下面几个基本的指导方针:
- 应用程序应该有基本的会话或提供接口来允许用户来决定什么时候来开始和停止处理蓝牙相关事件。
- 在被唤醒后,应用程序有十秒钟的时间来完成任务。理想状态下,它应该尽可能快的完成任务并允许它自己被再次挂起。应用程序在后台花费太多时间运行会被系统限制或杀死。
- 应用程序不应该有机会被唤醒来执行一个不相关的外来任务因为应用程序是由系统唤醒的。
关于你的应用程序应该如何在后台状态下表现的更多信息,请看做一个负责任的后台应用程序在App Programming Guide for iOS。
在后台执行长期的动作
有些应用程序可能需要使用Core Bluetooth框架在后台执行长期的动作。作为一个例子,想象你正在开发一个家庭安全的应用程序为iOS设备与门锁交互(装备有蓝牙低功耗技术)。在用户离开家的时候这个应用程序自动将门锁锁住并在用户返回家的时候将门锁打开--所有的过程都是在后台执行。在用户离开家时,应用程序最终可能离开锁的范围,造成与锁失去连接。在这一点上,可以简单地调用CBCentralManager
类的connectPeripheral:options:
方法,因为连接请求不会超时,iOS设备会在用户返回家的时候重连上。
现在想象离开家几天。如果在用户离开的时候应用程序被系统终止,这个应用程序将不可能在用户返回家的时候重连上锁,用户也不可能打开门锁。像这样的应用程序,关键是能够继续使用Core Bluetooth来执行长期动作,就像监测活动和挂起的连接。
状态保存和恢复
因为状态的保存和恢复是建立在Core Bluetooth中,你的应用程序能够选择这个特征让系统保存你的应用程序的中央管理者和外设管理者的状态并在它们的行为上继续执行蓝牙相关任务,甚至当你的应用程序不再运行了。当其中一个任务完成了,系统会重启你的应用程序进入后台,并让你的应用程序有机会恢复它们的状态和处理恰当的事件。在上面描述的家庭安全应用程序的情况下,系统会监测连接请求,并在用户返回家完成连接请求时重启应用程序处理centralManager:didConnectPeripheral:
代理的回调。
Core Bluetooth支持实现中央角色,外设角色或两者都有的应用程序的状态保存和恢复。当你的应用程序实现了中央角色并添加支持状态保存和恢复,在系统为释放内存终止你的应用程序时会保存你的中央管理者的状态(如果你的应用程序有多个中央管理者,你可以选择让你的系统保存其中一个)。特别地,对于一个给定的CBCentralManager
对象,系统会跟踪:
- 中央管理者已经扫描到服务(当开始扫描时制定扫描选项)
- 中央管理者已经尝试连接或已经连接过的外设
- 中央管理者已经订阅了特征
实现了外设角色的应用程序同样可以利用状态保存和恢复。对于CBPeripheralManager
对象,系统会跟踪:
- 外设管理者已经广播了数据
- 外设管理者已经给设备数据库发布了服务和特征
- 中央已经订阅了你的特征值
当你的应用程序被系统在重启进入后台(因为你的应用程序已经扫描发现的外设),你可以重新实例化你的应用程序的中央和外设管理者并恢复它们的状态。接下来的部分详细地描述在你的应用程序上怎样使用状态保存和恢复。
为状态保存和恢复添加支持
在Core Bluetooth中状态保存和恢复是可选的特征并对你的应用程序工作有必要的帮助。你可以通过下面的步骤在你的应用程序上的这个特征添加支持:
- (必须)当你给中央和外设管理者对象分配内存和初始化时选择状态保存和恢复。这一步在Opt in to State Preservation and Restoration有描述。
- (必须)在你的应用程序被系统重启时重新初始化任何中央或外设管理者对象。这一步在Reinstantiate Your Central and Peripheral Managers有描述。
- (必须)实现恰当的恢复代理方法。这一步在Implement the Appropriate Restoration Delegate Method有描述。
4.(可选)更新你的中央和外设管理者的初始化过程。这一步在Update Your Initialization Process有描述。
选择状态保存和恢复
为了选择状态保存和恢复特征,当你分配内存和初始化一个中央或外设管理者时简单地提供一个唯一的恢复标识符。一个恢复标识符是一个字符串,标识着你的应用程序和Core Bluetooth中的中央或外设管理者。这个字符串的值只对你的代码有重要意义,但是这个字符串的存在告诉了Core Bluetooth它需要保存标识对象的状态。Core Bluetooth只保存有一个恢复标识符的对象状态。
例如,在一个只使用一个CBCentralManager
对象的实例来实现中央角色的应用程序上选择状态保存和恢复,在你给它分配内存和初始化时指定CBCentralManagerOptionRestoreIdentifierKey
初始化选项并给中央管理者提供一个标识符。
myCentralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:@{CBCentralManagerOptionRestoreIdentifierKey: @"myCentralManagerIdentifier"}];
虽然上面的例子不能阐述这些,你可以在一个应用程序选择状态保存和恢复使用外设管理对象在相似的方式:在你分配内存和初始化任何外设管理者对象时指定CBPeripheralManagerOptionRestoreIdentifierKey
初始化选项并提供一个恢复标识符。
注意:因为应用程序可以有多个
CBCentralManager
和CBPeripheralManager
对象的实例,确保任何恢复标识符是唯一的,以至于系统能够从其它对象中区分一个中央(或外设)管理者
重新初始化你的中央和外设管理者
当你的应用程序被系统重启进入后台时,你需要做的第一件事是使用相同的恢复标识符来重新实例化恰当的中央和外设管理者当它们已经有过被第一次创建了。如果你的应用程序只有一个中央或外设管理者,并且这个管理者存在于你的应用程序的生命周期里,你不需要为这一步做更多的事。
如果你的应用程序使用了超过一个的中央或外设管理者,活着如果使用一个管理者不在你的应用程序的生命周期范围内,你的应用程序需要知道当被系统重启时哪一个管理者被重新初始化。你可以访问所有恢复标识符的一个列表为系统在它被终止时保存的管理者对象,通过使用恰当的启动选项键(UIApplicationLaunchOptionsBluetoothCentralsKey
或UIApplicationLaunchOptionsBluetoothPeripheralsKey
)在实现你的应用程序代理的application:didFinishLaunchingWithOptions:
方法中。
例如,当你的应用程序被系统重启,你可以获得系统为你的应用程序保存的中央管理者对象的恢复标识符。就像:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSArray *centralManagerIdentifiers = launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey];
...
}
在你有了这个恢复标识符列表后,通过它们简单的循环来重新初始化恰当的中央管理者对象。
注意:当你的应用程序重启时,系统提供的恢复标识符只是为了执行一些蓝牙相关任务的中央和外设管理者(当应用程序不再运行时),这些启动选项键在UIApplicationDelegate Protocol Reference中有详细的描述。
实现恰当的恢复代理方法
当你在你的应用程序里已经重新实例化了恰当的中央和外设管理者后,通过蓝牙系统的状态同步它们的状态来恢复它们。为了给你的应用程序带来与系统已经处理它的表现的速度(在它没有运行的时候),你必须实现恰当的恢复代理方法。对于中央管理者,实现centralManager:willRestoreState:
代理方法;对于外设管理者,实现peripheralManager:willRestoreState:
代理方法。
重点:对于应用程序选择Core Bluetooth的状态保存和恢复特征,当你的应用程序重启到后台来完成一些蓝牙相关任务时第一方法(
centralManager:willRestoreState:
和peripheralManager:willRestoreState:
)会被调用。对于没有选择保存状态的应用程序(或者如果没有恢复启动),centralManagerDidUpdateState:
和peripheralManagerDidUpdateState:
代理方法会被第一个调用。
在以上的两个代理方法中,最后一个参数是一个字典,包含在应用程序被终结时保存了状态的管理者的信息。对于有效字典键的列表,请看在CBCentralManagerDelegate Protocol Reference中的Central Manager State Restoration Options常量和在CBPeripheralManagerDelegate Protocol Reference中的Peripheral_Manager_State_Restoration_Options常量。
为了恢复CBCentralManager
对象的状态,使用在centralManager:willRestoreState:
代理方法中提供的字典的键。作为一个例子,如果你的中央管理者对象已经活跃或等待连接的应用程序被终止,系统会继续监视它们在你的应用程序的行为。如下所示,你可以使用CBCentralManagerRestoredStatePeripheralKey
字典键来获得所有中央管理者已经连接过或尝试连接的外设(使用CBPeripheral
对象表示)的列表。
- (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary *)state {
NSArray *peripherals = state[CBCentralManagerRestoredStatePeripheralsKey];
...
}
在上面的例子中你用这些恢复外设的列表做什么依赖与实际使用情况。例如,如果你的应用程序获得了中央管理者发现的外设列表,你可能想要恢复外设添加到列表来保持引用它们。正如在Connecting to a Peripheral Device After You've Discovered It中的描述,设置一个外设的代理确保能接受到它恰当的回调。
你可以用相似的方式通过使用peripheralManager: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:
方法开始探索恢复的外设数据。如果你的应用程序成功地发现了服务,你可以检查看看发现服务的恰当的特征(和你已经订阅了它们)。在这个方式上通过更新你的初始化过程,你将确保你在正确的时间调用正确的方法。
--翻译的文档地址: Core Bluetooth Background Processing for iOS Apps