由于iOS系统为“伪后台”运行模式,当按下HOME键时,如程序不做任何操作,应用会有5秒的执行缓冲时间,随机程序被挂起,所有任务终端,包括计时器和位置更新等操作,但程序打开后台模式开关后,部分任务可以再后台执行,如音频,定位,蓝牙,下载,VOIP,即便如此,程序的后台运行最多可以延长594秒(大概是10分钟)。不幸的是,程序在声明后台模式后很有可能在app上架时被拒。基于此,我研究出了不用申明后台模式就能让计时器和定位在app进入前台时继续运行的方法。
实现原理如下:
利用iOS的通知机制,在程序进入后台和再次回到前台时发送通知,并记录进入后台的当前时间和再次回到前台的当前时间,算出两者的时间间隔,在程序任何需要的地方添加通知监听者,在监听方法中执行代码块,代码块内参数为通知对象和计算出的时间间隔。以计时器为例,程序再进入后台后,计时器停止运行,此时运用上述方法,在程序再次回到前台时执行代码块中内容,将程序进入后台时计时器的当前时间间隔加上代码块的时间间隔参数就能使计时器准确无误地计时。废话不多说,上代码:
在AppDelegate.m实现文件中:
- (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. [[NSNotificationCenter defaultCenter]postNotificationName:UIApplicationDidEnterBackgroundNotification object:nil]; } - (void)applicationWillEnterForeground:(UIApplication *)application { // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. [[NSNotificationCenter defaultCenter]postNotificationName:UIApplicationWillEnterForegroundNotification object:nil]; }
代码说明:程序进入后台后,利用系统通知机制通知程序进入后台和再次回到前台,监听对象为所有对象。
之后定义一个处理程序进入后台的类YTHandlerEnterBackground
// // YTHandlerEnterBackground.h // 分时租赁 // // Created by 柯其谱 on 17/2/24. // Copyright ? 2017年 柯其谱. All rights reserved. // #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> /** 进入后台block typedef */ typedef void(^YTHandlerEnterBackgroundBlock)(NSNotification * _Nonnull note, NSTimeInterval stayBackgroundTime); /** 处理进入后台并计算留在后台时间间隔类 */ @interface YTHandlerEnterBackground : NSObject /** 添加观察者并处理后台 */ + (void)addObserverUsingBlock:(nullable YTHandlerEnterBackgroundBlock)block; /** 移除后台观察者 */ + (void)removeNotificationObserver:(nullable id)observer; @end
在YTHandlerEnterBackground.m实现文件中:
// // YTHandlerEnterBackground.m // 分时租赁 // // Created by 柯其谱 on 17/2/24. // Copyright ? 2017年 柯其谱. All rights reserved. // #import "YTHandlerEnterBackground.h" @implementation YTHandlerEnterBackground + (void)addObserverUsingBlock:(YTHandlerEnterBackgroundBlock)block { __block CFAbsoluteTime enterBackgroundTime; [[NSNotificationCenter defaultCenter]addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { if (![note.object isKindOfClass:[UIApplication class]]) { enterBackgroundTime = CFAbsoluteTimeGetCurrent(); } }]; __block CFAbsoluteTime enterForegroundTime; [[NSNotificationCenter defaultCenter]addObserverForName:UIApplicationWillEnterForegroundNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { if (![note.object isKindOfClass:[UIApplication class]]) { enterForegroundTime = CFAbsoluteTimeGetCurrent(); CFAbsoluteTime timeInterval = enterForegroundTime-enterBackgroundTime; block? block(note, timeInterval): nil; } }]; } + (void)removeNotificationObserver:(id)observer { if (!observer) { return; } [[NSNotificationCenter defaultCenter]removeObserver:observer name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter]removeObserver:observer name:UIApplicationWillEnterForegroundNotification object:nil]; } @end
该类实现了用来添加通知监听者并处理后台和移除通知监听者的方法,需要注意的是,在addObserverUsingBlock方法中,必须有if (![note.object isKindOfClass:[UIApplication class]])的判断,否则addObserverForName方法中的代码块会执行多次,此代码执行了两次。addObserverUsingBlock方法是在viewWillAppear方法中调用添加通知监听者,在viewWillDisappear方法中调用移除通知监听者。
例如,在使用了计时器NSTimer控制器中:
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [YTHandlerEnterBackground addObserverUsingBlock:^(NSNotification * _Nonnull note, NSTimeInterval stayBackgroundTime) { self.rentTimerInterval = self.rentTimerInterval-stayBackgroundTime; }]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [self.timer invalidate]; [YTHandlerEnterBackground removeNotificationObserver:self]; }
我定义了一个倒计时5分钟的计时器对象timer属性,并定义了一个计时器当前倒计时时间间隔rentTimerInterval属性,在添加通知监听者代码块中,rentTimerInterval等于进入后台时的倒计时时间间隔减去程序停留在后台的时间间隔,当计时器再次回到前台时,计时器此时的时间间隔是持续的。虽然计时器并未在后台持续运行,但是使用了此方法,同样实现了计时器的正确即时。
同样的,当程序存在位置更新功能时,当程序进入后台,位置服务对象会自动停止更新,此时的作法依然是调用上述两个处理进入后台的方法,使得程序进入后台后,再次开始定位:
在需要位置更新的类中:
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; self.locService.delegate = self; [self.locService startUserLocationService]; //进入后台再进入前台重新开始定位 [YTHandlerEnterBackground addObserverUsingBlock:^(NSNotification * _Nonnull note, NSTimeInterval stayBackgroundTime) { [self.locService startUserLocationService]; }]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; //停止定位 self.locService.delegate = nil; [self.locService stopUserLocationService]; //移除后台监听 [YTHandlerEnterBackground removeNotificationObserver:self]; }
此处使用的是百度地图SDK
利用这种方法,像是计时器和位置更新等需要在后台运行的任务都可以实现相应的需求,只是麻烦的是,在任何需要的类中都要调用这两种方法,你可以根据自己的需求,在程序进入后台和再次回到前台时添加别的参数(通知对象参数是必须的),例如保存进入后台前的操作等等。或是定义不同的添加通知监听者的方法以实现不同的需求。