我如何在我的iOS应用程序中每n分钟获得一个背景位置更新?

时间:2022-04-26 01:25:57

I'm looking for a way to get a background location update every n minutes in my iOS application. I'm using iOS 4.3 and the solution should work for non-jailbroken iPhones.

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

I tried / considered following options:

我尝试了以下选项:

  • 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
  • CLLocationManager startUpdatingLocation/ startmonitoringantlocationchanges:这是根据配置的属性在后台运行的,但是似乎不可能强迫它每n分钟更新一次位置。
  • NSTimer: Does work when the app is running in the foreground but doesn't seem to be designed for background tasks
  • NSTimer:当应用程序在前台运行时工作,但似乎没有为后台任务设计。
  • 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.
  • 本地通知:本地通知可以在每n分钟内调度,但是不可能执行一些代码来获取当前位置(没有用户需要通过通知启动应用程序)。这种方法似乎也不是一种干净的方法,因为这不是应该使用的通知。
  • 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.
  • UIApplication:beginBackgroundTaskWithExpirationHandler:据我所知,当一个应用程序被移动到后台而不是实现“长时间运行”的后台进程时,这应该被用来完成后台的一些工作(也限制在时间上)。

How can I implement these regular background location updates?

如何实现这些常规的背景位置更新?

15 个解决方案

#1


109  

I found a solution to implement this with the help of the Apple Developer Forums:

在苹果开发者论坛的帮助下,我找到了一个解决方案:

  • Specify location background mode
  • 指定位置的背景模式
  • Create an NSTimer in the background with UIApplication:beginBackgroundTaskWithExpirationHandler:
  • 在后台使用UIApplication创建一个NSTimer:beginBackgroundTaskWithExpirationHandler:
  • When n is smaller than UIApplication:backgroundTimeRemaining it will work just fine. When n is larger, the location manager should be enabled (and disabled) again before there is no time remaining to avoid the background task being killed.
  • 当n小于UIApplication:backgroundTimeRemaining它会工作得很好。当n更大时,位置管理器应该在没有剩余时间的情况下再次启用(和禁用),以避免被杀死的后台任务。

This works because location is one of the three allowed types of background execution.

这是因为位置是三种允许的后台执行类型之一。

Note: I lost some time by testing this in the simulator where it doesn't work. However, it works fine on my phone.

注意:我在模拟器中测试这个失败了一些时间。但是,它在我的手机上运行良好。

#2


48  

On iOS 8/9/10 to make background location update every 5 minutes do the following:

在iOS 8/9/10中,每5分钟更新一次后台位置:

  1. Go to Project -> Capabilities -> Background Modes -> select Location updates

    去项目->功能->后台模式->选择位置更新。

  2. Go to Project -> Info -> add a key NSLocationAlwaysUsageDescription with empty value (or optionally any text)

    Go to Project -> Info ->添加一个关键的NSLocationAlwaysUsageDescription,空值(或任意文本)

  3. To make location working when your app is in the background and send coordinates to web service or do anything with them every 5 minutes implement it like in the code below.

    当你的应用程序在后台运行时,要让位置发挥作用,并将坐标发送到web服务,或者每5分钟用它们做任何事情,在下面的代码中实现它。

I'm not using any background tasks or timers. I've tested this code with my device with iOS 8.1 which was lying on my desk for few hours with my app running in the background. Device was locked and the code was running properly all the time.

我没有使用任何后台任务或计时器。我用我的设备测试了这段代码,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

#3


34  

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]

我在我正在开发的应用程序中做到了这一点。当应用程序在后台时,计时器不会工作,但应用程序不断地接收位置更新。我在文档的某个地方读到(我现在似乎找不到它了,我将发布一个更新),当应用程序在后台时,一个方法只能在一个活动的运行循环中调用。即使在bg中,应用程序委托也有一个活跃的运行循环,因此您不需要创建自己来完成这项工作。我不确定这是否是正确的解释,但这就是我从我读到的内容中所理解的。

First of all, add the location object for the key UIBackgroundModes in your app's info.plist. Now, what you need to do is start the location updates anywhere in your app:

首先,在app的info.plist中添加关键的uibackgroundmode的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:

接下来,写一种方法来处理位置更新,比如(void)didUpdateToLocation:(CLLocation*)位置,在app委托中。然后实现方法locationManager:didUpdateLocation:在您启动位置管理器的类中的CLLocationManagerDelegate的位置(因为我们设置了位置管理委托给“self”)。在这个方法中,您需要检查您必须处理位置更新之后的时间间隔已经过去了。您可以通过每次节省当前时间来实现这一点。如果这段时间已经过去,请从应用程序委托调用方法UpdateLocation:

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];
        }
    }
}

This will call your method every 5 mins even when your app is in background. Imp: This implementation drains the battery, if your location data's accuracy is not critical you should use [locationManager startMonitoringSignificantLocationChanges]

这将会每5分钟调用一次你的方法,即使你的应用在后台。Imp:这个实现会耗尽电池,如果你的位置数据的准确性不重要,你应该使用[locationManager startmonitoringantlocationchanges]

