在后台启动更新位置,只调用了10-20次didUpdatingToLocation。

9

测试设备:iPhone 5 (iOS 7)

我有一个应用程序,使用了RegionMonitoringupdateLocation。如果进入了某个区域,didEnterRegion会按预期调用。然后我调用startUpdatingLocation。但是didUpdateToLocation方法只被调用了10-20次,而它应该会更新位置直到计时器触发。

相关代码:

CLLocationManager *_locationManager;
NSTimer *_timer;

-(void)initLocationManager 
{
    _locationManager = [[CLLocationManager alloc] init];
    _locationManager.delegate = self;
    [_locationManager setActivityType:CLActivityTypeOther];
    [_locationManager setDesiredAccuracy:kCLLocationAccuracyBestForNavigation];
    [_locationManager setPausesLocationUpdatesAutomatically:NO];
    [_locationManager setDistanceFilter:kCLDistanceFilterNone];
}


//Did Enter Region, called as expected:
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
    [_locationManager startUpdatingLocation];
    _timer = [NSTimer scheduledTimerWithTimeInterval:300.0f target:self selector:@selector(scheduleMethod:) userInfo:nil repeats:NO];
}


//Timer Fire Method:
- (void) scheduleMethod:(NSTimer*)timer
{
    [Utils writeToLog:@"Timer-Stop"];
    [_locationManager stopUpdatingLocation];
}


//This Method only called 10-20 Times (in the first 10-20 Seconds) and not the complete 5 Minutes:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
     [Utils writeToLog:@"LocationUpdate!"];
}

到目前为止,我尝试过: 在locationManagerDidPauseLocationUpdates方法中重新启动更新,但似乎从未被调用:

-(void)locationManagerDidPauseLocationUpdates:(CLLocationManager *)manager
{
    [WRUtils writeToLog:@"LocationUpdate paused, restarted"];
    [_locationManager startUpdatingLocation];
}

didFailWithError方法中检查错误,但也没有被调用。并且尝试了一些属性的设置:

[_locationManager setActivityType:CLActivityTypeOther];
[_locationManager setDesiredAccuracy:kCLLocationAccuracyBestForNavigation];
[_locationManager setPausesLocationUpdatesAutomatically:NO];

我猜 Plist 设置是正确的: 必需的后台模式 YES 必需的后台模式 项目 0 应用程序注册以获取位置更新
我该如何解决这个问题?
3个回答

27

苹果在 iOS 7 中引入了新政策。如果您在应用程序处于后台时调用“startUpdatingLocation”,iOS 7 将不再在后台提供位置更新。此时,只有在应用程序切换到前台时才会收到更新。

当使用地理围栏功能时,每次接收到 RegionEntered/Exited 通知时,您的应用程序只能获得几秒钟的时间来处理该通知。在这几秒钟内,您也可以获得位置更新。在这几秒钟结束后,iOS 7 将暂停您的应用程序。

您可以使用后台任务来获得更多时间,但是在 iOS 7 中,苹果还将应用程序在后台运行的时间从 10 分钟(iOS 6 及更早版本)缩短为仅 3 分钟。看起来这 3 分钟是应用程序在后台的总时间。这意味着您不能要求 iOS 7 十次获得 1 分钟的后台时间,您只能获得总共 3 分钟的后台时间,因此,在第三次请求 1 分钟后,您的应用程序将不再获得任何后台时间。

在后台获取位置更新的唯一机会是在应用程序前台调用“startUpdatingLocation”。这很遗憾,特别是当您只需要根据区域(Enter/Exit)消息获得位置更新时,因为您需要始终让位置更新保持运行状态。但至少可以通过在不需要位置更新时将准确度值设置为 kCLLocationAccuracyThreeKilometers,并仅在真正需要地理坐标时将准确度设置为 kCLLocationAccuracyBest 来降低电池使用率。在这种情况下,iOS 不会启动 GPS 来获取 kCLLocationAccuracyThreeKilometers 的值,因此电池使用率会适中。

此外,在 iOS 7 下,准确度值 kCLLocationAccuracyBestForNavigation 似乎会引起问题。如果设备没有连接外部电源,我将无法使用此值获得任何位置更新。

总而言之,新的iOS 7位置更新策略使得开发某些类型的应用程序变得更加困难。现在你不得不注册整个应用程序的生命周期才能接收位置更新,而不是只在需要时进行注册。这当然会更快地耗尽电池,尽管苹果推出这项新政策的初衷可能恰恰相反。
更新:
经过更多测试,我找到了一种解决问题的方法。苹果的文档提到,使用“显著位置变化”API时,即使在后台启动“startUpdatingLocation”,应用程序也可以在后台接收位置更新。
我的第一轮测试没有很好地运作。我是在区域监控委托方法内注册我的应用程序以获取重大位置更新,然后调用“startUpdatingLocation”(因此只有在需要时才启用此位置服务),但是这仍然不能像文档中所建议的那样在后台提供位置更新。
但是,如果在应用程序启动后立即开始监听重要位置更改(并永远不关闭此功能),则可以在应用程序后台调用“startUpdatingLocation”并在后台接收位置更新。持续打开“显著位置变化”功能的电池使用似乎非常低,所以这可能是大多数应用程序的解决方案。
您必须检查设备上是否支持“显著位置变化”功能,但是似乎所有当前的设备都支持此功能。即使第五代iPod Touch也支持它(iPod Touch无法使用基站进行位置更新,这是该功能的基本方法,根据苹果的文档,所以我们可以假设所有运行iOS 7的当前设备都可以使用“显著位置更新”API。尽管检查此功能是否真正可用可能是个好主意,也许在某些情况下该功能不可用)。使用“显著位置变化”API可能有一个缺点:如果应用程序在后台被iOS终止以重复利用其内存供其他应用程序使用,那么每当设备发生“显着”移动(根据文档:当基站发生改变时,但每5分钟最多一次),该应用程序就会在后台启动并不必要地重复运行。因此,只需要在某个特定区域退出或进入时激活应用程序的应用程序将一直被启动,并始终被通知位置已更改,而不仅仅是在这些区域。但我认为这仍然比始终使用标准位置更新要好得多。

