定期的iOS背景位置更新


91

我正在编写一个需要高精度和低频更新背景位置的应用程序。解决方案似乎是后台NSTimer任务,该任务启动位置管理器的更新,然后立即关闭。曾有人问过这个问题:

如何在iOS应用程序中每n分钟获取一次后台位置更新?

应用程序进入后台运行后每隔N分钟获取一次用户位置

iOS不是典型的后台位置跟踪计时器问题

iOS长期运行的背景计时器,带有“位置”背景模式

基于位置跟踪的iOS全职后台服务

但是我还没有一个起码的例子。在尝试了上述公认答案的所有排列之后,我提出了一个起点。输入背景:

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    self.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        NSLog(@"ending background task");
        [[UIApplication sharedApplication] endBackgroundTask:self.bgTask];
        self.bgTask = UIBackgroundTaskInvalid;
    }];


    self.timer = [NSTimer scheduledTimerWithTimeInterval:60
                                                  target:self.locationManager
                                                selector:@selector(startUpdatingLocation)
                                                userInfo:nil
                                                 repeats:YES];
}

和委托方法:

- (void)locationManager:(CLLocationManager *)manager 
    didUpdateToLocation:(CLLocation *)newLocation 
           fromLocation:(CLLocation *)oldLocation {

    NSLog(@"%@", newLocation);

    NSLog(@"background time: %f", [UIApplication sharedApplication].backgroundTimeRemaining);
    [self.locationManager stopUpdatingLocation];

}

当前的行为是backgroundTimeRemaining从180秒减少到零(在记录位置时),然后执行到期处理程序,并且不生成进一步的位置更新。如何修改上面的代码,以便在后台无限期地接收定期的位置更新

更新:我的目标是iOS 7,并且似乎有一些证据表明后台任务的行为有所不同:

从后台任务启动iOS 7中的位置管理器


您没有提及要定位的iOS版本。例如,iOS 7具有与其前身不同的多任务处理系统。
杰森·怀特霍恩

@JasonWhitehorn好点。我已经更新了问题。
pcoving 2013年

2
@pcoving:即使应用程序被终止或终止,您的解决方案也能正常工作吗?
尼拉夫

Answers:


62

似乎stopUpdatingLocation是触发后台看门狗计时器的原因,因此我将其替换didUpdateLocation为:

[self.locationManager setDesiredAccuracy:kCLLocationAccuracyThreeKilometers];
[self.locationManager setDistanceFilter:99999];

看来可以有效关闭GPS的电源。NSTimer然后,背景选择器变为:

- (void) changeAccuracy {
    [self.locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
    [self.locationManager setDistanceFilter:kCLDistanceFilterNone];
}

我正在做的是定期切换精度,以每隔几分钟获取高精度坐标,并且由于locationManager尚未停止,因此backgroundTimeRemaining保持在最大值。这将电池消耗从每小时约10%(kCLLocationAccuracyBest在后台保持不变)降低到每小时在我的设备上约2%。


4
您可以使用完整的工作示例来更新您的问题吗?像这样的东西:“更新:我想通了。这是我的解决方案……”
smholloway

@Virussmca我恢复了以前的答案,该答案在性能相似的情况下更加健壮。完全停止位置更新似乎会触发争用条件(或其他一些不确定的情况)。
pcoving

这种方法无法按您期望的那样工作:1)增加distanceFilter属性的值不会节省电池,因为gps仍然必须继续确定您的位置。2)在iOS 7中,背景时间不是连续的。3)您可能要考虑值得注意的ChangeLocation服务。4)除了您在iOS 7中的外观之外,您无法启动UIBackgroundModes-从背景定位...
aMother

2
@aMother 1)我意识到增加距离过滤器(并且不处理回调)并不能节省很多电池。但是,setDesiredAccuracy可以!它依赖于基本上免费提供的手机信号塔信息。2)不连续?3)我以粗体指定了我需要高精度。重大的位置更改不提供此功能。4)定位服务未从后台启动/停止。
pcoving

2
我知道这是一篇老文章,但是自从升级到iOS9以来,使用此技巧的应用程序用户已经注意到电池使用量显着增加。我还没有进行调查,但是我觉得这个“窍门”可能不再起作用了?
加里·赖特

45

我确实使用定位服务编写了一个应用,该应用必须每10秒发送一次位置。而且效果很好。

只需使用“ allowDeferredLocationUpdatesUntilTraveled:timeout按照Apple的文档 ”方法即可。

步骤如下:

必需:注册后台模式以更新位置。

1.使用所需的精度和filteredDistance创建LocationManger和startUpdatingLocation:

-(void) initLocationManager    
{
    // Create the manager object
    self.locationManager = [[[CLLocationManager alloc] init] autorelease];
    _locationManager.delegate = self;
    // This is the most important property to set for the manager. It ultimately determines how the manager will
    // attempt to acquire location and thus, the amount of power that will be consumed.
    _locationManager.desiredAccuracy = 45;
    _locationManager.distanceFilter = 100;
    // Once configured, the location manager must be "started".
    [_locationManager startUpdatingLocation];
}

2.为了在后台使用“ allowDeferredLocationUpdatesUntilTraveled:timeout”方法使应用永远运行,必须在应用移至后台时使用新参数重新启动updateLocation,如下所示:

- (void)applicationWillResignActive:(UIApplication *)application {
     _isBackgroundMode = YES;

    [_locationManager stopUpdatingLocation];
    [_locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
    [_locationManager setDistanceFilter:kCLDistanceFilterNone];
    _locationManager.pausesLocationUpdatesAutomatically = NO;
    _locationManager.activityType = CLActivityTypeAutomotiveNavigation;
    [_locationManager startUpdatingLocation];
 }

3.应用通过“ locationManager:didUpdateLocations:”回调正常获取更新位置:

-(void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
//  store data
    CLLocation *newLocation = [locations lastObject];
    self.userLocation = newLocation;

   //tell the centralManager that you want to deferred this updatedLocation
    if (_isBackgroundMode && !_deferringUpdates)
    {
        _deferringUpdates = YES;
        [self.locationManager allowDeferredLocationUpdatesUntilTraveled:CLLocationDistanceMax timeout:10];
    }
}

4.但是,您应该根据自己的目的在“ locationManager:didFinishDeferredUpdatesWithError:”回调中处理数据。

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

     _deferringUpdates = NO;

     //do something 
}

5. 注意:我认为每次应用程序在背景/地面模式之间切换时,我们都应该重置LocationManager的参数。


这很棒,我确实认为这是去年WWDC中提到的正确方法!感谢您的发布
prasad1250

@ samthui7感谢这项工作,但终止后我也需要获取位置。怎么会这样。??
Mohit Tomar

@ samthui7 ..请参阅此阙...... stackoverflow.com/questions/37338466/...
苏拉杰Sukale

2
@amar:我也检查了iOS 10 beta,它仍然有效。请注意,我们需要在startUpdatingLocation:allowsBackgroundLocationUpdates = YESrequestAlwaysAuthorization或之前添加一些代码requestWhenInUseAuthorization。例如:`CLLocationManager * locationManager = [[CLLocationManager alloc] init]; locationManager.delegate =自我;locationManager.allowsBackgroundLocationUpdates = YES; [locationManager requestAlwaysAuthorization]; [locationManager startUpdatingLocation];`
samthui7 '16

1
@Nirav号。据我所知,我认为Apple无法让我们这样做。
samthui7年

38
  1. 如果您UIBackgroundModes的plist中有location键,则无需使用beginBackgroundTaskWithExpirationHandlermethod。那是多余的。另外,您使用它的方式不正确(请参见此处),但由于设置了plist而已,所以这是没有意义的。

  2. 使用UIBackgroundModes locationplist中的,该应用程序将仅在运行时无限期地继续在后台CLLocationManger运行。如果你打电话stopUpdatingLocation在后台,则该应用将停止并且不会再次启动。

    也许你可以调用beginBackgroundTaskWithExpirationHandler之前调用stopUpdatingLocation,然后调用后,startUpdatingLocation你可以调用endBackgroundTask,以保持它转到后台运行,而GPS是停了,但我从来没有尝试过-它只是一个想法。

    另一个选择(我没有尝试过)是使位置管理器在后台运行,但是一旦获得准确的位置,请将desiredAccuracy属性更改为1000m或更高,以使GPS芯片关闭(以节省电池)。然后10分钟后,当您需要另一个位置更新时,将其改desiredAccuracy回100m以打开GPS,直到获得准确的位置,然后重复。

  3. 当您致电startUpdatingLocation位置经理时,您必须给它时间来获得职位。您不应该立即致电stopUpdatingLocation。我们让它最多运行10秒钟,或者直到获得未缓存的高精度位置为止。

  4. 您需要过滤掉缓存的位置,并检查所获得位置的准确性,以确保其满足最低要求的准确性(请参见此处)。您获得的第一个更新可能是10分钟或10天。您获得的第一个精度可能是3000m。

  5. 考虑改用重要的位置更改API。收到重大更改通知后,您可以开始CLLocationManager几秒钟以获取高精度位置。我不确定,我从未使用过重要的位置更改服务。


Apple CoreLocation参考似乎建议beginBackgroundTaskWithExpirationHandler在后台处理位置数据时使用:“如果iOS应用需要更多时间来处理位置数据,则可以使用UIApplication类的beginBackgroundTaskWithName:expirationHandler:方法来请求更多后台执行时间。” - developer.apple.com/library/ios/documentation/UserExperience/...
eliocs

