为什么beginBackgroundTaskWithExpirationHandler允许NSTimer在后台运行?

时间:2015-03-26 16:02:19

标签: ios ios8 nstimer cllocationmanager

我知道有很多关于类似主题的问题,但我认为没有任何问题可以解决这个问题(虽然我很高兴被证明是错误的!)。

首先是一些背景;我开发了一个应用程序,在后台监控用户位置,这工作正常,但电池使用率很高。为了尝试改进这一点,我的目标是每隔x分钟“唤醒”,致电startUpdatingLocation获取职位,然后致电stopUpdatingLocation

考虑我将以下样本放在一起进行测试:

@interface ViewController ()
@property (strong, nonatomic) NSTimer *backgroundTimer;
@property (strong, nonatomic) CLLocationManager *locationManager;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // Add observers to monitor background / foreground transitions
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil];

    _locationManager = [[CLLocationManager alloc] init];
    [_locationManager requestAlwaysAuthorization];
    _locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
    _locationManager.delegate = self;
    _locationManager.distanceFilter=100;
    [_locationManager startUpdatingLocation];


    NSTimeInterval time = 60.0;
    _backgroundTimer =[NSTimer scheduledTimerWithTimeInterval:time target:self selector:@selector(startLocationManager) userInfo:nil repeats:YES];

}

-(void)applicationEnterBackground{
    NSLog(@"Entering background");
}

-(void)applicationEnterForeground{
    NSLog(@"Entering foreground");
}

-(void)startLocationManager{
    NSLog(@"Timer fired, starting location update");
    [_locationManager startUpdatingLocation];
}

-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
    NSLog(@"New location received");
    [_locationManager stopUpdatingLocation];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];    
}
@end

正如预期的那样,当应用程序进入后台时,CLLocationManager不会更新位置,因此NSTimer会暂停,直到应用程序进入前台。

一些类似的SO问题已经询问过如何让NSTimers在后台运行,以及beginBackgroundTaskWithExpirationHandler的使用,所以我在viewDidLoad开始之前将其添加到NSTimer方法。 / p>

UIBackgroundTaskIdentifier bgTask = 0;
UIApplication *app = [UIApplication sharedApplication];
NSLog(@"Beginning background task");
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
    NSLog(@"Background task expired");
    [app endBackgroundTask:bgTask];
}];
NSLog(@"bgTask=%d", bgTask);

经过短暂的测试(是否长期工作还有待证明)这似乎解决了这个问题,但我不明白为什么。

如果您查看下面的日志输出,您可以看到调用beginBackgroundTaskWithExpirationHandler达到了预期的目标,因为当应用进入后台时NSTimer会继续触发:

2015-03-26 15:45:38.643 TestBackgroundLocation[1046:1557637] Beginning background task
2015-03-26 15:45:38.645 TestBackgroundLocation[1046:1557637] bgTask=1
2015-03-26 15:45:38.703 TestBackgroundLocation[1046:1557637] New location received
2015-03-26 15:45:46.459 TestBackgroundLocation[1046:1557637] Entering background
2015-03-26 15:46:38.682 TestBackgroundLocation[1046:1557637] Timer fired, starting location update
2015-03-26 15:46:38.697 TestBackgroundLocation[1046:1557637] New location received
2015-03-26 15:47:38.666 TestBackgroundLocation[1046:1557637] Timer fired, starting location update
2015-03-26 15:47:38.677 TestBackgroundLocation[1046:1557637] New location received
2015-03-26 15:48:38.690 TestBackgroundLocation[1046:1557637] Timer fired, starting location update
2015-03-26 15:48:38.705 TestBackgroundLocation[1046:1557637] New location received
2015-03-26 15:48:42.357 TestBackgroundLocation[1046:1557637] Background task expired
2015-03-26 15:49:38.733 TestBackgroundLocation[1046:1557637] Timer fired, starting location update
2015-03-26 15:49:38.748 TestBackgroundLocation[1046:1557637] New location received
2015-03-26 15:50:38.721 TestBackgroundLocation[1046:1557637] Timer fired, starting location update
2015-03-26 15:50:38.735 TestBackgroundLocation[1046:1557637] New location received
2015-03-26 15:50:44.361 TestBackgroundLocation[1046:1557637] Entering foreground

从这里,您可以看到后台任务在预期大约3分钟后过期,但NSTimer在此之后继续发射。

预计会出现这种情况吗?我会进行一些测试,看看这个解决方案是否能够长期运行,但我不禁觉得我在使用后台任务时遗漏了一些东西?

2 个答案:

答案 0 :(得分:1)

经过大量测试后,事实证明NSTimer在任务到期后无法无限期运行。在某些时候(可以是几小时,可以立即),计时器停止射击。

最后,我的问题的解决方案是降低GPS精度并在我不需要监控位置时增加距离滤波器,如下所述:

Periodic iOS background location updates

答案 1 :(得分:0)

可以选择将后台任务用于长时间运行的任务,然后iOS不会暂停执行。

根据要使用它的Background execution文档,应该将 UIBackgroundModes 键添加到Info.plist,其值为 location (用于位置更新)。还有另一个原因:

实施长期运行的任务部分

  

实施这些服务的应用必须声明其支持的服务,并使用系统框架来实现这些服务的相关方面。声明服务可以让系统知道您使用哪些服务,但在某些情况下,系统框架实际上会阻止您的应用程序被暂停。

可能只需初始化CLLocationManager就可以导致后台任务不被暂停。