使用显著位置变化功能时,我的iPhone 5s仅在夜间消耗1%的电池,而使用精度设置为3km的标准位置更新则消耗12%的电池。

希望这可以帮助所有正在与新的iOS 7行为斗争的开发人员。


你的“前台启动并设置为三公里”的技巧确实可行,但是它消耗的电量比24小时内10%更多,达到了50%。这太多了!该死,我为什么不能只在后台启动特定区域的位置更新呢... :( 如果你有其他想法,请告诉我。 - flo
至少在我的iPhone 5s上,电池使用似乎足够中等以供使用。我认为iOS使用新的M7芯片检查设备是否移动,如果整晚都放在桌子上,iOS知道位置不可能改变,因此甚至不尝试获取位置更新以节省能量。但是,在没有M7芯片的设备上,情况是不同的。-我建议向苹果发送错误报告/功能请求,也许如果有足够多的开发人员这样做,他们会考虑回到旧的行为... - Alex
嗨,我正在使用startMonitoringSignificantLocationChanges进行地理围栏。当后台模式下发生重大位置更改时,它会获取当前位置并调用服务器以获取新位置,并将其设置为地理围栏。我认为这符合新的苹果政策。@Alex,你能告诉我这样做可以吗? - mychar
4
这个回答简直救了我的命……我希望我可以给你点赞100次!! - LcSalazar
你的回答更新非常有帮助。启动重要位置更改更新允许我在后台更新位置。此外,我测试了将位置设置为kCLLocationAccuracyThreeKilometers,并每30分钟仅显示一个带地址的通知,而电池消耗在我的iPhone X和iPhone 7上24小时内只有1%。 - David Hopkins

0

在你的didEnterRegion方法中使用以下代码来重新开始更新

[_locationManager startMonitoringSignificantLocationChanges];
if ([_locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)])
{
    _locationManager.allowsBackgroundLocationUpdates =YES;

}
[_locationManager startUpdatingLocation];

0
在后台计时器不会运行,因此您的计时器对象将无法响应,您需要创建后台任务处理程序,请查看下面链接的评论, 如何在应用程序的后台运行计时器? 在后台如果您想要继续位置服务,则需要设置pausesLocationUpdatesAutomatically标志,有关标志信息,请参见 pausesLocationUpdatesAutomatically标志信息
  if ([self.locationManager respondsToSelector:@selector(pausesLocationUpdatesAutomatically)]) {
  self.locationManager.pausesLocationUpdatesAutomatically = NO;
  }

请查看我在iPhone 5中位置服务变为“非活动”状态的评论。

对于位置管理器,以下是用于位置更新的CLLocationManagerDelegate方法:

   - (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
       fromLocation:(CLLocation *)oldLocation [Deprecated in __IPHONE_6_0]

  - (void)locationManager:(CLLocationManager *)manager
 didUpdateLocations:(NSArray *)locations __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_6_0);

你找到了哪里

  - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation ??

谢谢您提供Timer的提示,但是我将pauseLocationUpdateAutomatically设置为NO(请参见上面的示例代码)。这样初始化后,didUpdatingToLocation方法只会被调用10-20次。 - flo
为什么你在stopUpdatingLocation中使用了 _timer - (void) scheduleMethod:(NSTimer*)timer? - Vishwa Patel
因为我需要在进入地理围栏后进行5分钟的移动统计(距离、速度),并在5分钟后停止更新位置。你说得对,这在后台不起作用,所以我必须使用后台任务处理程序来完成。但我的问题是为什么它停止调用didupdateToLocation(我现在已经学会了定时器不起作用),所以stopUpdatetingLocation从未被调用...但它只是停止了更新。 - flo
我找到了它:https://developer.apple.com/library/mac/documentation/CoreLocation/Reference/CLLocationManagerDelegate_Protocol。上面是一个字面错误,我只使用了第一个委托方法。但你是对的,它已经被弃用了,我会尝试其他的委托方法!如果它有效,我会标记为已解决。到目前为止谢谢你! - flo
整个方法就像这样 - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation,在那个页面上也没有半个方法 - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation。 - Vishwa Patel
这只是一个字面错误,我之前使用了fromLocation:方法。但现在测试另一个方法。我已经对此进行了编辑。 - flo

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