一般情况下,App切到后台后,会被挂起。
对于,需要在后台持续运行的App,包含以下几类:
Executing Finite-Length Tasks
App如果在被切到后台时,正在执行一个任务,需要一些额外的时间来完成,可以通过以下两个方法来创建BackgroundTask:
1 | beginBackgroundTaskWithName:expirationHandler: |
创建BackgroundTask时,系统暂时不会挂起App,在结束BackgroundTask时,需要调用:
1 | endBackgroundTask: |
如果没有成功调用endBackgroundTask,将会导致App被终止。如果设置了ExpirationHandler,则系统会调用该handle,给最后一次机会调用endBackgroundTask,避免App被终止。
例子如下:
1 | - (void)applicationDidEnterBackground:(UIApplication *)application |
如果需要知道还剩下多少时间可以运行,调用:
1 | [[UIApplication application] backgroundTimeRemaining] |
在iOS 10系统实际测试中,发现,即使bgTask被终止了,App依然不会被终止,且依然可以执行:
1 | - (void)applicationDidEnterBackground:(UIApplication *)application |
日志为:
1 | Still Exist, Remain: 179.988507 |
从日志来看,可执行的时间为180秒,即3分钟,过了3分钟,bg即被终止,但是,while循环依然可以执行,这可能是Bug,但是App倒是可以利用这一点做很多事情,例如保持Socket不会被挂起。
Downloading Content in the Background
对于需要在后台持续下载内容的App,应该使用NSURLSession,在切到后台时,系统会将NSURLSession单独划分到一个进程中,并且通知App下载的状态。流程如下:
一旦设置完成,NSURLSession对象将被系统接管:
如果任务完成时,app任然处于运行中(foreground或background状态),则:
NSURLSession对象会调用回调:
1 | application:handleEventsForBackgroundURLSession:completionHandler: |
在回调中,沿用之前的configuration,创建新的NSURLSessionConfiguration和NSURLSession对象,系统会自动将其与之前的Session对象连接在一起。
如果NSURLSession在下载过程中,App被终止了,则:
系统会继续其下载,在下载完成时,如果sessionSendsLaunchEvents为YES,则拉起App。如果需要进行权限验证或者任务相关的事件需要App支持时,系统也会拉起App。
如果用户手动终止了App,则:
该App所有在等待的Tasks将被取消。
Implementing Long-Running Tasks
对于需要长时间后台运行的App,需要声明相应的权限,包含以下类型:
- 后台播放音乐;
- 后台录音;
- 后台定位;
- VoIP;
- 下载和处理内容;
- 接收外部设备连接的更新;
声明支持的后台任务类型
设置Info.plist文件的UIBackgroundModes属性:
Xcode background mode | UIBackgroundModes value | Description |
---|---|---|
Audio and AirPlay | audio | 后台播放或录制音乐(包含通过AirPlay的流媒体) |
Location updates | location | 后台定位 |
Voice over IP | voip | 后台通话 |
Newsstand downloads | Newsstand | 后台下载杂志或者报纸内容 |
External accessory communication | external-accessory | 与硬件设备进行交互 |
Uses Bluetooth LE accessories | bluetooth-central | 与蓝牙设备进行交互 |
Acts as a Bluetooth LE accessory | bluetooth-peripheral | 通过peripheral模式进行蓝牙交互 |
Background fetch | fetch | 从网络下载以及处理较小的内容 |
Remote notifications | remote-notification | 在接收到推送时,App需要开始下载内容 |
Tracking the User’s Location
后台定位包含三种类型:
(1)Significant-Change Location Service
对于不需要精度定位数据的App,例如社交类或者新闻类,Significant-Change已经满足使用了。当定位刷新时,系统会将自动将App拉起。注意,Significant-Change只对包含蜂窝电波的设备有效。
(2)Foreground-Only Location Services
获取标准的定位数据,只在Foreground有效。
(3)Background Location Service
获取标准的定位数据,在Background时也有效。
Playing and Recording Background Audio
对于后台播放音乐的App,在后台时,如果在播放音乐,将不会被挂起,而一旦停止播放,会被系统挂起。对于多个App同时在后台播放音乐时,系统会根据Audio Session来管理音频焦点。
Implementing a VoIP App
对于支持VoIP的App,需要一直保持与服务器的联系,系统会挂起App,同时接管其Socket的状态,如果有流量进来,则唤醒App,并将Socket的控制交还给App。
配置VoIP流程如下:
- 设置UIBackgroundModes包含voip字段;
- 配置一个Socket用于VoIP;
- 在切换到后台时,调用setKeepAliveTimeout:handler:方法以使handler周期性运行,并在handler中管理其与服务器的联系;
- 在使用和结束使用时,管理好Audio Session。
Fetching Small Amounts of Content Opportunistically
对于需要周期性检查内容更新的App,可以设置UIBackgroundModes包含fetch字段,但这并不能保证系统一定会给予App时间去执行后台任务,只有在系统判断时机合适时,才会在后台唤醒或者拉起App,并调用回调:
1 | application:performFetchWithCompletionHandler: |
在该回调中,可以检查内容是否需要更新,并在更新完成后,调用CompletionHandler来通知系统。对于,下载更新内容越少和越精确的App,将比耗时和并没有下载内容的App,更可能被系统给予时间。在下载内容时,推荐使用NSURLSession来管理下载。
Using Push Notifications to Initiate a Download
对于接受到推送时,需要进行内容下载更新的App,设置UIBackgroundModes包含remote-notification字段,且推送的content-available的属性必须设置为1,此时,系统会在后台唤醒或拉起App,并调用回调:
1 | application:didReceiveRemoteNotification:fetchCompletionHandler: |
Downloading Newsstand Content in the Background
对于Newsstand类型的App,可以注册在后台下载咋着或者新闻内容,设置UIBackgroundModes包含newsstand-content字段,通过Newsstand Kit framework创建一个Download,系统会接管这个下载过程,即使App被挂起或者终止,系统会继续下载。当下载完成或者下载出现异常时,系统会发出通知来后台唤醒App以便处理。
Communicating with an External Accessory
与外部设备进行交互的App可以注册,在外部设备分发事件时,App被唤醒,例如,心率监测器,设置UIBackgroundModes包含external-accessory字段。当外设连接或者断开,以及外设发送数据时,系统会唤醒App来进行处理。
支持这一功能的App必须满足以下要求:
- App必须提供允许用户启动和终止外设的分发事件,同时开启或者关闭外设的连接Session;
- 在被唤醒后,App有将近10秒的时间来处理数据,理想情况下,App应该处理完数据,并让自身被再次挂起,而如果需要更多的时间,使用BackgroundTask。
Communicating with a Bluetooth Accessory
与蓝牙设备进行交互的App可以注册,在蓝牙设备分发事件时,App被唤醒,例如,蓝牙心率监测器,设置UIBackgroundModes包含bluetooth-central字段。当外设连接或者断开,以及外设发送数据时,系统会唤醒App来进行处理。
iOS 6中,App可以在peripheral模式下进行蓝牙交互,自身作为一个蓝牙设备,设置UIBackgroundModes包含bluetooth-peripheral字段。
注意事项,与上节一致。
Getting the User’s Attention While in the Background
当App处于挂起状态时,可以通过UILocalNotification来引起用户的注意。
例子:
1 | - (void)scheduleAlarmForDate:(NSDate*)theDate |
这里的LocalNotification的数量不能超过128个,音频格式支持:Linear PCM, MA4, µ-Law和a-Law。默认的音频可以用UILocalNotificationDefaultSoundName。
Understanding When Your App Gets Launched into the Background
对于支持后台运行的App,系统可能会出处理以下事件时,重新拉起:
- 定位更新时;
- 设备进入或离开一个特定的领域时,例如地理领域或者iBeacon领域;
- 音频操作相关时;
- 从一个连接的蓝牙设备接受数据时;
- 作为一个蓝牙设备,接收到命令时;
- 推送的content-available值为1时;
- 后台下载新内容时;
- NSURLSession下载完成或出现异常时;
- Newsstand下载完成时。
对于被用户手动终止的App,除非重启手机,否则一般情况下,系统不会再次拉起。唯一的例外是定位App,在iOS 8以上,会被重新拉起。
Being a Responsible Background App
处于前台的App,比后台的App拥有更高的优先级。后台运行的App需要遵循以下原则:
- 不要调用OpenGL ES。在后台调用OpenGL ES命令都会导致App立刻被终止;
- 在被挂起前,取消任何的Bonjour相关的服务。在后台运行时,应该取消注册Bonjour服务以及关闭所有的监听Sockets。如果没有关闭,在App被挂起时,系统会自动关闭;
- 网络相关的Sockets,应该处理连接失败的情况。在App被挂起时,系统可能会挂起Sockets连接。Sockets相关的代码应该处理各种网络异常的情况;
- 在切到后台前,保存App状态。在内存过低的情况下,后台运行的Apps可能会被从内存移除以释放内存,此时App不会收到任何通知,因此最好保存App状态;
- 在切到后台时,移除没必要的strong引用。如果App包含了占用内存大的对象,特别是图片,移除所有的strong引用;
- 在被挂起前,停止使用共享的系统资源。App与共享系统资源交互,例如通讯录,日历数据库等,在被挂起前,应该停止使用。共享系统资源会优先给前台App使用,如果App被挂起后,依然使用上述资源,会被终止;
- 避免更新Windows和Views。