应用程序执行的生命周期
main函数探究
在iOS项目中有一个main.m的文件,它是程序的入口类,代码如下:
#import <UIKit/UIKit.h> #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }
参数argc与argv与标准的C语言main函数一致,argc(arguments count)代表参数个数,argv(arguments value)是一个字符型指针数组。
默认的argc为1,即包含一个参数,这个参数是程序完整路径;我们也可以添加一些额外的参数来测试一下:
P.s. 通过Xcode菜单栏的Product—Scheme—Edit Scheme打开编辑窗体。
int main(int argc, char * argv[]) { for(int i=0; i<argc; i++) { NSLog(@"arg %i: %s",i,argv[i]); } @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }
输出结果:
arg 0: /var/mobile/Applications/C12B5C94-CAD7-489D-AB8E-13280E7CD886/Sample0616.app/Sample0616
arg 1: god
arg 2: bless
arg 3: me
arg 4: New
arg 5: York
可以看出参数是以空格来分割的。第一个参数为真机上程序的完整路径。
main函数中调用了一个UIApplicationMain的函数,先来看看这个函数的原型:
int UIApplicationMain ( int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName );
前面两个参数前面已经分析过了,重点是最后两个参数principalClassName和delegateClassName:
principalClassName是应用程序类的名字,该类必须继承自UIApplication类;如果传递nil,UIKit就缺省使用UIApplication类;每一个iOS应用程序都包含一个UIApplication对象,iOS系统通过该UIApplication对象监控应用程序生命周期全过程。
delegateClassName是应用程序委托类的名字,默认为AppDelegate类,该委托类处理应用程序的生命周期事件和系统事件。
通过调用main函数创建了一个UIApplication对象,UIApplication对象负责监听应用程序的生命周期时间,并将生命周期事件交由AppDelegate代理对象处理。
iOS程序运行的生命周期
app的状态有五种:
not running — 没有启动app
inactive — app运行在前台,但是没有处理任何事件
active — app运行在前台,并且在处理事件
background — app运行在后台,还在内存中,并且执行代码
suspend — app还在内存中,但是不运行任何代码,如果内存不足,会自动kill掉
app各种状态之间的改变图示:
启动阶段
当一个app开始运行前存在一个启动阶段(Launch Time),这个阶段包含两个系统方法:
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions { return YES; } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. return YES; }
这两个方法的作用几乎完全一样,只是执行顺序有先后。AppDelegate类默认出现的是application: didFinishLaunchingWithOptions:,一般情况下只需要处理这个方法就可以了。
didFinishLaunchingWithOptions函数的参数launchOptions是一个NSDictionary类型的对象,存储的是程序启动的原因。
- 用户(点击icon)直接启动程序,launchOptions内无数据;
- 其它程序通过openURL:方式启动,则可以通过键UIApplicationLaunchOptionsURLKey来获取传递过来的url
NSURL *url = (NSURL *)[launchOptions valueForKey:UIApplicationLaunchOptionsURLKey];
- 由本地通知启动,则可以通过键UIApplicationLaunchOptionsLocalNotificationKey来获取本地通知对象(UILocalNotification)
- 由远程通知启动,则可以通过键UIApplicationLaunchOptionsRemoteNotificationKey来获取远程通知信息(NSDictionary)
程序launchOptions中的可能键值可以参考UIApplication Class Reference的”Launch Options Keys”。
didFinishLaunchingWithOptions函数的返回值是一个BOOL类型,将决定是否处理URL资源,如果返回YES,则会由application:openURL:sourceApplication:annotation:方法处理URL。如果应用程序有一个远程通知启动,返回值会被忽略。
P.s. 如果同时出现了application: willFinishLaunchingWithOptions:和application: didFinishLaunchingWithOptions:,那么这两个方法都要返回YES,才会处理URL资源。
启动阶段结束后进入运行阶段,程序可能会进入前台(foreground)运行,也可能进入后台(background)运行,下面是两种运行方式的图示:
加载到前台:
加载到后台:
前台运行比较常见,后台运行出现在较后的iOS版本,这里不做很深的研究,只提供一个后台运行的配置及测试方法:
step 1:在application: didFinishLaunchingWithOptions:里设置数据获取时间间隔,并监控当前的程序运行状态
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum]; NSLog(@"current state:%d.(%d,%d,%d)",application.applicationState, UIApplicationStateActive, UIApplicationStateInactive, UIApplicationStateBackground); return YES; }
step 2:在Capabilities标签页中启用”Background Modes”,并勾选”Background fetch”
step 3:在AppDelegate里实现-application:performFetchWithCompletionHandler:,系统将会在执行fetch的时候调用这个方法。
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { //do something... }
step 4:使用Scheme更改Xcode运行程序的方式。Product—Scheme—Manage Schemes,然后新建或编辑一个scheme项:
step 5:启动调试。
运行阶段(只考虑直接Launch到前台的情况)
启动阶段执行完后,程序的状态为”inactive”,进入到运行阶段执行下面方法会变成”active”:
- (void)applicationDidBecomeActive:(UIApplication *)application { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. }
官方留下的注释翻译成中文是:“当应用程序处在不活动状态时,重新启动一个被暂停(或还未启动)的任务。如果程序之前就在后台,根据情况可以刷新用户界面。”这个方法触发很频繁,在程序第一次启动或程序恢复前台状态时,都会先执行这个方法。
中断情况
接下来考虑中断情况,有以下几种常见情况:
1. 按下home键或双击home键点击任务栏icon运行其它app
2. 有来电通知
3. 有短信或其它app的顶部通知或推送消息。
下面详细分析下每个动作,打印出触发的方法及状态:
case 1:当按下home键 function:[applicationWillResignActive], state:[Active] function:[applicationDidEnterBackground], state:[Background] case 2:当双击home键,然后选择返回当前app: function:[applicationWillResignActive], state:[Active] function:[applicationDidBecomeActive], state:[Active] case 3:当双击home键,然后选择其它app: function:[applicationWillResignActive], state:[Active] function:[applicationDidEnterBackground], state:[Background] case 4:当有来电,拒绝接听: function:[applicationWillResignActive], state:[Active] function:[applicationDidBecomeActive], state:[Active] case 5:当有来电,接听后并挂断: function:[applicationWillResignActive], state:[Active] function:[applicationDidEnterBackground], state:[Background] function:[applicationWillEnterForeground], state:[Background] function:[applicationDidBecomeActive], state:[Active] case 6:当有短信或顶部的推送消息,点击查看: function:[applicationWillResignActive], state:[Active] function:[applicationDidEnterBackground], state:[Background]
从上面的各种情况可以看出来,只要存在中断操作,第一个执行的方法都是:
- (void)applicationWillResignActive:(UIApplication *)application { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. }
通常情况下,我们应该在applicationWillResignActive:方法中执行下列操作:
- 停止timer和其它周期性任务
- 停止任何正在运行的请求
- 暂停视频的播放
- 如果是游戏那就暂停它
- 减少OpenGL ES的频率
- 挂起任何分发的队列和不重要的操作队列(你可以继续处理网络请求或其它时间敏感的后台任务)
当程序回到active状态,应该使用applicationDidBecomeActive:方法将上面暂停的操作重新开发或恢复运行,比如重新开始timer,继续分发队列,提高OpenGL ES的频率。对于游戏可能会回到一个暂停状态,有用户点击再开始。
如果程序中断后进入了后台,会调用方法:
- (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, this method is called instead of applicationWillTerminate: when the user quits. }
官方留下的注释翻译成中文是:“使用这个方法去释放共享资源,存储用户数据,取消定时器和存储足够的应用程序状态信息以便在终止前恢复到它当前的状态。如果你的应用程序支持后台操作,在用户离开程序后它将代替方法applicationWillTerminate:被调用”。
中断后返回的情况
中断情况发生后,再返回当前app,会调用方法:
- (void)applicationWillEnterForeground:(UIApplication *)application { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. }
这里有个疑问,当app从后台转回前台时,applicationWillEnterForeground:和applicationDidBecomeActive:都会被调用,两者有什么区别呢?我的理解是,applicationWillEnterForeground:只有当程序从后台返回到前台这一种情况下才会被调用;而applicationDidBecomeActive:除了从后台返回前台时被调用,还会在程序运行在前台时也被调用(例如之前提到的收到来电提醒后取消接听,双击home键后依旧返回当前app等操作)。所以applicationWillEnterForeground:适合处理那种加载前只需要执行一次的初始化。
终止阶段
当程序终止时将调用方法:
- (void)applicationWillTerminate:(UIApplication *)application { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. }
这个方法通常是用来保存数据和一些退出前的清理工作,需要要设置UIApplicationExitsOnSuspend的键值。