IOS4的多任务机制概述,后台运行详细分析
原文地址:http://www.cnblogs.com/Piosa/archive/2012/3/15.html
通过查看官方文档,我们了解到,其后台运行机制,允许三种服务在后台长时间运行,分别是
1. 位置服务
2. 音乐播放
3. VoIP
苹果对于这三种服务有一句话是这样说的,“Such
applications do not run continuously but are woken up by the system
frameworks at appropriate times to perform work related to those
services.”
所以他们也只是适时的被系统唤醒。其什么时候停止呢?拿音乐播放来说,“if the application stops playing audio, the system suspends it.”系统将会在停止播放音频的时候立即将其挂起。这些都不是我今天要说的重点。
我这里说的是另外一种后台运行机制,需要在有限的时间内完成的后台任务(官方原文:Finite Length Task)。
为什么说这个?因为前些天在坛子里看到有人问起关于多任务的问题,他举的例子是一个第三方闹钟,进入后台后,能不能完成5分钟后完成响铃提醒的任务。(因为不记得是哪个帖子,所以这里不给链接了,见谅!)
我看到了很多给他的回答,都说明了一个问题,那就是应用程序进入后台后,是被系统挂起的,不能继续执行代码。
也许是我个人理解有问题,这样描述,严格的来说是不正确的。因为苹果给了我们一定的时间让我们在后台做我们没做完的事情。
这里就拿发帖人举的例子,进入后台5分钟后响铃提醒。单从这个描述来说,实现这个功能是没有问题的(记住仅仅只是5分钟哦,看完后面的你就知道是为什么了)。
其实现方式,大致上分为以下步骤:
1. 向系统发出执行后台任务的申请,并定义当任务执行时间超过系统限定的时间后,进行哪些操作来保存数据和状态。引用一段官方代码,如下:
UIApplication* app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
2. 把自己要执行的任务以block的形式添加到系统的异步任务队列中去(官方推荐的是异步,我用同步实现了那个闹钟功能,详见附件中的代码包)。官方代码如下:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 此处添加你要完成的任务
// 任务做完以后通过下面的代码通知系统该任务结束了,如果没有其他的任务,那么可以将进程挂起了。
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
/**********************Split line for coder*********************/
通过上面的描述,我相信很多人已经知道这个闹钟该怎么去实现了。不过发扬一下共享精神,我还是把我的简单实现在这里说一下。
0. 说明一下dispatch_block_t这个类型,这个类型就是block类型,从字面上来讲,就是一个程序块。将你要执行的代码放到"^{"和"}"之间,就变成了一个block对象(姑且就叫它对象吧)。例如
dispatch_block_t block = ^{
NSLog(@"Hello DevDivers!");
};
1. 定义一个定时器用于程序激活状态的时候进行计时。
2. 定义两个宏用来写那些很麻烦的block结构,注意,我这里用的是同步队列。
#define DoorsBgTaskBegin() { \
UIApplication *app = [UIApplication sharedApplication]; \
UIBackgroundTaskIdentifier task = [app beginBackgroundTaskWithExpirationHandler:^{ \
[app endBackgroundTask:task]; \
/* task = UIBackgroundTaskInvalid; */ \
}]; \
dispatch_block_t block = ^{ \
#define DoorsBgTaskEnd() \
[app endBackgroundTask:task]; \
/* task = UIBackgroundTaskInvalid; */ \
}; \
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block); \
}
3. 在程序启动的时候把定时器启动,这一篇重点不在定时器,所以就不对定时器做过多描述了。
4. 在applicationDidEnterBackground中来做我们的闹钟。
4.1 首先把三个变量说明一下
static int giAlarmInterval = 5 * 60; // 5分钟,闹钟时间间隔
const int kTimerInterval = 1 * 60; // 1分钟,定时器时间间隔
const int kSysMaxTimePerBgTask = 10 * 600; // 10分钟,目前版本系统为每个后台任务分配的最大时间
4.2 执行代码
- (void)applicationDidEnterBackground:(UIApplication *)application {
/*
Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
If your application supports background execution, called instead of applicationWillTerminate: when the user quits.
*/
int i = giAlarmInterval / kTimerInterval;
while (i > 0)
{
DoorsBgTaskBegin();
[NSThread sleepForTimeInterval:kTimerInterval];
[self alarmFunc];
DoorsBgTaskEnd();
i--;
}
}
4.3 闹钟代码,这个比较简单,打印一点信息就是了。
- (void)alarmFunc
{
giAlarmInterval -= kTimerInterval;
if (giAlarmInterval > 0)
{
NSLog(@"%d minutes left!", giAlarmInterval / 60);
}
else
{
NSLog(@"Alarm!Alarm!Alarm!");
if ([alarmTimer isValid])
{
[alarmTimer invalidate];
}
}
}
/**********************Split line for coder*********************/
基本上就是这样子了,我这里只是希望给大家起到一点提示性的作用,大家如果能够有所收获就可以自由发挥,希望大家能开发出合理使用多任务的应用程序。
最后说明一点的是,官方推荐使用异步队列是方便大家把比较大的任务拆分成很多小的部分,同时放进队列里面去,提高效率的一种做法,如果用同步,有可能导致你要做的事情在10分钟内没做完,就只能被系统卡擦掉了。
一般的应用在进入后台的时候可以获取一定时间来运行相关任务,也就是说可以在后台运行一小段时间。
还有三种类型的可以运行在后台,
1.音乐 2.location 3.voip
大多数应用程序进入后台状态不久后转入暂停状态。在这种状态下,应用程序不执行任何代码,并有可能在任意时候从内存中删除。应用程序提供特定的服务,用户可以请求后台执行时间,以提供这些服务。
判断是否支持多线程
- UIDevice* device = [UIDevice currentDevice];
- BOOL backgroundSupported = NO;
- if ([device respondsToSelector:@selector(isMultitaskingSupported)])
- backgroundSupported = device.multitaskingSupported;
声明你需要的后台任务
Info.plist中添加UIBackgroundModes键值,它包含一个或多个string的值,包括
audio:在后台提供声音播放功能,包括音频流和播放视频时的声音
location:在后台可以保持用户的位置信息
voip:在后台使用VOIP功能
前面的每个value让系统知道你的应用程序应该在适当的时候被唤醒。例如,一个应用程序,开始播放音乐,然后移动到后台仍然需要执行时间,以填补 音频输出缓冲区。添加audio键用来告诉系统框架,需要继续播放音频,并且可以在合适的时间间隔下回调应用程序;如果应用程序不包括此项,任何音频播放 在移到后台后将停止运行。
除了添加键值的方法,IOS还提供了两种途径使应用程序在后台工作:
Task completion—应用程序可以向系统申请额外的时间去完成给定的任务
Local notifications—应用程序可以预先安排时间执行local notifications 传递
Task completion:
iOS不是真正的多任务系统,在用户按下Home按钮后,所有应用程序都会进入后台状态,并且大部分都会迅速进入暂停状态,应用程序的所有工作内存 都在RAM中,在暂停时它完全不执行。因此,切换回这样的应用程序非常快。但是如果系统需要更多的内存给当前处于活动状态的应用程序,就有可能终结暂停状 态的应用程序,它们的内存也将被释放。
一方面,应用程序在进入后台状态时,需要释放一些资源,使自身的暂停快照更小,从而减少从RAM中清除的风险,另一方面,为了避免被终结而丢失用户 的数据,需要在用户离开时保存他们的进度信息,这些工作,需要在5秒钟内完成,不然会被系统认定有异常被强制退出。可能通过接收应用程序发送的通知 (UIApplicationDidEnterBackgroundNotification)来触发处理,如果在处理代码中加上下面这条语句则必然会导 致异常退出:
[NSThread sleepForTimeInterval:10];
可以通过一种方法来请求更多后台时间来避免此问题。假设接收通知而触发的处理方法是applicationDidEnterBackground:
-(void)applicationDidEnterBackground{
NSLog(@"%@",NSStringFromSelector(_cmd));
//得到当前应用程序的UIApplication对象
UIApplication *app = [UIApplication sharedApplication];
//一个后台任务标识符
UIBackgroundTaskIdentifier taskID;
taskID = [app beginBackgroundTaskWithExpirationHandler:^{
//如果系统觉得我们还是运行了太久,将执行这个程序块,并停止运行应用程序
[app endBackgroundTask:taskID];
}];
//UIBackgroundTaskInvalid表示系统没有为我们提供额外的时候
if (taskID == UIBackgroundTaskInvalid) {
NSLog(@"Failed to start background task!");
return;
}
NSLog(@"Starting background task with %f seconds remaining", app.backgroundTimeRemaining);
[NSThread sleepForTimeInterval:10];
NSLog(@"Finishing background task with %f seconds remaining",app.backgroundTimeRemaining);
//告诉系统我们完成了
[app endBackgroundTask:taskID];
}
Local notifications:
UILocalNotification类提供了一种方法来传递local notifications。和push notifications需要设置remote server不同,local notifications 在程序中安排并在当前的设备上执行。满足如下条件可以使用该能力:
1、一个基于时间的程序,可以在将来特定的时间让程序post 一个alert,比如闹钟
2、一个在后台运行的程序,post 一个local notification去引起用户的注意
为了安排local notification 的传递,需要创建一个UILocalNotification的实例,并设置它,使用UIApplication类方法来安排它。Local notification对象包含了所要传递的类型(sound,alert,或者badge)和时间何时呈现) 。UIApplication类方法提供选项去确定是立即传递还是在指定的时间传递。
http://blog.csdn.net/duanyipeng/article/details/7101829
一、iOS应用程序状态机一共有五种状态:
1. Not running:应用还没有启动,或者应用正在运行但是途中被系统停止。
2. Inactive:当前应用正在前台运行,但是并不接收事件(当前或许正在执行其它代码)。一般每当应用要从一个 状态切换到另一个不同的状态时,中途过渡会短暂停留在此状态。唯一在此状态停留时间比较长的情况是:当用户锁屏时,或者系统提示用户去响应某些(诸如电话 来电、有未读短信等)事件的时候。
3. Active:当前应用正在前台运行,并且接收事件。这是应用正在前台运行时所处的正常状态。
4. Background:应用处在后台,并且还在执行代码。大多数将要进入Suspended状态的应用,会先短暂 进入此状态。然而,对于请求需要额外的执行时间的应用,会在此状态保持更长一段时间。另外,如果一个应用要求启动时直接进入后台运行,这样的应用会直接从 Not running状态进入Background状态,中途不会经过Inactive状态。比如没有界面的应用。注此处并不特指没有界面的应用,其实也可以是 有界面的应用,只是如果要直接进入background状态的话,该应用界面不会被显示。
5. Suspended:应用处在后台,并且已停止执行代码。系统自动的将应用移入此状态,且在此举之前不会对应用做 任何通知。当处在此状态时,应用依然驻留内存但不执行任何程序代码。当系统发生低内存告警时,系统将会将处于Suspended状态的应用清除出内存以为 正在前台运行的应用提供足够的内存。
如下图:
注意:运行在iOS3.2或更早期版本操作系统之上的应用并不进入后background和suspended状态。另外,一些即使运行在iOS4 或更新版本操作系统但是不支持多任务或后台执行的应用,也不会进入background和suspended状态。相应的这些应用在从前台运行状态离开时 会直接被终止。
大多时候状态转换通过调用你的应用委托对象继承的Delegate方法来完成。开发人员可以在提供的这些继承方法中做任何事,以响应状态转换。相关继承的方法及介绍如下所示:
application:didFinishLaunchingWithOptions: 这是程序启动时调用的函数。可以在此方法中加入初始化相关的代码。
applicationDidBecomeActive: 应用在准备进入前台运行时执行的函数。(当应用从启动到前台,或从后台转入前台都会调用此方法)
applicationWillResignActive: 应用当前正要从前台运行状态离开时执行的函数。
applicationDidEnterBackground: 此时应用处在background状态,并且没有执行任何代码,未来将被挂起进入suspended状态。
applicationWillEnterForeground: 当前应用正从后台移入前台运行状态,但是当前还没有到Active状态时执行的函数。
applicationWillTerminate: 当前应用即将被终止,在终止前调用的函数。如果应用当前处在suspended,此方法不会被调用。
二、关于main函数,UIApplication类和UIApplication代理类
每一个iPhone程序都包含一个UIApplication对象,它管理整个程序的生命周期,从加载第一个显示界面开始,并且监听系统事件、程序事件调度整个程序的执行。
int main(int argc, char *argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
[pool release];
return retVal;
}
在main函数中第二行代码UI Application Main(argc, argv, nil, nil);对UIApplication对象进行了初始化,这个对象是隐含的,这个方法除了argc 和 argv 参数外,另外这个函数还有两个字符串参数来识别UI Application类和UI Application代理类,在这里默认是2个nil,第一个参数为nil就默认把UI Application类作为缺省值进行初始化,可以在这里不填nil而是使用自己定义的UI Application子类。至于第二个参数nil,这里有了UI Application对象怎么又出来一个UI Application代理类对象呢?这里需要说明UI Application对象说是管理整个程序的生命周期其实它是什么具体的事情都不干,它只负责监听事件当需要做实际工作的时候就交给UI Application代理类去做,UI Application相当于传令官负责只把命令传达给UI Application代理类这个士兵,然后由这个士兵真正去冲锋陷阵,所以需要给UI Application对象设置代理类。
三、 iOS应用状态切换:
1. 应用启动周期
当应用启动时,将从Not running状态进入foreground或者直接进入background运行。进入前台时,其实最终是要进到Active状态,中途会先短暂进入到 Inactive状态。在应用启动时,系统会创建一个process和一个主thread,并且在主thread中调用main函数,即上面二提到的。 main函数在创建工程时由Xcode自动生成。main函数负责UIApplication对象初始化,及设置UIApplication代理类等等。 在应用初始化并准备进到前台运行之前的大部分工作都在main函数中完成。
应用被启动直到前台运行的过程如下图,右侧部分为调用的UIApplication代理类的方法。
如果你的应用要求启动后直接进入到Backgroundu状态,则对应的启动过程和上稍有区别,主要不同是,上面的应用是进入到active,而你的应用要进入到background,并且处理事件,当没有事件处理时,会被挂起进入到Suspended状态。如下图所示。
需要注意的一点是:对于从Not running状态直接进入到background状态的应用,在启动进入到background状态时,如果应用有界面,系统仍然会加载用户界面文件,只是不会显示在应用的window上面。
为了在程序中确定你的程序是进入到了foreground还是background,你可以在
application:didFinishLaunchingWithOptions:
方法中检测UIApplication类对象的applicationState属性,如果应用进入到了foreground,则属性值为
UIApplicationStateInactive,如果进入到了background,则为
UIApplicationStateBackground。
检测示例代码:
UIApplicationState state = [UIApplication sharedApplication].applicationState;
return (state==UIApplicationStateActive || state==UIApplicationStateInactive );
注:当应用启动时要求打开一个URL,则此类应用的启动过程和三中的两个图又有稍微区别, 具体如下:
具有自定义URL模式的应用必须能够处理所有传递给它的URLs。所有的URL都是传递给应用的代理来处理,无论当前应用是处在启动阶段或是正在运行running或是在后台background。为了能够处理URL请求,你的应用代理必须实现下面的接口方法:
(1)使用application:didFinishingLaunchingWithOptions:方法检索URL信息,并且决定是否想要打开这个URL,这个方法只有在应用被启动的时候调用。
(2)iOS4.2或更新的版本,使用方法application:openURL:sourceApplication:annotation:方法去打开文件。
(3)iOS4.1或更老的版本,使用方法application:handleOpenURL:方法去打开文件。
当URL请求到达时,如果你的应用没在正在运行,则会被启动并且移到前台运行以打开URL。你的application:didFinishingLaunchingWithOptions:方法实现中应该包含从选项字典options dictionary中检索URL并且判断该应用能否打开它的部分。如果能够打开,则返回YES,让方法application:openURL:sourceApplication:annotation:或方法application:handleOpenURL:去处理具体的URL打开过程。对于要求启动时打开URL的应用,启动顺序如下图所示:
当URL请求到来时,如果你的应用正在background运行或被suspended,它将会被移到前台以打开URL。之后不久,系统将会调用应用代理的application:openURL:sourceApplication:annotation:方法去检测URL并打开它。如果你的应用代理没有实现这个方法(或者当前系统是iOS4.1或更老的版本),系统将会调用应用代理的application:handleOpenURL:方法来代替。下面是唤醒后台或挂起的应用,去打开URL的程序执行流程,如下图所示:
支持自定义URL模式的应用,可以在应用启动和去处理URL之前,这个过程之间指定不同的启动画面图像。具体细节,请看Apple官方文档 iPhoneAppProgrammingGuide.pdf第85页,“Providing Launch Images for Custom URL Schemes”。
2. 响应中断
当一个基于警告的中断(诸如电话来电)发生时,应用会暂时从active状态切换到Inactive状态,以给系统提供机会提示用户,让用户决定如 何处理。在用户决定如何处理此中断警告之前,应用将一直处于Inactive状态。 在用户做出选择后,当前应用或者回到active状态继续运行,或者直接切换到background状态以让位于其它的应用运行。此种情况下,应用执行流 程如下图所示:
在iOS5中,notification,特指显示banner方式的notification,并不会像上面的中断一样使当前处于active状 态的应用切换到Inactive状态。此类通知的banner放置在你的应用窗口的上边沿之上,所以你的应用依然处在active状态,并且继续像以前一 样接收touch events。但是,如果用户拉下banner去呈现通知中心内容时,当前应用将会和上面基于警告的中断一样切换到inactive状态。此时应用将一直 处于Inactive状态直到用户对拉下的banner通知做出处理,或许仅仅清除通知或者启动另外一个应用。相应的当前应用要么切换回active状态 继续运行或者切换到background状态。用户可以通过Settings应用来配置哪些Notifications以banner的形式显示,哪些以 alert警告的形式显示。
用户按“休眠/唤醒”键是另外一种类型的中断,这类中断促使应用被deactived,当用户按下“休眠/唤醒”键时,系统除能触摸事 件,deactivate当前的应用,并且锁屏。针对使用数据保护进行加密文件的应用,锁屏事件除了上面的deactivated应用,除能触控事件之外 还有其它的处理过程。
当中断发生时,会做什么?
对于基于警告的中断将会导致用户暂时对应用失去控制。当前应用继续在前台foreground运行,但是不再接收任何触控事件。(事实上,应用只是不再接收触控类事件,其它类型的事件比如accelerometer事件,和通知Notification,应用仍然接收。)所以为了响应这些变化,应用需要在applicationWillResignActive:方法中做以下工作:
(1)停止timers及终止其它周期性任务。
(2)停止任何正在运行的元数据查询。
(3)不再初始化任何新任务。
(4)暂停电影播放(在AirPlay上的播放除外)
(5)游戏进入暂停状态。
(6)恢复OpenGL ES帧率。
(7)暂停任何正在临界区执行的分发队列或操作队列。(当然,当应用处于inactive状态时,应用仍然可以继续处理网络请求以及其它一些对时间敏感的后台任务)
当应用恢复切换回active状态时,将会在applicationDidBecomeActive:方法中恢复应用被挂起时执行applicationWillResignActive:方法中所做的所有工作。因此,当应用重新被激活reactivate时,应用应该重启timers,恢复任何分发队列,以及恢复OpenGL ES帧率。但是,游戏不应该自动恢复运行,应该继续保持在暂停状态直到用户手动恢复它们。
当用户按下“休眠/唤醒” 键时,带有NSFileProtectionComplete保护选项需要 对文件进行保护的应用必须关闭所有对文件的引用。对于带有密码的设备,按下“休眠/唤醒”键时,锁屏,并且强制系统扔掉解密密钥,以使完全保护使能。当屏 被锁时,任何尝试访问相应受保护文件的操作都将fail。所以如果你的应用中有此类受保护的文件时,你应该在applicationWillResignActive:方法中关闭所有对这些文件的引用,并且在applicationDidBecomeActive:方法中重新打开对此类文件的引用。
在通话过程中,调整你的应用的UI:
当用户正在接电话,并且返回你的应用继续保持通话,此时状态栏的高度应该增加以反应用户正在通话的事实。相似的,当用户结束通话时,状态栏的高度应 该缩减恢复常规高度。处理状态栏高度变化的最好方法是使用view controllers去管理你的应用views。当状态栏frame size改变时,view controllers会自动调整它们所管理的所有内部视图。
如果你的应用因为某些原因而没有使用view controllers,则你应该手动响应状态栏frame size的变化,具体即通过注册UIApplicationDidChangeStatusBarFrameNotification通知来实现。通知处 理函数handler应该获取状态栏的高度并且使用这些数据来适度调整当前应用所包含视图的高度。
3. 切向后台background状态
当用户按下"Home"键或者系统启动另外一个应用时,前台foreground应用首先切换到Inactive状态,然后切换到Background状态。此转换将会导致先后调用应用代理的applicationWillResignActive:和applicationDidEnterBackground:方法。在applicationDidEnterBackground:方法返回后,大部分应用在之后不久转入suspended状态。对于请求特定后台background任务的应用,比如播放音乐应用,或者那些请求需要额外执行时间的应用,可能会继续执行更长一段时间。具体流程如下图所示:
注:应用从froeground切换到background只有在支持多任务并且运行iOS4.0或更新版本系统的设备上才会发生。所有其它的情况,应用不是切向后台,而是直接终止,并且从内存中清除。
应用切向后台background时应该做什么:
应用可以在applicationDidEnterBackground:方法中做些切向background状态前需要做的一些准备工作,当切向background状态时,所有的应用需要做以下事情:
(1)应用界面快照。当applicationDidEnterBackground:方法返回时,系统保存应用界面的快照,并且使用快照图片作为转换动画。如果在你的应用界面中有涉及到敏感信息的视图,则你应该在applicationDidEnterBackground:方法返回前隐藏或者修改这些视图。
(2)保存用户数据和应用状态信息。所有没有保存的改变都应该在切向background状态前写入磁盘以保存。这一步是必须的,因为你的应用在后 台时很有可能因为多种其它原因而被很快kill掉。根据需要你可以在background thread后台线程中执行这些操作。
(3)释放尽可能多的内存资源。
applicationDidEnterBackground:方法允许最多有5秒的时间去完成任何任务然后返回。实际中,此方法应该尽可能快的返回。如果在时间到期之后,此方法没有返回,则应用即被kill掉,并且清除所占用的内存。如果你的应用确实需要更多的时间去执行任务,可以调用beginBackgroundTaskWithExpirationHandler:方法请求后台执行时间,然后启动一个能长期执行任务的线程。无论你是否启动一个执行后台任务的线程,applicationDidEnterBackground:方法都必须在5秒后退出。
注:UIApplicationDidEnterBackgroundNotification通知也会发送,以让应用对此通知感兴趣的部分知道当前应用正切向background状态。你的应用中的对象可以使用默认的通知中心注册这个通知。
依据不同的应用场合,应用切向后台时还有很多其它的事情需要做,比如active状态的Bonjour服务应该暂停,应用应该停止调用OpenGL ES函数。
因为前台应用在使用系统资源和硬件时一直比后台应用具有更高的优先权。运行在后台的应用应该对此差异有心理准备,并且在后台运行时要调整它们的访问资源行为。特别的,当应用切向background时尤其要遵循以下几点:
(1)不要在应用代码中调用任何OpenGL ES的东西。当应用在后台运行时不可以创建EAGLContext对象或者发出任何OpenGL ES绘画命令,使用这些调用将会导致应用立即被kill掉。应用也必须保证先前提交发出的所有命令在应用切向background状态前都已执行完毕。具 体细节请参考“OpenGL ES Programming Guide for iOS”中“Implementing a Multitasking-aware OpenGL ES Application”部分。
(2)在应用挂起suspended之前取消所有Bonjour相关的服务。当应用转向后台,并且在被挂起前,应用应该unregister Bonjour服务并且关掉任何和网络服务相关的sockets监听。挂起的应用是没法响应这些服务请求的。如果你的应用不关掉这些和Bonjour相关 的服务,当应用被挂起的时候,系统会自动帮你关掉这些服务。
(3)在基于网络sockets的应用中,需要处理连接失败的情况。当你的应用因为某些原因而被挂起时,系统可能会拆除socket连接。只要你的 应用对尽可能多的网络错误情况都有很好的处理,像丢掉信号等,此类问题不会导致你的应用出现不正常。当应用从后台退出恢复执行时,如果遇到sockets 使用错误,简单的重建socket连接即可。
(4)在切向background状态前保存应用状态。在低内存告警时,后台应用可能会被清除出内存以释放空间。处于suspended状态的应用 被优先清除内存,并且在被清除前不会给出任何通知。因此,当应用切入background状态前一定要保存足够多的应用状态信息以便后面恢复时使用。
(5)当切向后台时,释放所有不再需要的内存。如果你的应用保持着一个很大的内存缓存对象(比如图像),则切入后台前,释放所有的对这些缓存对象的引用。
(6)在被挂起前停止使用系统共享资源。使用系统共享资源(比如Address Book或Calendar Data)的应用,在被挂起前必须停止对这些共享资源的使用。对这些资源的使用,前台应用具有更高的优先使用权,如果发现你的应用在被挂起后还没有停止对 这些共享资源的使用,则应该将被kill掉。
(7)避免更新应用窗口和视图。当应用处在后台时,应用窗口和视图是不可见的,所以不需要更新它他。尽管在后台创建和操纵窗口和视图对象并不会导致应用被kill掉,但是可以考虑将这些工作推迟到应用返回前台时执行。
(8)响应外部附件连接和失去连接通知。针对和外部附件有通信的应用,当应用切向background状态时,系统会发送一个 disconnection通知。应用必须注册此通知并且使用它去关掉当前的附件访问session。当应用返回foreground时,会有一个与之匹 配的通知被发送,给应用提供重新建立session的机会。
(9)切向后台时,清除行为警告相关的资源。为了在应用相互切换之间保存应用上下文,当应用切向后台时,系统并不自动dismiss action sheets(UIActionSheet)和alert views(UIAlertView)。由应用设计者去提供具本的清除方案。对于运行在iOS4.0版本之前的应用,在退出时action sheets和alerts仍然被dismiss掉,以让应用的取消处理函数有机会去运行。
(10)切向后台时,移除所有敏感视图信息。因为系统会快照应用界面并且生成应用切换动画,所以带有敏感信息的视图或窗口必须隐藏或移除,具体原因前面已介绍。
(11)应用在后台运行时执行最少量化的工作。系统给后台运行的应用的执行时间和给前台运行的应用相比,通常非常有限。如果应用在后台播放音频或者 监测位置变化,则应用应该仅关注此任务,所有不必要的任务都应该被推迟。在后台执行时间过长的应用会被系统throttled back或者直接被kill掉。
当应用因为系统内存告警需要被清除出内存时,应用会调用他的代理的applicationWillTerminate:方法去执行应用退出前的最后的任务。
后台应用的内存使用:
当应用切入background时,每个应用应该释放尽可能多的实际占用的内存。系统尽量尝试在内存中同时保持尽量多的应用,但是当内存即将耗尽 时,系统会终止那些挂起suspended的应用以回收内存。然而那些消耗很大数量的内存同时又处于后台background运行的应用会优先被终止。
实事求是的讲,就是当你的应用在不再需要的时候要尽快的移除对那些用过对象的引用。移除引用允许自动引用计数系统去释放对象并且回收内存。然而,如果应用为了改进性能而使用了缓存,则应用应该在切换至后台状态前等待并且释放这些缓存。下面是一些需要回收的对象的例子:
(1)缓存的图像对象
(2)比较大的多媒体文件或数据文件,这些文件可以从磁盘重新装载。
(3)任何应用当前不再需要的对象,并且这些对象后面又可以很容易重新创建。
为了帮助您的应用程序,减少其内存占用,系统会自动释放出许多幕后用于支持您的应用程序的对象。例如:
(1)释放所有的核心动画层的后备存储,以避免这些层继续在屏幕上显示,同时又不改变当前层的属性。并且并不释放层对象自已。
(2)移除所有对缓存图像的引用。(如果应用没有对这些图像强引用,则他们随后即被移除内存)
(3)释放一些系统管理的其它的数据缓存。
4. 返回前台foreground
如果应用曾被移入后台,相应的任务被停止,则此时返回前台时可以重启任务继续执行。应用的applicationWillEnterForeground:方法应该恢复所有在applicationDidEnterBackground:方法所做的工作。同时,applicationDidBecomeActive:方法应该继续执行在应用启动时所做的同样的激活任务的操作。应用从后台切入前台的程序流程如下图所示:
注:如果应用在默认的通知中心注册了UIApplicationWillEnterForegroudNotification通知,则当应用重新进入前台时,该通知也是可用的。
(1)在应用切向前台被唤醒时处理通知队列
被挂起的应用要时刻准备当恢复foreground或background状态时去处理所有的通知队列。因为应用被挂起时不能执行任何代码,因此没 有办法处理那些和诸如方向改变、时间改变、偏好改变、以及其它的影响应用的外观的行为或状态等等。为了保证这些改变不丢失,系统将这些相关的通知入队列, 并且当应用恢复foreground或background重新执行代码时,立即将这些通知发往应用。为了防止应用恢复时因为通知过多而过载,系统会合并 事件并且仅传递一个能够反应自从应用被挂起有网络改变的单个通知。
具体通知合并规则如下表所示:
大部分通知直接传递给注册它作的observers,然而像方向改变这样的通知很明显是被系统框架解析的,这样的通知以另外的方式传递给应用。
(2)从容的处理本地改变
当应用处于挂起suspended状态时,如果用户改变了当前语言设置,则当应用返回前台的时候可以使用 NSCurrentLocalDidChaneNotification通知来强制任何包含本地敏感信息(像日期、时间、数字等等)的视图进行更新。当 然,避免本地信息相关的事件处理的最好的方法还是以那种更容易更新视图的方式来写更新视图的代码。比如:
a.使用autoupdatingCurrentLocal类方法来检索NSLocal对象。此方法返回一个本地对象,该对象响应本地改变并且自动更新自已。所以,你不需要再去重新创建它。然后,当本地信息改变时,你的应用仍然需要去刷新那些包含本地信息的视图。
b.无论任何时候本地信息改变时都去重新创建缓存日期或者数字格式对象。
(3)响应应用设置改变
如果应用包含被Settings应用所管理的设置,则应用应该关注NSUserDefaultsDidChangeNotification通知。 因为当你的应用处于后台或被挂起状态时,用户可以修改设置,所以你的应用中可以使用这个通知来响应任何重要的设置改变。某些情况下,响应此通知可以帮助关 掉一些潜在的安全漏洞。例如,email程序应该响应用户帐户信息的改变,如果不能成功的监测这些改变将会造成一些隐私和安全方面的问题。比如,用户很有 可能发送邮件时还是使用的是老用户帐户,即使那个帐户已经不再属于该用户,然而用户确丝毫不情以为用的就是新帐户。当应用接收到该通知时,应用应该重新加 载所有和设置相关的东西并且适当的复位用户接口,如果必要的话。比如密码或其它的安全相关的信息改变时,应用应该隐藏任何先前显示的信息并且强制用户输入 新密码。
5.应用终止
尽管应用通常被切向后台或被挂起,但是如果有任何下面的情况发生时,应用将被终止并且清除出内存:
(1)应用依赖于 iOS4.0以前的版本OS
(2)应用部署在运行iOS4.0版本操作系统的设备上
(3)当前设备不支持多任务
(4)应用在Info.plist文件中包含UIApplicationExitOnSuspend key。
如果应用将被终止时正在前台或后台运行,系统将会调用应用代理的applicationWillTerminate:方法以使应用能做退出前的任何 需要的回收处理。你可以使用此方法保存用户数据或应用状态信息,以供应用随后重新启动恢复状态时使用。该方法最长运行时限为5秒,过期应用即被kill掉 并且移除内存。
注:应用当前被suspended时,不会调用 applicationWillTerminate:方法。
即使是使用iOS SDK4或更新的版本SDK开发应用,也应该考虑应用在没有任何通知时被kill掉的情况。用户可以使用多任务UI很明确的kill掉某个应用。除此之 外,如果发生内存告警,系统也会从内存中移除应用以释放空间。处于suspended状态的应用被终止时不会有任何通知。但是如果应用当前正在后台 background运行,则当应用要被终止时,系统会调用应用代理的applicationWillTerminate:方法。应用不可以在此方法中申 请额外的后台执行时间。
6.主运行循环main run loop
应用主运行循环负责处理所有用户相关的事件。UIApplication对象在应用启动时安装主运行循环并且使用此循环去处理事件和处理基于视图的 界面更新。正如名字所表明的,该主运行循环是在应用的主线程app's main thread中运行的。以此保证所有用户事件是按照它们被接收时的顺序串行的执行。
下图展示了主运行循环的结构以及用户事件如何导致了应用行为。当用户和应用交互时,和这些交互相关的事件由系统自动产生并且借助UIKit设定的特 殊端口传递给应用。事件在应用内部以队列的形式存在并且一个一个的被分发到应用的主运行循环去执行。UIApplication对象是第一个接收事件的对 象,并且决定需要如何处理事件。触控事件通常被分发到应用的主窗口对象,并且最终分发到发生该触控事件的视图上面。其它的事件传递也许会经过各种各样的应 用对象而与触控事件传递稍微有所不同。
在iOS应用中可以传递很多类型的事件。最常见的事件列在下表中:
这些事件类型中的大部分通过应用的主运行循环进行传递,但是还有一些并不是的。例如:accelerometer事件直接被传递到应用指定的 accelerometer代理对像。关于系统如何处理大多数类型事件,包括touch、remote control、motion、accelerometer,以及gyroscopic事件,详见Event Handling Guide for iOS.
一些像触控、远程控制类的事件,通常被应用的响应对象处理。响应对象存在于应用的任何地方。(UIApplication对象,view对 象,view controller对象等等都是响应对象的例子)。大多数事件是以特定的响应对象为目标,但是也可以被传递给其它的响应对象(借助响应链),例如:一个 不处理任何事件的view可以将事件传递给它的父view或传递给view controller。
发生在controls类的视图(例如button)上的事件的处理过程和发生在其它类型的views上的触控事件处理过程有些不一样。因为发生在 control类的对象上面的交互行为只有非常有限的几种,因此这些交互重新打包进active message并且传递给合适的目标对象。 这种target-action的设计模式,使应用通过control类型的view对象去触发一段自定义代码的执行变得非常容易。