Before adding this to your app, please read the Location Awareness Programming Guide

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

#4


22  

Now that iOS6 is out the best way to have a forever running location services is...

现在iOS6已经过时了,最好的方式是拥有一个永远运行的位置服务。

- (void)applicationWillResignActive:(UIApplication *)application
{
/*
 Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
 Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
 */

NSLog(@"to background");

app.isInBackground = TRUE;

UIApplication *app = [UIApplication sharedApplication];

// Request permission to run in the background. Provide an
// expiration handler in case the task runs long.
NSAssert(bgTask == UIBackgroundTaskInvalid, nil);

bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
    // Synchronize the cleanup call on the main thread in case
    // the task actually finishes at around the same time.
    dispatch_async(dispatch_get_main_queue(), ^{

        if (bgTask != UIBackgroundTaskInvalid)
        {
            [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.

    locationManager.distanceFilter = 100;
    locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
    [locationManager startMonitoringSignificantLocationChanges];
    [locationManager startUpdatingLocation];

    NSLog(@"App staus: applicationDidEnterBackground");
    // Synchronize the cleanup call on the main thread in case
    // the expiration handler is fired at the same time.
    dispatch_async(dispatch_get_main_queue(), ^{
        if (bgTask != UIBackgroundTaskInvalid)
        {
            [app endBackgroundTask:bgTask];
            bgTask = UIBackgroundTaskInvalid;
        }
    });
});

NSLog(@"backgroundTimeRemaining: %.0f", [[UIApplication sharedApplication] backgroundTimeRemaining]);

}

Just tested it like that:

就像这样测试:

I started the app, go background and move in the car by some minutes. Then I go home for 1 hour and start moving again (without opening again the app). Locations started again. Then stopped for two hours and started again. Everything ok again...

我启动了应用程序,在后台运行,然后在车里移动几分钟。然后我回家1个小时,然后重新开始移动(没有打开应用程序)。位置重新开始了。然后停了两个小时,又开始了。一切都好了…

DO NOT FORGET USING the new location services in iOS6

不要忘记在iOS6中使用新的位置服务?

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{   
    CLLocation *loc = [locations lastObject];

    // Lat/Lon
    float latitudeMe = loc.coordinate.latitude;
    float longitudeMe = loc.coordinate.longitude;
}

#5


13  

To someone else having nightmare figure out this one. I have a simple solution.

对其他人来说,这是噩梦。我有一个简单的解。

  1. look this example from raywenderlich.com-> have sample code, this works perfectly, but unfortunately no timer during background location. this will run indefinitely.
  2. 看看这个来自raywenderlich.com的例子——>有样例代码,这很好,但不幸的是,在后台位置没有计时器。这将运行下去。
  3. Add timer by using :

    添加定时器:

    -(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];
    
    }
    
  4. Just don't forget to add "App registers for location updates" in info.plist.

    只是别忘了在info.plist中添加“应用程序注册表”。

#6


5  

Unfortunately, all of your assumptions seem correct, and I don't think there's a way to do this. In order to save battery life, the iPhone's location services are based on movement. If the phone sits in one spot, it's invisible to location services.

不幸的是,你所有的假设似乎都是正确的,我认为没有办法做到这一点。为了节省电池寿命,iPhone的定位服务基于移动。如果手机处于一个位置,定位服务是看不见的。

The CLLocationManager will only call locationManager:didUpdateToLocation:fromLocation: when the phone receives a location update, which only happens if one of the three location services (cell tower, gps, wifi) perceives a change.

CLLocationManager只会调用locationManager:didUpdateToLocation:fromLocation:当手机接收到位置更新时,只有当三个位置服务(cell tower, gps, wifi)感知到一个变化时才会发生。

A few other things that might help inform further solutions:

其他一些可能有助于进一步解决的问题:

  • Starting & Stopping the services causes the didUpdateToLocation delegate method to be called, but the newLocation might have an old timestamp.

    启动和停止服务会导致didUpdateToLocation委托方法被调用,但是newLocation可能有一个旧的时间戳。

  • Region Monitoring might help

    区域监测可以帮助

  • When running in the background, be aware that it may be difficult to get "full" LocationServices support approved by Apple. From what I've seen, they've specifically designed startMonitoringSignificantLocationChanges as a low power alternative for apps that need background location support, and strongly encourage developers to use this unless the app absolutely needs it.

    当在后台运行时,请注意,可能很难获得苹果批准的“完整”的本地化服务支持。从我所看到的来看,他们特别设计了startmonitoringantlocationchanges作为应用程序的低功力替代品,它需要后台支持,并且强烈鼓励开发者使用它,除非应用程序绝对需要它。

Good Luck!

好运!

UPDATE: These thoughts may be out of date by now. Looks as though people are having success with @wjans answer, above.

更新:这些想法可能已经过时了。看起来人们在@wjans的回答中取得了成功。

#7


5  

I did write an app using Location services, app must send location every 10s. And it worked very well.

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

Just use the "allowDeferredLocationUpdatesUntilTraveled:timeout" method, following Apple's doc.

只要使用“allowdeferredlocationupdatesuntiltravel:timeout”方法,就可以使用苹果的doc。

What I did are:

我所做的是:

Required: Register background mode for update Location.

需要:为更新位置注册后台模式。

1. Create LocationManger and startUpdatingLocation, with accuracy and filteredDistance as whatever you want:

1。创建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. To keep app run forever using allowDeferredLocationUpdatesUntilTraveled:timeout method in background, you must restart updatingLocation with new parameter when app moves to background, like this:

2。为了使应用程序永远运行,使用allowdeferredlocationupdatesuntil: timeout方法在后台,当应用程序移动到后台时,必须重新启动updatingLocation和新的参数,比如:

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

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

3. App gets updatedLocations as normal with locationManager:didUpdateLocations: callback:

3所示。应用程序通过locationManager获得updatedLocations:didUpdateLocations: callback:

-(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. But you should handle the data in then locationManager:didFinishDeferredUpdatesWithError: callback for your purpose

4所示。但是,您应该在随后的locationManager中处理数据:didFinishDeferredUpdatesWithError:回调您的目的。

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

     _deferringUpdates = NO;

     //do something 
}

5. NOTE: I think we should reset parameters of LocationManager each time app switches between background/forground mode.

5。注意:我认为我们应该重新设置LocationManager的参数,每次在后台/forground模式之间切换。

#8


5  

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

This is needed for background location tracking since iOS 9.

这是自ios9以来的背景位置跟踪所需要的。

#9


5  

Here is what I use:

以下是我所使用的:

import Foundation
import CoreLocation
import UIKit

class BackgroundLocationManager :NSObject, CLLocationManagerDelegate {

    static let instance = BackgroundLocationManager()
    static let BACKGROUND_TIMER = 150.0 // restart location manager every 150 seconds
    static let UPDATE_SERVER_INTERVAL = 60 * 60 // 1 hour - once every 1 hour send location to server

    let locationManager = CLLocationManager()
    var timer:NSTimer?
    var currentBgTaskId : UIBackgroundTaskIdentifier?
    var lastLocationDate : NSDate = NSDate()

    private override init(){
        super.init()
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
        locationManager.activityType = .Other;
        locationManager.distanceFilter = kCLDistanceFilterNone;
        if #available(iOS 9, *){
            locationManager.allowsBackgroundLocationUpdates = true
        }

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.applicationEnterBackground), name: UIApplicationDidEnterBackgroundNotification, object: nil)
    }

    func applicationEnterBackground(){
        FileLogger.log("applicationEnterBackground")
        start()
    }

    func start(){
        if(CLLocationManager.authorizationStatus() == CLAuthorizationStatus.AuthorizedAlways){
            if #available(iOS 9, *){
                locationManager.requestLocation()
            } else {
                locationManager.startUpdatingLocation()
            }
        } else {
                locationManager.requestAlwaysAuthorization()
        }
    }
    func restart (){
        timer?.invalidate()
        timer = nil
        start()
    }

    func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
        switch status {
        case CLAuthorizationStatus.Restricted:
            //log("Restricted Access to location")
        case CLAuthorizationStatus.Denied:
            //log("User denied access to location")
        case CLAuthorizationStatus.NotDetermined:
            //log("Status not determined")
        default:
            //log("startUpdatintLocation")
            if #available(iOS 9, *){
                locationManager.requestLocation()
            } else {
                locationManager.startUpdatingLocation()
            }
        }
    }
    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

        if(timer==nil){
            // The locations array is sorted in chronologically ascending order, so the
            // last element is the most recent
            guard let location = locations.last else {return}

            beginNewBackgroundTask()
            locationManager.stopUpdatingLocation()
            let now = NSDate()
            if(isItTime(now)){
                //TODO: Every n minutes do whatever you want with the new location. Like for example sendLocationToServer(location, now:now)
            }
        }
    }

    func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
        CrashReporter.recordError(error)

        beginNewBackgroundTask()
        locationManager.stopUpdatingLocation()
    }

    func isItTime(now:NSDate) -> Bool {
        let timePast = now.timeIntervalSinceDate(lastLocationDate)
        let intervalExceeded = Int(timePast) > BackgroundLocationManager.UPDATE_SERVER_INTERVAL
        return intervalExceeded;
    }

    func sendLocationToServer(location:CLLocation, now:NSDate){
        //TODO
    }

    func beginNewBackgroundTask(){
        var previousTaskId = currentBgTaskId;
        currentBgTaskId = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
            FileLogger.log("task expired: ")
        })
        if let taskId = previousTaskId{
            UIApplication.sharedApplication().endBackgroundTask(taskId)
            previousTaskId = UIBackgroundTaskInvalid
        }

        timer = NSTimer.scheduledTimerWithTimeInterval(BackgroundLocationManager.BACKGROUND_TIMER, target: self, selector: #selector(self.restart),userInfo: nil, repeats: false)
    }
}

