在应用程序进入后台后每n分钟获取用户位置

27

我正试图实施这篇文章中提供的建议。

不幸的是,这些步骤对我来说并不清晰。我尝试实现这些建议,但即使在我启动和停止locationServices之后,backgroundTimeRemaining仍然会减少。 这是我开发它的方式:

- (void)applicationDidEnterBackground:(UIApplication *)application {

    UIApplication*    app = [UIApplication sharedApplication];

    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];

    // Start the long-running task and return immediately.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // Do the work associated with the task.
        self.timer = nil;
        [self initTimer];

    });
}

初始化计时器:

- (void)initTimer {

    // Create the location manager if this object does not
    // already have one.
    if (nil == self.locationManager)
        self.locationManager = [[CLLocationManager alloc] init];

    self.locationManager.delegate = self;
    [self.locationManager startMonitoringSignificantLocationChanges];

    if (self.timer == nil) {
        self.timer = [NSTimer scheduledTimerWithTimeInterval:0.3
                                              target:self
                                            selector:@selector(checkUpdates:)
                                            userInfo:nil
                                             repeats:YES];
    }
}

检查更新:

- (void)checkUpdates:(NSTimer *)timer{
    UIApplication*    app = [UIApplication sharedApplication];
    double remaining = app.backgroundTimeRemaining;
    if(remaining < 580.0) {
        [self.locationManager startUpdatingLocation]; 
        [self.locationManager stopUpdatingLocation]; 
        [self.locationManager startMonitoringSignificantLocationChanges];
    }
    DbgLog(@"Reminaing %f", app.backgroundTimeRemaining);
}
有人能指出我的代码可能有什么问题吗?initTimer和checkUpdates都在后台执行时调用,但只有在执行时间(约为10分钟)内才会被调用。我希望这个应用程序可以每n分钟更新一次位置,一直更新下去。
我的应用程序的UIBackgroundModes设置为location。
更新: 现在我在didUpdateToLocation和didFailWithError上重置了计时器,但backgroundTimeRemaining仍然在减少:
- (void)locationManager:(CLLocationManager *)manager 
didUpdateToLocation:(CLLocation *)newLocation 
       fromLocation:(CLLocation *)oldLocation {

NSLog(@"Did Update Location = %f / %f", [newLocation coordinate].latitude, [newLocation coordinate].longitude);

UIApplication*    app = [UIApplication sharedApplication];

bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
    [app endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}];

// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // Do the work associated with the task.

    [self initTimer];

});
}

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {

[self.locationManager stopUpdatingLocation]; 
UIApplication*    app = [UIApplication sharedApplication];

bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
    [app endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}];

// Start the long-running task and return immediately.
[self initTimer];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // Do the work associated with the task.

});

}

我还在使定时器失效:

- (void)checkUpdates:(NSTimer *)timer{
UIApplication*    app = [UIApplication sharedApplication];
double remaining = app.backgroundTimeRemaining;
if(remaining < 580.0 && remaining > 570.0) {
    [self.timer invalidate];
    self.timer = nil;
    [self.locationManager startUpdatingLocation]; 
    [self.locationManager stopUpdatingLocation]; 
}
DbgLog(@"*************************Checking for updates!!!!!!!!!!! Reminaing %f", app.backgroundTimeRemaining);
}

3
你的应用程序在App Store里被接受了吗?我正在考虑采用相同的方法,但我需要确保我的应用程序不会被拒绝。顺便说一句,谢谢! - aslı
1
你成功了吗? - SRP-Achiever
@scurioni,你如何在“冷却”期间(即从stopUpdatingLocation到下一次startUpdatingLocation之间)移除状态栏图标?即使调用了stopUpdatingLocation,它仍然存在。只有在显式调用stopUpdatingLocation(从ViewController或类似的东西中)后,该图标才会消失。 - dor506
4个回答

10

对于任何一个试图解决这个问题的人,我有一个简单的解决方案。

  1. 学习来自raywenderlich.com的示例。示例代码完美运行,但不幸的是在后台定位过程中没有计时器。这将无限期地运行。
  2. 使用以下代码片段添加计时器:

    -(void)applicationDidEnterBackground {
      [self.locationManager stopUpdatingLocation];
    
      UIApplication* app = [UIApplication sharedApplication];
    
      bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
      }];
    
      self.timer = [NSTimer scheduledTimerWithTimeInterval:intervalBackgroundUpdate
                                                    target:self.locationManager
                                                  selector:@selector(startUpdatingLocation)
                                                  userInfo:nil
                                                   repeats:YES];
    }
    
  3. 只是别忘了在info.plist中添加“应用程序注册位置更新”。


4
bgTask 是从哪里来的? - SirRupertIII
2
@property (nonatomic) UIBackgroundTaskIdentifier bgTask; 在头文件类中。 - HelmiB
2
@HelmiB:在iOS 7中它无法工作,你能否建议其他方法?我需要在后台启动locationmanger。 - user2265763
1
在iOS 8上,您可以检查此方法:https://dev59.com/8mw15IYBdhLWcg3w3vle#28373247 它应该也适用于iOS 7。 - Leszek Szary
在“冷却”期间(从stopUpdatingLocation到下一次startUpdatingLocation之间),如何删除状态栏图标?即使调用了stopUpdatingLocation,它仍然保留在那里。只有在显式调用stopUpdatingLocation(从ViewController或类似的东西)后,该图标才会消失。 - dor506

