我正在寻找一种方法来获得背景位置更新每n分钟在我的iOS应用程序。我使用的是iOS 4.3,解决方案应该适用于未越狱的iphone。

我尝试/考虑了以下选项:

CLLocationManager startUpdatingLocation/startMonitoringSignificantLocationChanges: This works in the background as expected, based on the configured properties, but it seems not possible to force it to update the location every n minutes NSTimer: Does work when the app is running in the foreground but doesn't seem to be designed for background tasks Local notifications: Local notifications can be scheduled every n minutes, but it's not possible to execute some code to get the current location (without the user having to launch the app via the notification). This approach also doesn't seem to be a clean approach as this is not what notifications should be used for. UIApplication:beginBackgroundTaskWithExpirationHandler: As far as I understand, this should be used to finish some work in the background (also limited in time) when an app is moved to the background rather than implementing "long-running" background processes.

如何实现这些定期的后台位置更新?


当前回答

在iOS 8/9/10上,每5分钟更新一次后台位置,执行以下操作:

进入项目->能力->背景模式->选择位置更新 转到项目->信息->添加一个键NSLocationAlwaysUsageDescription空值(或可选的任何文本) 当你的应用程序在后台工作,并发送坐标到web服务或做任何事情,每5分钟实现如下代码。

我没有使用任何后台任务或计时器。我在我的iOS 8.1设备上测试了这段代码,我的应用程序在后台运行了几个小时。设备被锁定,代码一直正常运行。

@interface LocationManager () <CLLocationManagerDelegate>
@property (strong, nonatomic) CLLocationManager *locationManager;
@property (strong, nonatomic) NSDate *lastTimestamp;

@end

@implementation LocationManager

+ (instancetype)sharedInstance
{
    static id sharedInstance = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
        LocationManager *instance = sharedInstance;
        instance.locationManager = [CLLocationManager new];
        instance.locationManager.delegate = instance;
        instance.locationManager.desiredAccuracy = kCLLocationAccuracyBest; // you can use kCLLocationAccuracyHundredMeters to get better battery life
        instance.locationManager.pausesLocationUpdatesAutomatically = NO; // this is important
    });

    return sharedInstance;
}

- (void)startUpdatingLocation
{
    CLAuthorizationStatus status = [CLLocationManager authorizationStatus];

    if (status == kCLAuthorizationStatusDenied)
    {
        NSLog(@"Location services are disabled in settings.");
    }
    else
    {
        // for iOS 8
        if ([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)])
        {
            [self.locationManager requestAlwaysAuthorization];
        }
        // for iOS 9
        if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)])
        {
            [self.locationManager setAllowsBackgroundLocationUpdates:YES];
        }

        [self.locationManager startUpdatingLocation];
    }
}

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    CLLocation *mostRecentLocation = locations.lastObject;
    NSLog(@"Current location: %@ %@", @(mostRecentLocation.coordinate.latitude), @(mostRecentLocation.coordinate.longitude));

    NSDate *now = [NSDate date];
    NSTimeInterval interval = self.lastTimestamp ? [now timeIntervalSinceDate:self.lastTimestamp] : 0;

    if (!self.lastTimestamp || interval >= 5 * 60)
    {
        self.lastTimestamp = now;
        NSLog(@"Sending current location to web service.");
    }
}

@end

其他回答

if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) {
    [self.locationManager setAllowsBackgroundLocationUpdates:YES];
}

自iOS 9以来,这是后台位置跟踪所需要的。

附件是基于以下的Swift解决方案:

在info.plist中定义位置更新的App寄存器

保持locationManager一直运行

在BestForNavigation和threekm之间切换kCLLocationAccuracy(5秒获得位置),以避免电池耗尽

这个例子在前台每1分钟更新一次位置,在后台每15分钟更新一次位置。

这个例子适用于运行在iOS 7设备上的Xcode 6 Beta 6。

在App委托中(mapView是一个指向mapView控制器的可选选项)

func applicationDidBecomeActive(application: UIApplication!) {
    if appLaunched! == false { // Reference to mapView used to limit one location update per timer cycle
        appLaunched = true
        var appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
        var window = appDelegate.window
        var tabBar = window?.rootViewController as UITabBarController
        var navCon = tabBar.viewControllers[0] as UINavigationController
        mapView = navCon.topViewController as? MapViewController
    }
    self.startInitialPeriodWithTimeInterval(60.0)
}

func applicationDidEnterBackground(application: UIApplication!) {
    self.startInitialPeriodWithTimeInterval(15 * 60.0)
}

func startInitialPeriodWithTimeInterval(timeInterval: NSTimeInterval) {
    timer?.invalidate() // reset timer
    locationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation
    timer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: Selector("getFirstLocationUpdate:"), userInfo: timeInterval, repeats: false)
}

func getFirstLocationUpdate(sender: NSTimer) {
    let timeInterval = sender.userInfo as Double
    timer?.invalidate()
    mapView?.canReportLocation = true
    timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval, target: self, selector: Selector("waitForTimer:"), userInfo: timeInterval, repeats: true)
}

func waitForTimer(sender: NSTimer) {
    let time = sender.userInfo as Double
    locationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation
    finalTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: Selector("getLocationUpdate"), userInfo: nil, repeats: false)
}

func getLocationUpdate() {
    finalTimer?.invalidate()
    mapView?.canReportLocation = true
}

在mapView中(locationManager指向AppDelegate中的对象)

override func viewDidLoad() {
    super.viewDidLoad()
    var appDelegate = UIApplication.sharedApplication().delegate! as AppDelegate
    locationManager = appDelegate.locationManager!
    locationManager.delegate = self
    canReportLocation = true
}

  func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
        if canReportLocation! {
            canReportLocation = false
            locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
        } else {
            //println("Ignore location update")
        }
    }