I start the tracking in AppDelegate like that:

我在AppDelegate中开始跟踪:

BackgroundLocationManager.instance.start()

#10


4  

I used xs2bush's method of getting an interval (using timeIntervalSinceDate) and expanded on it a little bit. I wanted to make sure that I was getting the required accuracy that I needed and also that I was not running down the battery by keeping the gps radio on more than necessary.

我使用了xs2bush的方法来获得一个间隔(使用timeIntervalSinceDate),并将其扩展了一点。我想要确保我得到了我所需要的准确性,而且我也没有通过把gps收音机保持在必要的位置来降低电池电量。

I keep location running continuously with the following settings:

我保持位置连续运行,设置如下:

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

this is a relatively low drain on the battery. When I'm ready to get my next periodic location reading, I first check to see if the location is within my desired accuracy, if it is, I then use the location. If it's not, then I increase the accuracy with this:

这是一个相对较低的消耗电池。当我准备好进行下一次周期性的位置读取时,我首先检查位置是否在我想要的精度范围内,如果是的话,我就使用这个位置。如果不是,那么我就增加了准确性:

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.

找到我的位置,然后一旦我有了位置,我就会再次调低精度,以减少电池的消耗。我已经编写了一个完整的工作示例,还编写了服务器端代码的源代码来收集位置数据,将其存储到数据库中,允许用户实时查看gps数据,或者检索和查看以前存储的路由。我有iOS、android、windows phone和java me的客户。所有的客户机都是本地编写的,它们都在后台正常工作。该项目由麻省理工学院授权。