9

经过几天尝试所有可能的解决方案后,我终于成功了。以下是我的解决方案中出现的问题:

  • 我需要设置2个UIBackgroundTaskIdentifiers,一个用于计时器,另一个用于位置管理器。

在我的解决方案中,只需添加:

UIApplication *app = [UIApplication sharedApplication];

bgTask2 = [app beginBackgroundTaskWithExpirationHandler:^{ 
[app endBackgroundTask:bgTask2]; 
bgTask2 = UIBackgroundTaskInvalid; }];

self.locationManager.delegate = self; 
[self.locationManager startUpdatingLocation]; 

32
你将这些行添加到哪个方法中? - Michal
1
@scurioni,您可以请提供您的完整工作代码吗? - Sharme
请问您能否分享一下您的AppDelegate工作代码?我们遇到了相同的问题。 - Sharon Nathaniel

4

一旦您进入后台,您只能有额外的10分钟时间。无法延长时间。您应该使用位置后台服务。


1
这些服务目前尚未有基于时间的版本,而是在每次更新时触发或者在位置发生重大变化时触发。 - Robot Woods
1
他们所采用的方法(一眼看过去)是使用全功率后台位置服务(因此您将不断收到更新,但每隔X分钟处理一次)。这可能有效,但你的电池每晚都会耗尽。 - Robot Woods
1
我在另一篇帖子中找到了引用的讨论:https://devforums.apple.com/thread/146398?start=0&tstart=0 - Robot Woods
1
当剩下10分钟时,可以调用startUpdatingLocation并使用CLLocationManager委托回调(didFail.. or didUpdate...)来设置下一个10分钟的后台任务。如果已经达到所需的时间间隔,则此时它将连接到服务器并发送必要的数据。例如,在60分钟周期的40分钟处,则不连接到服务器,并继续正常设置新的后台任务-没有问题。 - Robot Woods
@RobotWoods 请问,我无法弄明白如何让这个问题起作用,尽管 scurioni 已经回答了如何让它起作用。在下面的帖子中,scurioni 没有说出要把“这些行”放在哪里。有什么提示吗? - Michal
显示剩余4条评论

2

可用的代码(完整的逐步代码) 修改了scurioni的代码

步骤1

  • 进入项目 -> 能力 -> 后台模式 -> 选择位置更新。
  • 进入项目 -> 信息 -> 添加一个键NSLocationAlwaysUsageDescription和一个可选字符串。

步骤2

将此代码添加到AppDelegate.m中

@interface AppDelegate ()<CLLocationManagerDelegate>
@property (strong, nonatomic) CLLocationManager *locationManager;
@property (strong, nonatomic) NSTimer *timer;
@end

第三步 将以下代码添加到AppDelegate.m文件中的applicationDidEnterBackground方法中

    - (void)applicationDidEnterBackground:(UIApplication *)application {
        UIApplication *app = [UIApplication sharedApplication];
        __block UIBackgroundTaskIdentifier bgTaskId =
        [app beginBackgroundTaskWithExpirationHandler:^{
            [app endBackgroundTask:bgTaskId];
            bgTaskId = UIBackgroundTaskInvalid;
        }];

        dispatch_async( dispatch_get_main_queue(), ^{
            self.timer = nil;
            [self initTimer];
            [app endBackgroundTask:bgTaskId];
            bgTaskId = UIBackgroundTaskInvalid;
        });
    }

- (void)initTimer {
    if (nil == self.locationManager)
        self.locationManager = [[CLLocationManager alloc] init];

    self.locationManager.delegate = self;
    [self.locationManager requestAlwaysAuthorization];
    [self.locationManager startMonitoringSignificantLocationChanges];
    if (self.timer == nil) {
        self.timer = [NSTimer scheduledTimerWithTimeInterval:0.3
                                                      target:self
                                                    selector:@selector(checkUpdates:)
                                                    userInfo:nil
                                                     repeats:YES];
    }
}

- (void)checkUpdates:(NSTimer *)timer{
    UIApplication *app = [UIApplication sharedApplication];
    double remaining = app.backgroundTimeRemaining;
    if(remaining < 580.0) {
        [self.locationManager startUpdatingLocation];
        [self.locationManager stopUpdatingLocation];
        [self.locationManager startMonitoringSignificantLocationChanges];
    }
}

- (void)locationManager:(CLLocationManager *)manager
    didUpdateToLocation:(CLLocation *)newLocation
           fromLocation:(CLLocation *)oldLocation {
    NSLog(@"Did Update Location = %f / %f", [newLocation coordinate].latitude, [newLocation coordinate].longitude);
    [self updateLocationWithLatitude:[newLocation coordinate].latitude andLongitude:[newLocation coordinate].longitude];
    UIApplication*    app = [UIApplication sharedApplication];
    __block UIBackgroundTaskIdentifier bgTask =
    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self initTimer];
    });
}

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
    [self.locationManager stopUpdatingLocation];
    UIApplication *app = [UIApplication sharedApplication];
    __block UIBackgroundTaskIdentifier bgTask =
    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
    [self initTimer];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // Do the work associated with the task
    });
}

-(void)updateLocationWithLatitude:(CLLocationDegrees)latitude
                     andLongitude:(CLLocationDegrees)longitude{
//Here you can update your web service or back end with new latitude and longitude
}

无法工作,位置的委托方法未被调用。 - Hitarth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接