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状态的应用清除出内存以为正在前台运行的应用提供足够的内存。

      如下图:

     iOS_应用程序的生命周期

 

3. 状态转换

   3.1  应用启动

     a.开启新项目   

     当应用启动时,将从Not running状态进入foreground或者直接进入background运行。进入前台时,其实最终是要进到Active状态,中途会先短暂进入到Inactive状态。在应用启动时,系统会创建一个process和一个主thread,并且在主thread中调用main函数,即上面二提到的。 main函数在创建工程时由Xcode自动生成。main函数负责UIApplication对象初始化,及设置UIApplication代理类等等。在应用初始化并准备进到前台运行之前的大部分工作都在main函数中完成。

    应用被启动直到前台运行的过程如下图,右侧部分为调用的UIApplication代理类的方法。

iOS_应用程序的生命周期

     如果你的应用要求启动后直接进入到Background状态,则对应的启动过程和上稍有区别,主要不同是,上面的应用是进入到active,而你的应用要进入到background,并且处理事件,当没有事件处理时,会被挂起进入到Suspended状态。如下图所示。

iOS_应用程序的生命周期

 

     需要注意的一点是:对于从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的应用,启动顺序如下图所示:

iOS_应用程序的生命周期

当URL请求到来时,如果你的应用正在background运行或被suspended,它将会被移到前台以打开URL。之后不久,系统将会调用应用代理的application:openURL:sourceApplication:annotation:方法去检测URL并打开它。如果你的应用代理没有实现这个方法(或者当前系统是iOS4.1或更老的版本),系统将会调用应用代理的application:handleOpenURL:方法来代替。下面是唤醒后台或挂起的应用,去打开URL的程序执行流程,如下图所示:

iOS_应用程序的生命周期

支持自定义URL模式的应用,可以在应用启动和去处理URL之前,这个过程之间指定不同的启动画面图像。具体细节,请看Apple官方文档iPhoneAppProgrammingGuide.pdf第85页,“Providing Launch Images for Custom URL Schemes”。

 3.2  响应中断

     当一个基于警告的中断(诸如电话来电)发生时,应用会暂时从active状态切换到Inactive状态,以给系统提供机会提示用户,让用户决定如何处理。在用户决定如何处理此中断警告之前,应用将一直处于Inactive状态。 

在用户做出选择后,当前应用或者回到active状态继续运行,或者直接切换到background状态以让位于其它的应用运行。此种情况下,应用执行流程如下图所示:

iOS_应用程序的生命周期

在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.3 切向后台background状态

     当用户按下"Home"键或者系统启动另外一个应用时,前台foreground应用首先切换到Inactive状态,然后切换到Background状态。

此转换将会导致先后调用应用代理的applicationWillResignActive:和applicationDidEnterBackground:方法。

在applicationDidEnterBackground:方法返回后,大部分应用在之后不久转入suspended状态。

对于请求特定后台background任务的应用,比如播放音乐应用,或者那些请求需要额外执行时间的应用,可能会继续执行更长一段时间。

具体流程如下图所示:

iOS_应用程序的生命周期

注:应用从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)任何应用当前不再需要的对象,并且这些对象后面又可以很容易重新创建。

为了帮助您的应用程序,减少其内存占用,系统会自动释放出许多幕后用于支持您的应用程序的对象。例如:

(1)释放所有的核心动画层的后备存储,以避免这些层继续在屏幕上显示,同时又不改变当前层的属性。并且并不释放层对象自已。

(2)移除所有对缓存图像的引用。(如果应用没有对这些图像强引用,则他们随后即被移除内存)

(3)释放一些系统管理的其它的数据缓存。

3.4  返回前台foreground

      如果应用曾被移入后台,相应的任务被停止,则此时返回前台时可以重启任务继续执行。应用的applicationWillEnterForeground:方法应该恢复所有在applicationDidEnterBackground:方法所做的工作。同时,applicationDidBecomeActive:方法应该继续执行在应用启动时所做的同样的激活任务的操作。应用从后台切入前台的程序流程如下图所示:

iOS_应用程序的生命周期

注:如果应用在默认的通知中心注册了UIApplicationWillEnterForegroudNotification通知,则当应用重新进入前台时,该通知也是可用的。

(1)在应用切向前台被唤醒时处理通知队列

被挂起的应用要时刻准备当恢复foreground或background状态时去处理所有的通知队列。因为应用被挂起时不能执行任何代码,因此没有办法处理那些和诸如方向改变、时间改变、偏好改变、以及其它的影响应用的外观的行为或状态等等。为了保证这些改变不丢失,系统将这些相关的通知入队列,并且当应用恢复foreground或background重新执行代码时,立即将这些通知发往应用。为了防止应用恢复时因为通知过多而过载,系统会合并事件并且仅传递一个能够反应自从应用被挂起有网络改变的单个通知。