The iOS project is targeted for iOS 6 using a base SDK of iOS 7. You can get the code here.

iOS 6使用iOS 7的基础SDK来实现iOS 6的目标。你可以在这里得到代码。

Please file an issue on github if you see any problems with it. Thanks.

如果发现有任何问题,请向github上提出问题。谢谢。

#11


2  

It seems that stopUpdatingLocation is what triggers the background watchdog timer, so I replaced it in didUpdateLocation with:

似乎stopUpdatingLocation是触发后台监视计时器的原因,所以我将它替换为didUpdateLocation:

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

which appears to effectively power down the GPS. The selector for the background NSTimer then becomes:

这似乎有效地降低了GPS的能量。背景NSTimer的选择器随后变为:

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

All I'm doing is periodically toggling the accuracy to get a high-accuracy coordinate every few minutes and because the locationManager hasn't been stopped, backgroundTimeRemaining stays at its maximum value. This reduced battery consumption from ~10% per hour (with constant kCLLocationAccuracyBest in the background) to ~2% per hour on my device

我所做的就是每隔几分钟就切换精度以获得高精度的坐标,因为locationManager没有被停止,backgroundTimeRemaining保持最大值。这减少了电池的消耗,从每小时10% ~10%(在背景中恒定的kCLLocationAccuracyBest),到我的设备每小时2% ~2%。

#12


2  

There is a cocoapod APScheduledLocationManager that allows to get background location updates every n seconds with desired location accuracy.

有一个cocoapod APScheduledLocationManager,它允许每n秒获得一个背景位置更新,并获得所需的位置精度。

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

The repository also contains an example app written in Swift 3.

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

#13


1  

Working Code(Entire Stepwise Code)

工作代码(整个分段代码)

Step 1

步骤1

  • Go to project -> Capabilities -> Background Modes -> select Location updates.
  • 去项目->功能->后台模式->选择位置更新。
  • Go to Project -> Info -> add a key NSLocationAlwaysUsageDescription with an optional string.
  • Go to Project -> Info ->添加一个关键的NSLocationAlwaysUsageDescription和一个可选字符串。

Step 2

步骤2

Add this code to AppDelegate.m

将此代码添加到AppDelegate.m。

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

Step 3 Add This Code in to applicationDidEnterBackground method in AppDelegate.m

步骤3将此代码添加到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
}

#14


0  

In iOS 9 and watchOS 2.0 there's a new method on CLLocationManager that lets you request the current location: CLLocationManager:requestLocation(). This completes immediately and then returns the location to the CLLocationManager delegate.

在ios9和watchOS 2.0中,CLLocationManager上有一个新方法,它允许您请求当前位置:CLLocationManager:requestLocation()。这将立即完成,然后将位置返回给CLLocationManager委托。

You can use an NSTimer to request a location every minute with this method now and don't have to work with startUpdatingLocation and stopUpdatingLocation methods.

您可以使用NSTimer以这种方法每分钟请求一个位置,而不必使用startUpdatingLocation和stopUpdatingLocation方法。

However if you want to capture locations based on a change of X meters from the last location, just set the distanceFilter property of CLLocationManger and to X call startUpdatingLocation().

但是,如果您想要根据距离最后一个位置的X米的变化来捕获位置,只需设置CLLocationManger的distanceFilter属性和X调用startUpdatingLocation()。

#15


-1  

Attached is a Swift solution based in:

附件是一个快速的解决方案:

Define App registers for location updates in the info.plist

在info.plist中为位置更新定义应用程序寄存器。

Keep the locationManager running all the time

让locationManager一直运行。

Switch kCLLocationAccuracy between BestForNavigation (for 5 secs to get the location) and ThreeKilometers for the rest of the wait period to avoid battery drainage