I did this in an application I'm developing. The timers don't work when the app is in the background but the app is constantly receiving the location updates. I read somewhere in the documentation (i can't seem to find it now, i'll post an update when i do) that a method can be called only on an active run loop when the app is in the background. The app delegate has an active run loop even in the bg so you dont need to create your own to make this work. [Im not sure if this is the correct explanation but thats how I understood from what i read]

首先,在应用的info.plist中添加关键UIBackgroundModes的location对象。现在,你需要做的是启动位置更新在你的应用程序的任何地方:

    CLLocationManager locationManager = [[CLLocationManager alloc] init];
    locationManager.delegate = self;//or whatever class you have for managing location
    [locationManager startUpdatingLocation];

Next, write a method to handle the location updates, say -(void)didUpdateToLocation:(CLLocation*)location, in the app delegate. Then implement the method locationManager:didUpdateLocation:fromLocation of CLLocationManagerDelegate in the class in which you started the location manager (since we set the location manager delegate to 'self'). Inside this method you need to check if the time interval after which you have to handle the location updates has elapsed. You can do this by saving the current time every time. If that time has elapsed, call the method UpdateLocation from your app delegate:

NSDate *newLocationTimestamp = newLocation.timestamp;
NSDate *lastLocationUpdateTiemstamp;

int locationUpdateInterval = 300;//5 mins

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if (userDefaults) {

        lastLocationUpdateTiemstamp = [userDefaults objectForKey:kLastLocationUpdateTimestamp];

        if (!([newLocationTimestamp timeIntervalSinceDate:lastLocationUpdateTiemstamp] < locationUpdateInterval)) {
            //NSLog(@"New Location: %@", newLocation);
            [(AppDelegate*)[UIApplication sharedApplication].delegate didUpdateToLocation:newLocation];
            [userDefaults setObject:newLocationTimestamp forKey:kLastLocationUpdateTimestamp];
        }
    }
}

这将每5分钟调用你的方法,即使你的应用程序是在后台。 Imp:这个实现耗尽电池,如果你的位置数据的准确性不是关键,你应该使用[locationManager startMonitoringSignificantLocationChanges]

在将此添加到应用程序之前,请阅读位置感知编程指南

我使用了xs2bush获取间隔的方法(使用timeIntervalSinceDate),并对其进行了一点扩展。我想确保我得到了我所需要的精度,同时我也没有因为保持gps收音机开得太久而耗尽电池。

我保持位置运行持续与以下设置:

locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
locationManager.distanceFilter = 5;

这是一个相对低的电池消耗。当我准备好获得下一个周期性位置读数时,我首先检查位置是否在我期望的精度范围内,如果是,然后使用该位置。如果不是,那么我用这个来提高准确性:

locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
locationManager.distanceFilter = 0;

get my location and then once I have the location I turn the accuracy back down again to minimize the drain on the battery. I have written a full working sample of this and also I have written the source for the server side code to collect the location data, store it to a database and allow users to view gps data in real time or retrieve and view previously stored routes. I have clients for iOS, android, windows phone and java me. All clients are natively written and they all work properly in the background. The project is MIT licensed.

iOS项目的目标是iOS 6,使用iOS 7的基础SDK。你可以在这里得到代码。

如果你看到任何问题,请在github上提交一个问题。谢谢。

在iOS 8/9/10上,每5分钟更新一次后台位置,执行以下操作:

进入项目->能力->背景模式->选择位置更新 转到项目->信息->添加一个键NSLocationAlwaysUsageDescription空值(或可选的任何文本) 当你的应用程序在后台工作,并发送坐标到web服务或做任何事情,每5分钟实现如下代码。

我没有使用任何后台任务或计时器。我在我的iOS 8.1设备上测试了这段代码,我的应用程序在后台运行了几个小时。设备被锁定,代码一直正常运行。

@interface LocationManager () <CLLocationManagerDelegate>
@property (strong, nonatomic) CLLocationManager *locationManager;
@property (strong, nonatomic) NSDate *lastTimestamp;

@end

@implementation LocationManager

+ (instancetype)sharedInstance
{
    static id sharedInstance = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
        LocationManager *instance = sharedInstance;
        instance.locationManager = [CLLocationManager new];
        instance.locationManager.delegate = instance;
        instance.locationManager.desiredAccuracy = kCLLocationAccuracyBest; // you can use kCLLocationAccuracyHundredMeters to get better battery life
        instance.locationManager.pausesLocationUpdatesAutomatically = NO; // this is important
    });

    return sharedInstance;
}

- (void)startUpdatingLocation
{
    CLAuthorizationStatus status = [CLLocationManager authorizationStatus];

    if (status == kCLAuthorizationStatusDenied)
    {
        NSLog(@"Location services are disabled in settings.");
    }
    else
    {
        // for iOS 8
        if ([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)])
        {
            [self.locationManager requestAlwaysAuthorization];
        }
        // for iOS 9
        if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)])
        {
            [self.locationManager setAllowsBackgroundLocationUpdates:YES];
        }

        [self.locationManager startUpdatingLocation];
    }
}

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    CLLocation *mostRecentLocation = locations.lastObject;
    NSLog(@"Current location: %@ %@", @(mostRecentLocation.coordinate.latitude), @(mostRecentLocation.coordinate.longitude));

    NSDate *now = [NSDate date];
    NSTimeInterval interval = self.lastTimestamp ? [now timeIntervalSinceDate:self.lastTimestamp] : 0;

    if (!self.lastTimestamp || interval >= 5 * 60)
    {
        self.lastTimestamp = now;
        NSLog(@"Sending current location to web service.");
    }
}

@end