Periodic iOS background location updates
If you have the
UIBackgroundModes
in your plist withlocation
key then you don't need to usebeginBackgroundTaskWithExpirationHandler
method. That's redundant. Also you're using it incorrectly (see here) but that's moot since your plist is set.With
UIBackgroundModes location
in the plist the app will continue to run in the background indefinitely only as long asCLLocationManger
is running. If you callstopUpdatingLocation
while in the background then the app will stop and won't start again.Maybe you could call
beginBackgroundTaskWithExpirationHandler
just before callingstopUpdatingLocation
and then after callingstartUpdatingLocation
you could call theendBackgroundTask
to keep it backgrounded while the GPS is stopped, but I've never tried that - it's just an idea.Another option (which I haven't tried) is to keep the location manager running while in the background but once you get an accurate location change the
desiredAccuracy
property to 1000m or higher to allow the GPS chip to get turned off (to save battery). Then 10 minutes later when you need another location update, change thedesiredAccuracy
back to 100m to turn on the GPS until you get an accurate location, repeat.When you call
startUpdatingLocation
on the location manager you must give it time to get a position. You should not immediately callstopUpdatingLocation
. We let it run for a maximum of 10 seconds or until we get a non-cached high accuracy location.You need to filter out cached locations and check the accuracy of the location you get to make sure it meets your minimum required accuracy (see here). The first update you get may be 10 mins or 10 days old. The first accuracy you get may be 3000m.
Consider using the significant location change APIs instead. Once you get the significant change notification, you could start
CLLocationManager
for a few seconds to get a high accuracy position. I'm not certain, I've never used the significant location change services.
I did write an app using Location services, app must send location every 10s. And it worked very well.
Just use the "allowDeferredLocationUpdatesUntilTraveled:timeout" method, following Apple's doc.
Steps are as follows:
Required: Register background mode for update Location.
1. Create LocationManger and startUpdatingLocation, with accuracy and filteredDistance as whatever you want:
-(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:
- (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:
-(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
- (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.
It seems that stopUpdatingLocation
is what triggers the background watchdog timer, so I replaced it in didUpdateLocation
with:
[self.locationManager setDesiredAccuracy:kCLLocationAccuracyThreeKilometers];
[self.locationManager setDistanceFilter:99999];
which appears to effectively power down the GPS. The selector for the background NSTimer
then becomes:
- (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.