开关kCLLocationAccuracy between BestForNavigation (for 5 secs to get the location)和threekm for the rest of the wait period to avoid battery。

This example updates location every 1 min in Foreground and every 15 mins in Background.

这个例子更新了前台每1分钟的位置,以及每15分钟的背景。

The example works fine with Xcode 6 Beta 6, running in a iOS 7 device.

该示例适用于Xcode 6 Beta 6,运行于iOS 7设备中。

In the App Delegate (mapView is an Optional pointing to the mapView Controller)

在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
}

In the mapView (locationManager points to the object in the AppDelegate)

在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")
        }
    }

#1


109  

I found a solution to implement this with the help of the Apple Developer Forums:

在苹果开发者论坛的帮助下,我找到了一个解决方案:

  • Specify location background mode
  • 指定位置的背景模式
  • Create an NSTimer in the background with UIApplication:beginBackgroundTaskWithExpirationHandler:
  • 在后台使用UIApplication创建一个NSTimer:beginBackgroundTaskWithExpirationHandler:
  • When n is smaller than UIApplication:backgroundTimeRemaining it will work just fine. When n is larger, the location manager should be enabled (and disabled) again before there is no time remaining to avoid the background task being killed.
  • 当n小于UIApplication:backgroundTimeRemaining它会工作得很好。当n更大时,位置管理器应该在没有剩余时间的情况下再次启用(和禁用),以避免被杀死的后台任务。

This works because location is one of the three allowed types of background execution.

这是因为位置是三种允许的后台执行类型之一。

Note: I lost some time by testing this in the simulator where it doesn't work. However, it works fine on my phone.

注意:我在模拟器中测试这个失败了一些时间。但是,它在我的手机上运行良好。

#2


48  

On iOS 8/9/10 to make background location update every 5 minutes do the following:

在iOS 8/9/10中,每5分钟更新一次后台位置:

  1. Go to Project -> Capabilities -> Background Modes -> select Location updates

    去项目->功能->后台模式->选择位置更新。

  2. Go to Project -> Info -> add a key NSLocationAlwaysUsageDescription with empty value (or optionally any text)

    Go to Project -> Info ->添加一个关键的NSLocationAlwaysUsageDescription,空值(或任意文本)

  3. To make location working when your app is in the background and send coordinates to web service or do anything with them every 5 minutes implement it like in the code below.

    当你的应用程序在后台运行时,要让位置发挥作用,并将坐标发送到web服务,或者每5分钟用它们做任何事情,在下面的代码中实现它。

I'm not using any background tasks or timers. I've tested this code with my device with iOS 8.1 which was lying on my desk for few hours with my app running in the background. Device was locked and the code was running properly all the time.

我没有使用任何后台任务或计时器。我用我的设备测试了这段代码,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

#3


34  

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]

我在我正在开发的应用程序中做到了这一点。当应用程序在后台时,计时器不会工作,但应用程序不断地接收位置更新。我在文档的某个地方读到(我现在似乎找不到它了,我将发布一个更新),当应用程序在后台时,一个方法只能在一个活动的运行循环中调用。即使在bg中,应用程序委托也有一个活跃的运行循环,因此您不需要创建自己来完成这项工作。我不确定这是否是正确的解释,但这就是我从我读到的内容中所理解的。

First of all, add the location object for the key UIBackgroundModes in your app's info.plist. Now, what you need to do is start the location updates anywhere in your app:

首先,在app的info.plist中添加关键的uibackgroundmode的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:

接下来,写一种方法来处理位置更新,比如(void)didUpdateToLocation:(CLLocation*)位置,在app委托中。然后实现方法locationManager:didUpdateLocation:在您启动位置管理器的类中的CLLocationManagerDelegate的位置(因为我们设置了位置管理委托给“self”)。在这个方法中,您需要检查您必须处理位置更新之后的时间间隔已经过去了。您可以通过每次节省当前时间来实现这一点。如果这段时间已经过去,请从应用程序委托调用方法UpdateLocation:

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];
        }
    }
}

This will call your method every 5 mins even when your app is in background. Imp: This implementation drains the battery, if your location data's accuracy is not critical you should use [locationManager startMonitoringSignificantLocationChanges]

这将会每5分钟调用一次你的方法,即使你的应用在后台。Imp:这个实现会耗尽电池,如果你的位置数据的准确性不重要,你应该使用[locationManager startmonitoringantlocationchanges]

Before adding this to your app, please read the Location Awareness Programming Guide

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

#4


22  

Now that iOS6 is out the best way to have a forever running location services is...

现在iOS6已经过时了,最好的方式是拥有一个永远运行的位置服务。

