原文:http://blog.csdn.net/swibyn/article/details/52096646
Core Bluetooth Background Processing for iOS Apps
iOS蓝牙应用的后台处理
对于ios应用,你必须要清楚它是在前台运行,还是在后台运行。因为资源有限,你要对这两种模式区别处理。
默认情况下,当应用进入后台或挂起时,蓝牙任务是不执行的。但是,你可以把应用声明为支持蓝牙后台执行模式,这样当有蓝牙相关事件发生时,你的应用就可以被唤醒来处理任务。即使你的应用不要求后台处理支持,当有重要的事件发生时,系统仍然可能跳出警告,要求处理。
即使你的应用支持一种或两种都支持后台执行模式,也不是就一定能永远执行。在某些情况下,系统可能终止你的应用以便为前台应用让出内存,这将导致当前活动和连接等信息丢失。自ios7之后,蓝牙库支持保存状态信息,并可在下次启动app时还原状态信息。你可以通过这个特性来实现长连接。
Foreground-Only Apps
只支持前台运行的应用
大部分的apps,除非你要求后台运行,在进入后台后,应用会很快被挂起。在挂起状态下,应用无法处理蓝牙相关任务,无法接收蓝牙事件。直到重新回到前台。
在central端,只支持前台运行的应用,在进入后台或被挂起时就无法扫描和发现peripheral的广播包。如果是在peripheral端,广播将停止,任何central想访问characteristic的值都将收到异常信息。
不同情况下,默认的行为可能会影响你的程序。比如,在你与peripheral交互数据时,应用挂起(比如用户切到另一个应用)。这时连接可能会断开,你并不会收到通知,直到应用重新激活。
Take Advantage of Peripheral Connection Options
利用peripheral连接选项
只支持前台的蓝牙应用在挂起后发生的蓝牙事件会被系统排队,并在应用进入前台时把事件发给应用。当特定的central事件发生时,蓝牙库可以提供一种方式来提示用户。用户可以根据这些提示来决定是否激活应用。
若想利用使用这些提示,你需要在调用connectPeripheral:options: 方法时传入如下参数。
CBConnectPeripheralOptionNotifyOnConnectionKey
: 在应用挂起后,与指定的peripheral成功建立连接,则发出通知
CBConnectPeripheralOptionNotifyOnDisconnectionKey
: 在应用挂起后,如果与指定的peripheral断开连接,则发出通知
CBConnectPeripheralOptionNotifyOnNotificationKey
: 在应用挂起后,指定的peripheral有任何通知都进行提示
Core Bluetooth Background Execution Modes
后台执行模式
如果你的应用在后台时也需要处理蓝牙事件,就必须在Info.plist中声明应用要支持蓝牙后台模式,这样,当有蓝牙事件发生时,系统会唤醒应用来处理。
有两种蓝牙后台模式,一种为central角色,另一种为peripheral角色。如果应用需要两种角色,则可以声明支持两种模式。
声明方式:增加UIBackgroundModes 键,并增加包含下列字符串的array值。
• bluetooth-central
—The app communicates with Bluetooth low energy peripherals using the Core Bluetooth framework.
• bluetooth-peripheral
—The app shares data using the Core Bluetooth framework
注意:Info.plist中会显示为更加人性化的文本,不是直接显示实际的键值对。如要显示实际值,可右键,或control点击,在弹出菜单中选择Show Raw Keys/Values
The bluetooth-central Background Execution Mode
支持central后台运行的模式
如果你的应用支持central角色的后台模式,也就是Info.plist中UIBackgroundModes键的值中包含bluetooth-central值。那么应用将可以在后台处理特定的蓝牙相关事件。即使在后台,你仍然可以发现和连接peripherals,可以检索和读写数据。并且当有CBCentralManagerDelegate or CBPeripheralDelegate 代理事件发生时,系统会唤醒应用来处理。
需要注意的是,进入后台时,扫描的处理有些区别: 1, CBCentralManagerScanOptionAllowDuplicatesKey
这个键会被忽略,多次发现同一peripheral会被合并成一个发现事件。 2,如果所有扫描中的应用都在后台,那么你应用的扫描间隙会延长。结果是,扫描到peripheral的时间可能会延长。
这样做是为了减少辐射节省电量。
The bluetooth-peripheral Background Execution Mode
支持peripheral后台运行的模式
如果要支持peripheral角色的后台模式,你需要在Info.plist中的增加UIBackgroundModes键并在值中包含bluetooth-peripheral值。这样系统会唤醒应用来处理读写和订阅事件。
蓝牙框架(Core Bluetooth framework)不仅允许你的应用被唤醒来处理读写和订阅请求,还允许你的应用在后台状态下发送广播。但你必须注意后台时广播与前台时广播是不同的。即便如此,你必须注意后台与前台时广播处理的区别。特别是当你的应用需要在后台发送广播。
1,CBAdvertisementDataLocalNameKey
这个键会被忽略,并且peripheral的local name不会被广播
2,CBAdvertisementDataServiceUUIDsKey
的值中包含的所有service uuids都会被放到“overflow”区域;只有ios设备显示指明在搜索它时才会搜索到这些值。
3,如果所有的处于广播状态的应用都在后台,广播频率将降低。
Use Background Execution Modes Wisely
明智使用后台运行模式
虽然为了完成某些事情,有必要把你的应用声明成支持后台运行模式,你也应该要能有效处理后台任务。因为执行蓝牙任务会使用无线电,从而耗费电池电量,所以尽量最小化后台任务。应用被蓝牙事件唤醒后应能尽快处理好任务,以便被重新挂起。
支持后台运行的任务要遵循几个原则 1,应用应该是基于会话的,并提供接口让用户决定何时开始或停止蓝牙事件。 2,应用被唤醒后,大约有10秒钟的时间来完成任务,所以应该尽快完成任务并重新挂起。若在后台花费太多时间,则将受到系统的遏制甚至被扼杀。 3,应用不应该使用这种被唤醒的机会来执行与之无关的事情。
Performing Long-Term Actions in the Background
后台长时间执行
一些应用需要长时间后台运行。举个例子,你可发一款家庭安全应用,ios设备与蓝牙门锁通讯。当用户离开家时自动锁门,当用户回到家时门自动打开,整个过程应用都是后台运行。当用户离开家时,ios与门锁断开连接。这是应用只简单调用connectPeripheral:options:
,因为连接没有超时,ios设备将在用户回到家时重新连接上。 假设用户离开家好几天,并假设app被系统终止,应用将无法在用户回到家时重连门锁,这时用户将无法开门。对于这类应用,很重要的一点要能够继续使用蓝牙执行长时事件,如管理活动和悬停连接。
State Preservation and Restoration
状态保存和还原
因为状态保存和还原是蓝牙内在支持的,你的应用可选择支持这一特征来让系统保存central和peripheral manager的状态,并继续执行蓝牙任务,即使你的应用不在运行。当任务完成,系统重新激活应用到后台,让你的应用有机会还原状态并处理事件。上面说的家庭安全应用,系统可以管理连接请求,并在用户回到家重新连接上蓝牙时重新激活应用来处理 centralManager:didConnectPeripheral:
代理回调。
蓝牙库支持状态保存和还原,支持central角色,peripheral角色。当应用实现central角色并增加支持状态保存和还原,系统就会在终止应用释放内存前保存central manager对象的状态,如果应用有多个central managers,你可选择哪些对象你希望系统为你维护。对于CBcentralManger 对象,系统维护这些:
1,central manager扫描的services和对应的options
2,已连接的和未连接上的peripherals
3,订阅的characteristics
实现peirpheral角色的应用类似处理。对于CBPeripheralManager对象,系统维护这些:
1,广播的数据
2,peripheral manager发布到设备数据库的services和characteristic
3,那些订阅了你characteristics的值得centrals
当应用被系统重新激活到后台,假如应用之前有发现peripheral,你可以重新创建应用的central和peripheral manager,并还原他们的状态。后面将继续说明如何利用状态保存与还原。
Adding Support for State Preservation and Restoration
添加状态保存与还原的支持
这一特性是可选的,增加步骤如下:
1,(必须)在创建和初始化时选择支持状态保存和还原。Opt In to State Preservation and Restoration 这一节将更详细描述
2,(必须)在应用被系统唤醒时复原central或peripheral manager对象。Reinstantiate Your Central and Peripheral Managers 这里将继续描述
3,(必须)实现还原代理方法。Implement the Appropriate Restoration Delegate Method. 这里将继续说明
4,(可选)更新central和peripheral managers的初始化过程。Update Your Initialization Process。这里将继续说明
Opt In to State Preservation and Restoration
状态保存和还原的设置
在创建和初始化时,提供唯一的还原id。还原id是字符串,对于蓝牙库和应用来说,还原id是用来标记central或peripheral manger的。你的代码只关心这个字符串,但这个字符串告诉蓝牙库需要保存被标记对象的状态。蓝牙库只保存那些有标记还原id的对象的状态。
假如,选择支持状态保存和还原的应用只有一个CBCentralMnager对象实例实现了central角色,那么在初始化时初始化options中增加CBCentralManagerOptionRestoreIdentifierKey 键,并赋值还原id.
/*
NSString * const CBCentralManagerOptionShowPowerAlertKey 对应一个NSNumber类型的bool值,用于设置是否在关闭蓝牙时弹出用户提示
NSString * const CBCentralManagerOptionRestoreIdentifierKey 对应一个NSString对象,设置管理中心的后台模式恢复标识符ID
*/
myCentralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:@{ CBCentralManagerOptionRestoreIdentifierKey: @"myCentralManagerIdentifier" }];
peripheral manager的处理也是类似的,key是CBPeripheralManagerOptionRestoreIdentifierKey
注意:因为应用可以有多个CBCentralManager 和 CBPeripheralManger实例。注意每个还原id都是唯一的,这样系统才能区分开来。
Reinstantiate Your Central and Peripheral Managers
还原central和peripheral manager
当应用被系统唤醒,你需要做的第一件事是使用还原id复原central and peripheral manager。如果应用中只有一个central or peripheral manager,并且在应用的整个生命周期中存在,那么就简单了。 如果应用使用多个central or peripheral manager 或如果应用使用的manager不是在app的整个生命周期中存在,那么应用需要知道哪些managers需要复原。在实现application:didFinishLaunchingWithOptions:
这个代理方法时,通过使用参数launchoptions中的键(UIApplicationLaunchOptionsBluetoothCentralsKey
or UIApplicationLaunchOptionsBluetoothPeripheralsKey
) 可以获得应用在终止时为我们保存的还原id列表。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSArray *centralManagerIdentifiers = launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey]; ...
有了还原id列表后,就可以复原出central manager 对象了。
注意:当应用被激活时,系统只提供那些应用终止时有蓝牙任务的central and peripheral managers 的还原ids.
Implement the Appropriate Restoration Delegate Method
实现还原代理方法
在重新创建central and peripheral managers之后,需要通过蓝牙系统还原他们的状态。对于central managers,要实现centralManager:willRestoreState:
代理方法,对于peripheral managers 实现peripheralManager:willRestoreState:
方法。
重要:对于使用状态保存和还原特性的应用,应用被激活到后台的第一个代理调用是centralManager:willRestoreState: and peripheralManager:willRestoreState:
。对于未使用这一特性的应用,第一个代理调用是centralManagerDidUpdateState: and peripheralManagerDidUpdateState:
。
在这些代理中,最后一个参数是dictionary,包含了应用被终止时managers的信息。可用键值参考 Central Manager State Restoration Options constants in CBCentralManagerDelegate Protocol Reference and the Peripheral_Manager_State_Restoration_Options constants in CBPeripheralManagerDelegate Protocol Reference
要还原CBCentralMnager 对象的状态,要使用centralManager:willRestoreState:
方法中dictionary的键值对。举个例子,假如centralmanger对象在app被终止时有acitve或pending连接,系统会继续管理他们。就像下面代码所示,可以使用CBCentralManagerRestoredStatePeripheralsKey
键从dictionary中获取所有设备的列表,这些设备就是central manger已连接或正在连接的设备。
//dict中会传入如下键值对
/*
//恢复连接的外设数组
NSString *const CBCentralManagerRestoredStatePeripheralsKey;
//恢复连接的服务UUID数组
NSString *const CBCentralManagerRestoredStateScanServicesKey;
//恢复连接的外设扫描属性字典数组
NSString *const CBCentralManagerRestoredStateScanOptionsKey;
*/
- (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary *)state {NSArray *peripherals = state[CBCentralManagerRestoredStatePeripheralsKey];...
如何使用这个列表要看具体情况。比如,如果应用要维护central manger 已发现peripherals的列表,你可能就需要利用到它。参见Connecting to a Peripheral Device After You’ve Discovered It, 请注意在需要给peripheral设置相应的代理。
对于CBPeripheralManager 对象,也需要类似的处理,相应的代理方法是peripheralManager:willRestoreState:
。
Update Your Initialization Process
更新你的初始化进程
在前面的三个步骤之后,你可能想知道central and peripheral manager的初始化进程。虽然这是一个可选步骤,但如果想让你的应用跑起来更流畅,这可是很重要的。假设应用在检索peripheral的服务时被终止。当应用还原后,它不知道这个过程到底进行到哪一步了。你也想知道从哪一步继续。
举例,当在centralManagerDidUpdateState:
方法中初始化你的应用时,你可以查到在应用被终止时你是否成功发现被还原peripheral的某个service,如下:
NSUInteger serviceUUIDIndex = [peripheral.services indexOfObjectPassingTest:^BOOL(CBService *obj,NSUInteger index, BOOL *stop) { return [obj.UUID isEqual:myServiceUUIDString];}];if (serviceUUIDIndex == NSNotFound) { [peripheral discoverServices:@[myServiceUUIDString]]; ...
如上,如果系统在应用发现service之前终止它,那么开始搜索peripheral的数据,使用discoverServices:搜索。如果应用在被终止前已搜索到service,那么你需要检查时候搜索到你要的characteristics,(如果有订阅,也检查是否已订阅)。通过检查初始化过程,可以确保在这时调用到最合适的方法。