iOS_应用程序的生命周期
每个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对象进行了初始化,这个对象是隐含的。改函数的最后2个參数:字符串来识别UI Application类和UI Application代理类,在这里默认是2个nil,
前一个nil就默认把UI Application类作为缺省值进行初始化,也能够以在这里不填nil而是使用自定义的UI Application子类。
后一个參数就是代理。尽管说UI Application对象管理着整个程序的生命周期。事实上它什么详细的事情都不干,它仅仅负责监听事件,当须要做实际工作的时候就交给UI Application代理类去做。UI Application相当于长官仅仅负责把命令传达给UI Application代理类这个士兵,然后由这个士兵去解决这个问题,所以须要给UI Application对象设置代理类。
2. 开发过程
iOS应用程序一共同拥有五种状态:
1. Not running:应用还没有启动,或者应用正在执行可是途中被系统停止。
2. Inactive:当前应用正在前台执行,可是并不接收事件(当前也许正在执行其他代码)。一般每当应用要从一个状态切换到还有一个不同的状态时,中途过渡会短暂停留在此状态。唯一在此状态停留时间比較长的情况是:当用户锁屏时,或者系统提示用户去响应某些(诸如电话来电、有未读短信等)事件的时候。
3. Active:当前应用正在前台执行,而且接收事件。这是应用正在前台执行时所处的正常状态。
4. Background:应用处在后台,而且还在运行代码。
大多数将要进入Suspended状态的应用。会先短暂进入此状态。
然而,对于请求须要额外的运行时间的应用,会在此状态保持更长一段时间。另外。假设一个应用要求启动时直接进入后台运行。这种应用会直接从Not running状态进入Background状态,中途不会经过Inactive状态。
比方没有界面的应用。注此处并不特指没有界面的应用,事实上也能够是有界面的应用,仅仅是假设要直接进入background状态的话,该应用界面不会被显示。
5. Suspended:应用处在后台。而且已停止运行代码。系统自己主动的将应用移入此状态,且在此举之前不会相应用做不论什么通知。
当处在此状态时,应用依旧驻留内存但不运行不论什么程序代码。当系统发生低内存告警时。系统将会将处于Suspended状态的应用清除出内存以为正在前台运行的应用提供足够的内存。
例如以下图:
3. 状态转换
3.1 应用启动
a.开启新项目
当应用启动时,将从Not running状态进入foreground或者直接进入background执行。进入前台时,事实上终于是要进到Active状态,中途会先短暂进入到Inactive状态。在应用启动时。系统会创建一个process和一个主thread,而且在主thread中调用main函数,即上面二提到的。
main函数在创建project时由Xcode自己主动生成。
main函数负责UIApplication对象初始化,及设置UIApplication代理类等等。
在应用初始化并准备进到前台执行之前的大部分工作都在main函数中完毕。
应用被启动直到前台执行的步骤例如以下图,右側部分为调用的UIApplication代理类的方法。
假设你的应用要求启动后直接进入到Background状态,则相应的启动过程和上稍有差别,主要不同是,上面的应用是进入到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:方法去打开文件。
b.改动老项目
当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”。
3.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保护选项须要对文件进行保护的应用必须关闭全部对文件的引用。
对于带有password的设备,按下“休眠/唤醒”键时,锁屏。而且强制系统扔掉解密密钥,以使全然保护使能。当屏被锁时,不论什么尝试訪问对应受保护文件的操作都将fail。所以假设你的应用中有此类受保护的文件时,你应该在applicationWillResignActive:方法中关闭全部对这些文件的引用,而且在applicationDidBecomeActive:方法中又一次打开对此类文件的引用。
在通话过程中。调整你的应用的UI:
当用户正在接电话,而且返回你的应用继续保持通话。此时状态栏的高度应该添加以反应用户正在通话的事实。相似的,当用户结束通话时。状态栏的高度应该缩减恢复常规高度。处理状态栏高度变化的最好方法是使用view controllers去管理你的应用views。
当状态栏frame size改变时。view controllers会自己主动调整它们所管理的全部内部视图。
假设你的应用由于某些原因而没有使用view controllers,则你应该手动响应状态栏frame size的变化,详细即通过注冊UIApplicationDidChangeStatusBarFrameNotification通知来实现。通知处理函数handler应该获取状态栏的高度而且使用这些数据来适度调整当前应用所包括视图的高度。
3.3 切向后台background状态
当用户按下"Home"键或者系统启动另外一个应用时,前台foreground应用首先切换到Inactive状态。然后切换到Background状态。
此转换将会导致先后调用应用代理的applicationWillResignActive:和applicationDidEnterBackground:方法。
在applicationDidEnterBackground:方法返回后,大部分应用在之后不久转入suspended状态。
对于请求特定后台background任务的应用,比方播放音乐应用,或者那些请求须要额外运行时间的应用,可能会继续运行更长一段时间。
详细流程例如以下图所看到的:
注:应用从foreground切换到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状态前都已执行完成。
(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)不论什么应用当前不再须要的对象,而且这些对象后面又能够非常easy又一次创建。
为了帮助您的应用程序。降低其内存占用,系统会自己主动释放出很多幕后用于支持您的应用程序的对象。比如:
(1)释放全部的核心动画层的后备存储。以避免这些层继续在屏幕上显示,同一时候又不改变当前层的属性。而且并不释放层对象自已。
(2)移除全部对缓存图像的引用。(假设应用没有对这些图像强引用,则他们随后即被移除内存)
(3)释放一些系统管理的其他的数据缓存。
3.4 返回前台foreground
假设应用曾被移入后台。对应的任务被停止。则此时返回前台时能够重新启动任务继续运行。应用的applicationWillEnterForeground:方法应该恢复全部在applicationDidEnterBackground:方法所做的工作。
同一时候。applicationDidBecomeActive:方法应该继续运行在应用启动时所做的相同的激活任务的操作。应用从后台切入前台的程序流程例如以下图所看到的:
注:假设应用在默认的通知中心注冊了UIApplicationWillEnterForegroudNotification通知。则当应用又一次进入前台时,该通知也是可用的。
(1)在应用切向前台被唤醒时处理通知队列
被挂起的应用要时刻准备当恢复foreground或background状态时去处理全部的通知队列。由于应用被挂起时不能运行不论什么代码。因此没有办法处理那些和诸如方向改变、时间改变、偏好改变、以及其他的影响应用的外观的行为或状态等等。
为了保证这些改变不丢失,系统将这些相关的通知入队列。而且当应用恢复foreground或background又一次运行代码时,马上将这些通知发往应用。
为了防止应用恢复时由于通知过多而过载,系统会合并事件而且仅传递一个可以反应自从应用被挂起有网络改变的单个通知。
详细通知合并规则例如以下表所看到的:
大部分通知直接传递给注冊它作的observers。然而像方向改变这种通知非常明显是被系统框架解析的,这种通知以另外的方式传递给应用。
(2)从容的处理本地改变
当应用处于挂起suspended状态时。假设用户改变了当前语言设置。则当应用返回前台的时候能够使用NSCurrentLocalDidChaneNotification通知来强制不论什么包括本地敏感信息(像日期、时间、数字等等)的视图进行更新。
当然,避免本地信息相关的事件处理的最好的方法还是以那种更easy更新视图的方式来写更新视图的代码。比方:
a.使用autoupdatingCurrentLocal类方法来检索NSLocal对象。此方法返回一个本地对象,该对象响应本地改变而且自己主动更新自已。所以,你不须要再去又一次创建它。
然后,当本地信息改变时,你的应用仍然须要去刷新那些包括本地信息的视图。
b.不管不论什么时候本地信息改变时都去又一次创建缓存日期或者数字格式对象。
(3)响应应用设置改变
假设应用包括被Settings应用所管理的设置,则应用应该关注NSUserDefaultsDidChangeNotification通知。由于当你的应用处于后台或被挂起状态时,用户能够改动设置,所以你的应用中能够使用这个通知来响应不论什么重要的设置改变。某些情况下,响应此通知能够帮助关掉一些潜在的安全漏洞。比如。email程序应该响应用户帐户信息的改变,假设不能成功的监測这些改变将会造成一些隐私和安全方面的问题。比方。用户非常有可能发送邮件时还是使用的是老用户帐户。即使那个帐户已经不再属于该用户。然而用户确丝毫不情以为用的就是新帐户。当应用接收到该通知时,应用应该又一次载入全部和设置相关的东西而且适当的复位用户接口。假设必要的话。
比方password或其他的安全相关的信息改变时,应用应该隐藏不论什么先前显示的信息而且强制用户输入新password。
3.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:方法。应用不能够在此方法中申请额外的后台执行时间。
3.6 主执行循环main run loop
应用主执行循环负责处理全部用户相关的事件。UIApplication对象在应用启动时安装主执行循环而且使用此循环去处理事件和处理基于视图的界面更新。
正如名字所表明的。该主执行循环是在应用的主线程app's main thread中执行的。
以此保证全部用户事件是依照它们被接收时的顺序串行的执行。
下图展示了主执行循环的结构以及用户事件怎样导致了应用行为。
当用户和应用交互时。和这些交互相关的事件由系统自己主动产生而且借助UIKit设定的特殊port传递给应用。事件在应用内部以队列的形式存在而且一个一个的被分发到应用的主执行循环去执行。
UIApplication对象是第一个接收事件的对象。而且决定须要怎样处理事件。触控事件通常被分发到应用的主窗体对象,而且终于分发到发生该触控事件的视图上面。其他的事件传递或许会经过各种各样的应用对象而与触控事件传递略微有所不同。
在iOS应用中能够传递非常多类型的事件。
最常见的事件列在下表中:
这些事件类型中的大部分通过应用的主执行循环进行传递,可是另一些并非的。比如:accelerometer事件直接被传递到应用指定的accelerometer代理对象。
一些像触控、远程控制类的事件。通常被应用的响应对象处理。
响应对象存在于应用的不论什么地方。(UIApplication对象。view对象。view controller对象等等都是响应对象的样例)。大多数事件是以特定的响应对象为目标,可是也能够被传递给其他的响应对象(借助响应链),比如:一个不处理不论什么事件的view能够将事件传递给它的父view或传递给view controller。
发生在controls类的视图(比如button、switch等)上的事件的处理过程和发生在其他类型的views上的触控事件处理过程有些不一样。
由于发生在control类的对象上面的交互行为仅仅有很有限的几种,因此这些交互又一次打包进active message而且传递给合适的目标对象。 这样的target-action的设计模式,使应用通过control类型的view对象去触发一段自己定义代码的运行变得很easy。