- (void)applicationWillResignActive:(UIApplication *)application
{
/*
 Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
 Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
 */

NSLog(@"to background");

app.isInBackground = TRUE;

UIApplication *app = [UIApplication sharedApplication];

// Request permission to run in the background. Provide an
// expiration handler in case the task runs long.
NSAssert(bgTask == UIBackgroundTaskInvalid, nil);

bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
    // Synchronize the cleanup call on the main thread in case
    // the task actually finishes at around the same time.
    dispatch_async(dispatch_get_main_queue(), ^{

        if (bgTask != UIBackgroundTaskInvalid)
        {
            [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.

    locationManager.distanceFilter = 100;
    locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
    [locationManager startMonitoringSignificantLocationChanges];
    [locationManager startUpdatingLocation];

    NSLog(@"App staus: applicationDidEnterBackground");
    // Synchronize the cleanup call on the main thread in case
    // the expiration handler is fired at the same time.
    dispatch_async(dispatch_get_main_queue(), ^{
        if (bgTask != UIBackgroundTaskInvalid)
        {
            [app endBackgroundTask:bgTask];
            bgTask = UIBackgroundTaskInvalid;
        }
    });
});

NSLog(@"backgroundTimeRemaining: %.0f", [[UIApplication sharedApplication] backgroundTimeRemaining]);

}

Just tested it like that:

就像这样测试:

I started the app, go background and move in the car by some minutes. Then I go home for 1 hour and start moving again (without opening again the app). Locations started again. Then stopped for two hours and started again. Everything ok again...

我启动了应用程序,在后台运行,然后在车里移动几分钟。然后我回家1个小时,然后重新开始移动(没有打开应用程序)。位置重新开始了。然后停了两个小时,又开始了。一切都好了…

DO NOT FORGET USING the new location services in iOS6

不要忘记在iOS6中使用新的位置服务?

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{   
    CLLocation *loc = [locations lastObject];

    // Lat/Lon
    float latitudeMe = loc.coordinate.latitude;
    float longitudeMe = loc.coordinate.longitude;
}

#5


13  

To someone else having nightmare figure out this one. I have a simple solution.

对其他人来说,这是噩梦。我有一个简单的解。

  1. look this example from raywenderlich.com-> have sample code, this works perfectly, but unfortunately no timer during background location. this will run indefinitely.
  2. 看看这个来自raywenderlich.com的例子——>有样例代码,这很好,但不幸的是,在后台位置没有计时器。这将运行下去。
  3. Add timer by using :

    添加定时器:

    -(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];
    
    }
    
  4. Just don't forget to add "App registers for location updates" in info.plist.

    只是别忘了在info.plist中添加“应用程序注册表”。

#6


5  

Unfortunately, all of your assumptions seem correct, and I don't think there's a way to do this. In order to save battery life, the iPhone's location services are based on movement. If the phone sits in one spot, it's invisible to location services.

不幸的是,你所有的假设似乎都是正确的,我认为没有办法做到这一点。为了节省电池寿命,iPhone的定位服务基于移动。如果手机处于一个位置,定位服务是看不见的。

The CLLocationManager will only call locationManager:didUpdateToLocation:fromLocation: when the phone receives a location update, which only happens if one of the three location services (cell tower, gps, wifi) perceives a change.

CLLocationManager只会调用locationManager:didUpdateToLocation:fromLocation:当手机接收到位置更新时,只有当三个位置服务(cell tower, gps, wifi)感知到一个变化时才会发生。

A few other things that might help inform further solutions:

其他一些可能有助于进一步解决的问题:

  • Starting & Stopping the services causes the didUpdateToLocation delegate method to be called, but the newLocation might have an old timestamp.

    启动和停止服务会导致didUpdateToLocation委托方法被调用,但是newLocation可能有一个旧的时间戳。

  • Region Monitoring might help

    区域监测可以帮助

  • When running in the background, be aware that it may be difficult to get "full" LocationServices support approved by Apple. From what I've seen, they've specifically designed startMonitoringSignificantLocationChanges as a low power alternative for apps that need background location support, and strongly encourage developers to use this unless the app absolutely needs it.

    当在后台运行时,请注意,可能很难获得苹果批准的“完整”的本地化服务支持。从我所看到的来看,他们特别设计了startmonitoringantlocationchanges作为应用程序的低功力替代品,它需要后台支持,并且强烈鼓励开发者使用它,除非应用程序绝对需要它。

Good Luck!

好运!

UPDATE: These thoughts may be out of date by now. Looks as though people are having success with @wjans answer, above.

更新:这些想法可能已经过时了。看起来人们在@wjans的回答中取得了成功。

#7


5  

I did write an app using Location services, app must send location every 10s. And it worked very well.

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

Just use the "allowDeferredLocationUpdatesUntilTraveled:timeout" method, following Apple's doc.

只要使用“allowdeferredlocationupdatesuntiltravel:timeout”方法,就可以使用苹果的doc。

What I did are:

我所做的是:

Required: Register background mode for update Location.

需要:为更新位置注册后台模式。

1. Create LocationManger and startUpdatingLocation, with accuracy and filteredDistance as whatever you want:

1。创建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. To keep app run forever using allowDeferredLocationUpdatesUntilTraveled:timeout method in background, you must restart updatingLocation with new parameter when app moves to background, like this:

2。为了使应用程序永远运行,使用allowdeferredlocationupdatesuntil: timeout方法在后台,当应用程序移动到后台时,必须重新启动updatingLocation和新的参数,比如:

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

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

3. App gets updatedLocations as normal with locationManager:didUpdateLocations: callback:

3所示。应用程序通过locationManager获得updatedLocations:didUpdateLocations: callback:

-(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. But you should handle the data in then locationManager:didFinishDeferredUpdatesWithError: callback for your purpose

4所示。但是,您应该在随后的locationManager中处理数据:didFinishDeferredUpdatesWithError:回调您的目的。

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

     _deferringUpdates = NO;

     //do something 
}

5. NOTE: I think we should reset parameters of LocationManager each time app switches between background/forground mode.

5。注意:我认为我们应该重新设置LocationManager的参数,每次在后台/forground模式之间切换。

#8


5  

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

This is needed for background location tracking since iOS 9.

这是自ios9以来的背景位置跟踪所需要的。

#9


5  

Here is what I use:

以下是我所使用的:

import Foundation
import CoreLocation
import UIKit

class BackgroundLocationManager :NSObject, CLLocationManagerDelegate {

    static let instance = BackgroundLocationManager()
    static let BACKGROUND_TIMER = 150.0 // restart location manager every 150 seconds
    static let UPDATE_SERVER_INTERVAL = 60 * 60 // 1 hour - once every 1 hour send location to server

    let locationManager = CLLocationManager()
    var timer:NSTimer?
    var currentBgTaskId : UIBackgroundTaskIdentifier?
    var lastLocationDate : NSDate = NSDate()

    private override init(){
        super.init()
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
        locationManager.activityType = .Other;
        locationManager.distanceFilter = kCLDistanceFilterNone;
        if #available(iOS 9, *){
            locationManager.allowsBackgroundLocationUpdates = true
        }

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.applicationEnterBackground), name: UIApplicationDidEnterBackgroundNotification, object: nil)
    }

    func applicationEnterBackground(){
        FileLogger.log("applicationEnterBackground")
        start()
    }

    func start(){
        if(CLLocationManager.authorizationStatus() == CLAuthorizationStatus.AuthorizedAlways){
            if #available(iOS 9, *){
                locationManager.requestLocation()
            } else {
                locationManager.startUpdatingLocation()
            }
        } else {
                locationManager.requestAlwaysAuthorization()
        }
    }
    func restart (){
        timer?.invalidate()
        timer = nil
        start()
    }

    func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
        switch status {
        case CLAuthorizationStatus.Restricted:
            //log("Restricted Access to location")
        case CLAuthorizationStatus.Denied:
            //log("User denied access to location")
        case CLAuthorizationStatus.NotDetermined:
            //log("Status not determined")
        default:
            //log("startUpdatintLocation")
            if #available(iOS 9, *){
                locationManager.requestLocation()
            } else {
                locationManager.startUpdatingLocation()
            }
        }
    }
    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

        if(timer==nil){
            // The locations array is sorted in chronologically ascending order, so the
            // last element is the most recent
            guard let location = locations.last else {return}

            beginNewBackgroundTask()
            locationManager.stopUpdatingLocation()
            let now = NSDate()
            if(isItTime(now)){
                //TODO: Every n minutes do whatever you want with the new location. Like for example sendLocationToServer(location, now:now)
            }
        }
    }

    func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
        CrashReporter.recordError(error)

        beginNewBackgroundTask()
        locationManager.stopUpdatingLocation()
    }

    func isItTime(now:NSDate) -> Bool {
        let timePast = now.timeIntervalSinceDate(lastLocationDate)
        let intervalExceeded = Int(timePast) > BackgroundLocationManager.UPDATE_SERVER_INTERVAL
        return intervalExceeded;
    }

    func sendLocationToServer(location:CLLocation, now:NSDate){
        //TODO
    }

    func beginNewBackgroundTask(){
        var previousTaskId = currentBgTaskId;
        currentBgTaskId = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
            FileLogger.log("task expired: ")
        })
        if let taskId = previousTaskId{
            UIApplication.sharedApplication().endBackgroundTask(taskId)
            previousTaskId = UIBackgroundTaskInvalid
        }

        timer = NSTimer.scheduledTimerWithTimeInterval(BackgroundLocationManager.BACKGROUND_TIMER, target: self, selector: #selector(self.restart),userInfo: nil, repeats: false)
    }
}

