iOS申请持续的后台时间
由于苹果的后台机制,当我们按下home键的时候,所有线程包括主线程的任务都会被挂起,一些资源比如socket也会被系统回收,会导致很多问题,比如一个很重要的资源中断下载,或者定时器方法被暂停等等。
苹果在4.0以后提供了一种申请后台时间的机制:
- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:(void (^)(void))handler
声明:
标记要开始一个新的长期运行的后台任务
参数:
handler
应用程序后台剩余时间快到达为0的时候的一个处理回调,你应该使用这个回调来做一些清理工作和后台任务结束的标记,未能明确地结束任务将导致APP的终止,这个处理回调将在主线程中被同步调用,并立刻阻止app的暂停当app被通知的时候。
返回值:
一个新的后台任务的唯一的标示符,你必须将这个值传给endBackgroundTask:方法来标记任务的结束。如果无法在后台运行这个方法将返回UIBackgroundTaskInvalid。
描述:
这个方法会让你的app转到后台以后继续运行一段时间,你可以在一个任务未完成将会导致影响用户体验的情况下调用此方法。例如,你可以调用次方法来获取足够的时间来传输一个很重要的文件到远程服务器或者至少尝试标记一些错误。你不应该随意的调用这个方法来保持你的app在后台长期运行。
每一次调用此方法都应该有对应的endBackgroundTask:方法,app的后台运行时间是有限的(你可以通过backgroundTimeRemaining属性来获取这个可用时间)如果在这个时间耗尽之前你没有调用endBackgroundTask:方法来结束相应的每个后台任务,系统就会杀掉这个app。如果你提供了这个handler,系统将会在到期时间到达之前调用这个handler来给你这个机会来终止你的任务。
你可以在你应用程序执行的任何地方调用这个方法,你也可以多次调用这个方法来标记多个并行的后台任务,然而,每个任务必须分开终止,你可以通过这个方法的返回值来标记不同的任务。
为了便于调试,这个方法是基于调用函数或者方法的名称来为任务命名的,如果你需要自定义这个名称,可以使用beginBackgroundTaskWithName:expirationHandler:方法来代替。
这个方法可以在非主线程中安全调用。
注意:
如果你在调试后台任务遇到麻烦,你可以尝试使用beginBackgroundTaskWithName:expirationHandler:方法太替代这个方法,这个方法提供相同的功能但是可以给你一个调试可用的任务名称。
使用方法:
以下在子线程中打开一个定时器,并在应用程序进入到后台以后打开回调
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- NSTimer* timer = [NSTimer timerWithTimeInterval:10.0
- target:self
- selector:@selector(timerHandle)
- userInfo:nil
- repeats:YES];
- [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
- [[NSRunLoop currentRunLoop]run];
- });
以下回调方法是申请后台时间,当系统无法再给更多的时间的时候会调用handler中的block块,在这个block中,我们要做相应的清理工作并终止后台任务(备注:如果我们不调用endBackgroundTask:来终止相应的后台任务的话,好像还能继续运行后台任务,不知道为啥,苹果文档是说你不自己终止,系统会给你kill掉)
- -(void)applicationDidEnterBackground
- {
- UIApplication* application = [UIApplication sharedApplication];
- bgTaskIdentifier = [application beginBackgroundTaskWithExpirationHandler:^{
- NSLog(@"Starting background task with %f seconds remaining", application.backgroundTimeRemaining);
- if (bgTaskIdentifier != UIBackgroundTaskInvalid)
- {
- [application endBackgroundTask:bgTaskIdentifier];
- bgTaskIdentifier = UIBackgroundTaskInvalid;
- }
- }];
- }
- - (void)timerHandle
- {
- NSLog(@"timerHandle");
- }
我在子线程中开启了一个定时器,每隔10秒打印一次timerHandle,我在应用程序进入到后台以后申请了更多的后台时间,因此,当我按下home或者锁屏的时候,定时器方法会继续调用。
我们把定时器方法修改如下:
- - (void)timerHandle
- {
- NSLog(@"timerHandle");
- UIApplication* application = [UIApplication sharedApplication];
- if (bgTaskIdentifier != UIBackgroundTaskInvalid)
- {
- [application endBackgroundTask:bgTaskIdentifier];
- bgTaskIdentifier = UIBackgroundTaskInvalid;
- }
- }
当定时器一启动我们就按下home键,在第一个10秒会打印timerHandle,然后应用程序会终止后台任务,直到我们重新进入到前台,才会继续打印信息。
同理,如果我此时是在后台下载文件,完成以后应该通过以上方法来终止相应的后台任务,按照苹果文档说的不能什么事情也没干还让程序在后台运行,
以上的bgTaskIdentifier是全局的UIBackgroundTaskIdentifier型变量,用来标示不同的后台任务的。比如说我有一个定时器需要后台运行,还有一个文件需要后台下载,那么我们就要通过这个不同的标示符来分开申请后台时间,并在后台完成任务以后分别去终止相应地后台任务。