具体通知合并规则如下表所示:

iOS_应用程序的生命周期

大部分通知直接传递给注册它作的observers,然而像方向改变这样的通知很明显是被系统框架解析的,这样的通知以另外的方式传递给应用。

通知队列典型的在任何触控事件和用户输入之前被投递向应用的主运行循环main run loop。大多数应用应该能够足够快的处理这些事件以致于不会造成应用恢复时有任何明显的滞后。然而,如果发现你的应用在从后台恢复时看起来明显呆滞,则可以使用Instruments去确定是否是通知处理代码正在运行而造成了延迟。
一个应用在返回前台时也会接收所有自从上更新后被标记为dirty的视图的更新通知。处于后台background运行的应用也可以调用setNeedsDisplay或setNeedsDisplayInRect:方法去请求视图更新。然而,因为这时界面不可见,所以系统合并这些请求并且只有当应用恢复前台后才去更新视图。

(2)从容的处理本地改变

当应用处于挂起suspended状态时,如果用户改变了当前语言设置,则当应用返回前台的时候可以使用NSCurrentLocalDidChaneNotification通知来强制任何包含本地敏感信息(像日期、时间、数字等等)的视图进行更新。当然,避免本地信息相关的事件处理的最好的方法还是以那种更容易更新视图的方式来写更新视图的代码。比如:

a.使用autoupdatingCurrentLocal类方法来检索NSLocal对象。此方法返回一个本地对象,该对象响应本地改变并且自动更新自已。所以,你不需要再去重新创建它。然后,当本地信息改变时,你的应用仍然需要去刷新那些包含本地信息的视图。

b.无论任何时候本地信息改变时都去重新创建缓存日期或者数字格式对象。

(3)响应应用设置改变

如果应用包含被Settings应用所管理的设置,则应用应该关注NSUserDefaultsDidChangeNotification通知。因为当你的应用处于后台或被挂起状态时,用户可以修改设置,所以你的应用中可以使用这个通知来响应任何重要的设置改变。某些情况下,响应此通知可以帮助关掉一些潜在的安全漏洞。例如,email程序应该响应用户帐户信息的改变,如果不能成功的监测这些改变将会造成一些隐私和安全方面的问题。比如,用户很有可能发送邮件时还是使用的是老用户帐户,即使那个帐户已经不再属于该用户,然而用户确丝毫不情以为用的就是新帐户。当应用接收到该通知时,应用应该重新加载所有和设置相关的东西并且适当的复位用户接口,如果必要的话。比如密码或其它的安全相关的信息改变时,应用应该隐藏任何先前显示的信息并且强制用户输入新密码。

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设定的特殊端口传递给应用。事件在应用内部以队列的形式存在并且一个一个的被分发到应用的主运行循环去执行。UIApplication对象是第一个接收事件的对象,并且决定需要如何处理事件。触控事件通常被分发到应用的主窗口对象,并且最终分发到发生该触控事件的视图上面。其它的事件传递也许会经过各种各样的应用对象而与触控事件传递稍微有所不同。

iOS_应用程序的生命周期

在iOS应用中可以传递很多类型的事件。最常见的事件列在下表中:

iOS_应用程序的生命周期

iOS_应用程序的生命周期

这些事件类型中的大部分通过应用的主运行循环进行传递,但是还有一些并不是的。例如:accelerometer事件直接被传递到应用指定的accelerometer代理对象。

一些像触控、远程控制类的事件,通常被应用的响应对象处理。响应对象存在于应用的任何地方。(UIApplication对象,view对象,view controller对象等等都是响应对象的例子)。大多数事件是以特定的响应对象为目标,但是也可以被传递给其它的响应对象(借助响应链),例如:一个不处理任何事件的view可以将事件传递给它的父view或传递给view controller。

发生在controls类的视图(例如button、switch等)上的事件的处理过程和发生在其它类型的views上的触控事件处理过程有些不一样。因为发生在control类的对象上面的交互行为只有非常有限的几种,因此这些交互重新打包进active message并且传递给合适的目标对象。  这种target-action的设计模式,使应用通过control类型的view对象去触发一段自定义代码的执行变得非常容易。

iOS_应用程序的生命周期,布布扣,bubuko.com

iOS_应用程序的生命周期

上一篇:移动设备上的媒体查询 CSS media queries for mobile device


下一篇:node-gyp gyp: No Xcode or CLT version detected!` macOS Catalina 10.15问题解决