I start the tracking in AppDelegate like that:

我在AppDelegate中开始跟踪:

BackgroundLocationManager.instance.start()

#10


4  

I used xs2bush's method of getting an interval (using timeIntervalSinceDate) and expanded on it a little bit. I wanted to make sure that I was getting the required accuracy that I needed and also that I was not running down the battery by keeping the gps radio on more than necessary.

我使用了xs2bush的方法来获得一个间隔(使用timeIntervalSinceDate),并将其扩展了一点。我想要确保我得到了我所需要的准确性,而且我也没有通过把gps收音机保持在必要的位置来降低电池电量。

I keep location running continuously with the following settings:

我保持位置连续运行,设置如下:

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

this is a relatively low drain on the battery. When I'm ready to get my next periodic location reading, I first check to see if the location is within my desired accuracy, if it is, I then use the location. If it's not, then I increase the accuracy with this:

这是一个相对较低的消耗电池。当我准备好进行下一次周期性的位置读取时,我首先检查位置是否在我想要的精度范围内,如果是的话,我就使用这个位置。如果不是,那么我就增加了准确性:

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.

找到我的位置,然后一旦我有了位置,我就会再次调低精度,以减少电池的消耗。我已经编写了一个完整的工作示例,还编写了服务器端代码的源代码来收集位置数据,将其存储到数据库中,允许用户实时查看gps数据,或者检索和查看以前存储的路由。我有iOS、android、windows phone和java me的客户。所有的客户机都是本地编写的,它们都在后台正常工作。该项目由麻省理工学院授权。