4
不要忘记添加_locationManager.allowsBackgroundLocationUpdates = YES; 对于iOS9 +,否则应用将在180秒后进入睡眠状态
kolyancz

@kolyancz我刚刚在iPhone 6+的iOS 10.1.1上尝试过。大约需要17分钟才能进入睡眠并触发locationManagerDidPauseLocationUpdates。我已验证该行为10次!
蜂蜜

我真的不明白为什么你要说这没有意义,然后又说应该将其包装在backgroundTask中...
Honey

9

当需要启动位置服务并停止后台任务时,应延迟后台任务(1秒就足够了)。否则,定位服务将不会启动。定位服务也应保持开启状态几秒钟(例如3秒钟)。

有一个cocoapod APScheduledLocationManager,它允许每n秒以所需的位置精度获取后台位置更新。

let manager = APScheduledLocationManager(delegate: self)
manager.startUpdatingLocation(interval: 170, acceptableLocationAccuracy: 100)

该存储库还包含一个用Swift 3编写的示例应用程序。


1

尝试使用startMonitoringSignificantLocationChanges:API怎么样?它肯定会以较低的频率发射,并且精度相当不错。此外,与使用其他locationManagerAPI 相比,它具有更多的优势。

关于此API的更多信息已在此链接上讨论。


1
我已经尝试过这种方法。它违反了准确性要求-来自文档This interface delivers new events only when it detects changes to the device’s associated cell towers
pcoving 2013年

1

您需要通过在info.plist中添加以下键来在应用程序中添加位置更新模式。

<key>UIBackgroundModes</key>
<array>
    <string>location</string>
</array>

didUpdateToLocation方法将被调用(即使您的应用程序在后台运行)。您可以在此方法调用的基础上执行任何操作


位置更新模式已经在info.plist中。正如我提到的,我正在获取更新,直到180秒的后台超时为止。
pcoving

0

在iOS 8之后,它们是与Apple进行的CoreLocation框架相关的背景获取和更新有关的几处更改。

1)苹果公司引入了AlwaysAuthorization请求,以获取应用程序中的位置更新。

2)苹果公司引入了backgroundLocationupdates,用于从iOS中的后台获取位置。

要在后台获取位置,您需要在Xcode功能中启用位置更新。

//
//  ViewController.swift
//  DemoStackLocation
//
//  Created by iOS Test User on 02/01/18.
//  Copyright © 2018 iOS Test User. All rights reserved.
//

import UIKit
import CoreLocation

class ViewController: UIViewController {

    var locationManager: CLLocationManager = CLLocationManager()
    override func viewDidLoad() {
        super.viewDidLoad()
        startLocationManager()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    internal func startLocationManager(){
        locationManager.requestAlwaysAuthorization()
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.delegate = self
        locationManager.allowsBackgroundLocationUpdates = true
        locationManager.startUpdatingLocation()
    }

}

extension ViewController : CLLocationManagerDelegate {

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
        let location = locations[0] as CLLocation
        print(location.coordinate.latitude) // prints user's latitude
        print(location.coordinate.longitude) //will print user's longitude
        print(location.speed) //will print user's speed
    }

}

0

要在应用程序处于后台时使用标准位置服务,您需要首先在“目标”设置的“功能”选项卡中打开“后台模式”,然后选择“位置更新”。

或者,将其直接添加到Info.plist

<key>NSLocationAlwaysUsageDescription</key>
 <string>I want to get your location Information in background</string>
<key>UIBackgroundModes</key>
 <array><string>location</string> </array>

然后您需要设置CLLocationManager

目标C

//The Location Manager must have a strong reference to it.
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
//Request Always authorization (iOS8+)
if ([_locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) { [_locationManager requestAlwaysAuthorization];
}
//Allow location updates in the background (iOS9+)
if ([_locationManager respondsToSelector:@selector(allowsBackgroundLocationUpdates)]) { _locationManager.allowsBackgroundLocationUpdates = YES;
}
[_locationManager startUpdatingLocation];

迅速

self.locationManager.delegate = self
if #available (iOS 8.0,*) {
    self.locationManager.requestAlwaysAuthorization()
}
if #available (iOS 9.0,*) {
    self.locationManager.allowsBackgroundLocationUpdates = true
}
self.locationManager.startUpdatingLocation()

参考:https : //medium.com/@javedmultani16/location-service-in-the-background-ios-942c89cd99ba


-2
locationManager.allowsBackgroundLocationUpdates = true

4
您可以编辑答案并添加一些解释为什么/如何解决问题的解释吗?
barbsan
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.