【问题标题】:Why does beginBackgroundTaskWithExpirationHandler allow an NSTimer to run in the background?为什么 beginBackgroundTaskWithExpirationHandler 允许 NSTimer 在后台运行?
【发布时间】:2015-03-26 16:02:19
【问题描述】:

我知道有很多关于类似主题的问题,但我认为没有人能完全解决这个问题(尽管我很高兴被证明是错误的!)。

首先是一些背景;我开发了一个在后台监控用户位置的应用程序,这工作正常,但电池使用率很高。为了尝试改进这一点,我的目标是每 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 会暂停,直到应用程序进入前台。

关于保持 NSTimers 在后台运行以及使用 beginBackgroundTaskWithExpirationHandler 的一些类似的 SO 问题,所以我在 NSTimer 启动之前将其添加到 viewDidLoad 方法中。

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 继续触发。

这是预期的行为吗?我将进行更多测试,看看这个解决方案是否长期有效,但我不禁觉得我在使用后台任务时遗漏了什么?

【问题讨论】:

    标签: ios ios8 nstimer cllocationmanager


    【解决方案1】:

    经过大量测试,发现 NSTimer 不会在任务过期后无限期运行。在某个时间点(可以是几个小时,可以是立即)计时器停止触发。

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

    Periodic iOS background location updates

    【讨论】:

      【解决方案2】:

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

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

      来自实现长时间运行的任务部分:

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

      大概初始化 CLLocationManager 让后台任务不被挂起就足够了。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-10-09
        • 2015-07-26
        相关资源
        最近更新 更多