The iOS project is targeted for iOS 6 using a base SDK of iOS 7. You can get the code here.

iOS 6使用iOS 7的基础SDK来实现iOS 6的目标。你可以在这里得到代码。

Please file an issue on github if you see any problems with it. Thanks.

如果发现有任何问题,请向github上提出问题。谢谢。

#11


2  

It seems that stopUpdatingLocation is what triggers the background watchdog timer, so I replaced it in didUpdateLocation with:

似乎stopUpdatingLocation是触发后台监视计时器的原因,所以我将它替换为didUpdateLocation:

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

which appears to effectively power down the GPS. The selector for the background NSTimer then becomes:

这似乎有效地降低了GPS的能量。背景NSTimer的选择器随后变为:

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

All I'm doing is periodically toggling the accuracy to get a high-accuracy coordinate every few minutes and because the locationManager hasn't been stopped, backgroundTimeRemaining stays at its maximum value. This reduced battery consumption from ~10% per hour (with constant kCLLocationAccuracyBest in the background) to ~2% per hour on my device

我所做的就是每隔几分钟就切换精度以获得高精度的坐标,因为locationManager没有被停止,backgroundTimeRemaining保持最大值。这减少了电池的消耗,从每小时10% ~10%(在背景中恒定的kCLLocationAccuracyBest),到我的设备每小时2% ~2%。

#12


2  

There is a cocoapod APScheduledLocationManager that allows to get background location updates every n seconds with desired location accuracy.

有一个cocoapod APScheduledLocationManager,它允许每n秒获得一个背景位置更新,并获得所需的位置精度。

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

The repository also contains an example app written in Swift 3.

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

#13


1  

Working Code(Entire Stepwise Code)

工作代码(整个分段代码)

Step 1

步骤1

  • Go to project -> Capabilities -> Background Modes -> select Location updates.
  • 去项目->功能->后台模式->选择位置更新。
  • Go to Project -> Info -> add a key NSLocationAlwaysUsageDescription with an optional string.
  • Go to Project -> Info ->添加一个关键的NSLocationAlwaysUsageDescription和一个可选字符串。

Step 2

步骤2

Add this code to AppDelegate.m

将此代码添加到AppDelegate.m。

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

Step 3 Add This Code in to applicationDidEnterBackground method in AppDelegate.m

步骤3将此代码添加到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
}

#14


0  

In iOS 9 and watchOS 2.0 there's a new method on CLLocationManager that lets you request the current location: CLLocationManager:requestLocation(). This completes immediately and then returns the location to the CLLocationManager delegate.

在ios9和watchOS 2.0中,CLLocationManager上有一个新方法,它允许您请求当前位置:CLLocationManager:requestLocation()。这将立即完成,然后将位置返回给CLLocationManager委托。

You can use an NSTimer to request a location every minute with this method now and don't have to work with startUpdatingLocation and stopUpdatingLocation methods.

您可以使用NSTimer以这种方法每分钟请求一个位置,而不必使用startUpdatingLocation和stopUpdatingLocation方法。

However if you want to capture locations based on a change of X meters from the last location, just set the distanceFilter property of CLLocationManger and to X call startUpdatingLocation().

但是,如果您想要根据距离最后一个位置的X米的变化来捕获位置,只需设置CLLocationManger的distanceFilter属性和X调用startUpdatingLocation()。

#15


-1  

Attached is a Swift solution based in:

附件是一个快速的解决方案:

Define App registers for location updates in the info.plist

在info.plist中为位置更新定义应用程序寄存器。

Keep the locationManager running all the time

让locationManager一直运行。

Switch kCLLocationAccuracy between BestForNavigation (for 5 secs to get the location) and ThreeKilometers for the rest of the wait period to avoid battery drainage

开关kCLLocationAccuracy between BestForNavigation (for 5 secs to get the location)和threekm for the rest of the wait period to avoid battery。

This example updates location every 1 min in Foreground and every 15 mins in Background.

这个例子更新了前台每1分钟的位置,以及每15分钟的背景。

The example works fine with Xcode 6 Beta 6, running in a iOS 7 device.

该示例适用于Xcode 6 Beta 6,运行于iOS 7设备中。

In the App Delegate (mapView is an Optional pointing to the mapView Controller)

在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
}

In the mapView (locationManager points to the object in the AppDelegate)

在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")
        }
    }