【转载】让你的iPhone app在iOS4中支持最基本的多任务
转载自:http://bbs.et8.net/bbs/showthread.php?t=1000165
周末把我自己的iPhone app升级了一下,现在可以在iOS4上局部得益于多任务了。把自己的过程写下来就算是个学习笔记吧。
首先明确一些概念。iOS4的多任务和我们一般理解的PC多任务,甚至和Symbian, Android这些移动系统的多任务实现,都是不同的。在其它系统中,“后台的程序仍然能够做事”是天经地义的事情(Android对后台程序的CPU占用率作了上限,但只要不超出就没问题。一般后台放点音乐、连接一下网络这些是没有问题的)。
在iOS4里面,是反过来的:默认的多任务支持仅提供最低限度的“挂起”。程序被切换到后台时,有5秒钟左右的时间完成一些操作,然后就被冻结,冻结时间内这个程序的消息队列会被关闭,没有任何消息能够进来,线程也被挂起。直到程序被再次切换到前台。要想在后台做事,那就要在程序描述文件中说明,并且使用特殊的API。
换句话说,不用这些特殊后台API的程序,能够利益于多任务的提升非常有限:基本上只能让切换回来的时候略快一些(以前切换回来的时候是重启动,现在是直接恢复)。除此之外就没什么别的了。不过,除了那种放音乐、需要在后台持续发连接的任务外,对很多普通程序,其实能做到这样也是一个不错的进展,而且它需要的代码变动非常少。我自己的程序就是这种情况,也是本文主要讨论的内容。
另外一个需要强调的是,这个“挂起”操作的目的并不是让开发者偷懒,省掉保存现场的工作。因为程序进入后台后,系统如果需要更多的资源(比如要跑一个大游戏),它可能直接把后台的程序干掉。而且是直接kill,根本不给后台程序发任何消息,后台程序在这个时间点没有机会执行哪怕是一行的代码。这种情况发生后,下次用户切换回你的程序时,实际上又变成重启动了。所以,要保存的现场还是要保存,只是时间点从以前的“将要退出时”移到“将要进入后台时”。
不过这种挂起多任务还是给那些自我要求不高的开发者提供了一定的偷懒空间。以前不实现现场保护,那程序的体验是非常差的,稍微有点什么事情打个岔,回来就变成第一屏了。实现了挂起支持之后,至少大部分情况下,系统能够把程序保留在后台,现场还在内存中。比如切出去接个电话,记个事啥的,都不会有问题。仅在系统需要大量资源,或者人为关机后再恢复时会丢失现场。所以用户的感觉是这个程序大部分时候还行,就偶尔记不住事,不至于每次都抓狂。
(扯远点,其实在保存现场这一点上,我觉得Android要做得人性化得多。系统帮你做了很多事情,比如保存视图栈、每个视图上控件的状态等,都是系统帮你自动保存和恢复的。你只要配合系统实现几个生存期管理的回调就行了。但在iPhone里面,要把视图栈保存起来(重启后不光回到同一屏,而且按back回去的屏幕也是跟退出前一样的),你得自己去实现一个数据结构,自己定个文件格式(串流化或者XML plist)存下来,恢复也是自己一个个重建。不难,但是非常之烦琐而且容易出错。)
下面总结一下让程序支持OS4最基本的多任务(即“挂起”),并且仍然保留与OS3设备兼容性所需的改动。这里假设你的程序已经在OS3上正常工作,并且在退出前做了一些现场保存。
1. 用SDK 4.0重新编译程序
这是最低要求。如果系统发现程序编译所用的SDK为旧版,会彻底禁用多任务。所以第一件事情就是用新的xcode3.2.3 + SDK4.0重新编译你的程序。这里要注意两点:
- 如果程序想保持和3.0系统兼容,那么你需要在项目设置中把Base SDK设为iPhone Device 4.0,但在Deployment Target中设为3.0。
- 有些3.0的API在4.0中不推荐使用,编译器会报警。如果你的程序想保持3.0兼容性,不要去为了这些警告去改代码
做完这一步你的程序应该已经可以在OS4上基本正常的运行了,而且已经可以挂起到后台。但目前状态下程序是无法正确处理以下场景的:
- 程序切到后台后,因为系统需要更多资源而强制杀掉。下次你的程序会丢失现场。这个问题的根源是applicationWillTerminated在多任务环境下不保证被调用。
- 如果你的程序在系统设置中加了一些设置选项,用户切出去改了选项(比如字体大小)再切回来,你的程序多半不能正常反映出这个改动。更可能的情况是程序状态发生一些错乱(比如旧的内容是旧字体,局部刷新为新字体)。这个问题的根源是旧版OS要改设置一定要退出程序再重新启动,一般程序不会去对运行时设置变更作相应的处理
2. 改变现场保存的时间点
OS3中,现场保存一般在程序退出前完成。也就是说你的代码很可能是这样:
- (void) applicationWillTerminate:(UIApplication *)application { [self saveAppState]; }
在OS4中,如前所述,程序还是有可能在后台被终止,而且这个时候你的程序已经丧失运行的能力。所以,要把现场保存在进入后台前也做一次:
- (void) applicationDidEnterBackground:(UIApplication *)application { [self saveAppState]; }
这个回调会在程序被切入后台后执行。在被冻结前你有大概5秒钟的时间运行。
注意applicationWillTerminate也仍然保留。这样在不支持多任务的系统上仍然可以正常工作。
3. 处理运行时的系统设置变动
一个比较常见的例子:我的程序在系统设置中加了一页,可以改变页面字体大小。假设程序运行到一半,用户切换出去把字体加大,再切换回来。不加特殊处理的话,这时程序肯定是不对劲的。具体到我的程序上,就是字体的变化没有反应出来。更糟的是,如果这时用户滚动页面,新的数据会用大字体,而旧的仍然是小字体。
正确的做法是程序要注册NSUserDefaultsDidChangeNotification系统通知。当程序在挂起时出现设置变动,恢复时会收到这个通知。这时你可以进行刷新等操作。比如我习惯用view controller来接收这个通知:
- (void) viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(prefDidChange:) name:NSUserDefaultsDidChangeNotification object:NULL]; } - (void) viewDidUnload { [super viewDidUnload]; [[NSNotificationCenter defaultCenter] removeObserver:self name:NSUserDefaultsDidChangeNotification object:NULL]; } - (void) dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:NSUserDefaultsDidChangeNotification object:NULL]; // other release calls [super dealloc]; } - (void) prefDidChange:(NSNotification*) noti { // reread pref from NSUserDefaults, refresh view // ... }
4. 其它
以上就是支持最基本多任务的主要改动。除此之外其它的就要看你程序本身的设计了。在升级过程中我还发现一些跟多任务无关的变动,如果你的程序从SDK3升级上来多半也会遇到:
- 如果程序要提供中文资源,SDK3中允许只提供zh.lproj,这样不论是简体还是繁体系统都会从这里读资源(当然,如果我的资源是简体的,那繁体用户看到的也是简体字)。但在SDK4中,我的程序不论在什么系统下都显示英文。后来发现改成zh_CN.lproj或者zh-Hans.lproj就好了。这个很奇怪,在Apple文档中没有提到相应的改动。
- 这个我觉得是个bug。如果你的程序使用翻译后的CFBundleDisplayName,在OS3中,系统设置中,你程序对应那一行的标题也会使用翻译后的值。但在OS4中,它始终只显示未翻译的CFBundleDisplayName。
- 好象对资源文件编码的要求变严格了。我有几个strings文件是UTF8的,在OS3下正常,在OS4下就无论如何都显示不出相应的资源。后来改成UTF16就好了。
就这些了。希望对大家有所帮助。当然,想彻底使用多任务的优势,在后台还能做一些事情,要做的改动就肯定多一些。这就不属于本文讨论的范围了。大家自己去翻文档吧。