Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ios deferred location updates fail to defer

I am looking into using deferred location updates for an iOS activity tracker, which allows location services in background. I've implemented the suggested code snippets (see below). In Xcode debugging, deferred locations attempt to start a few times until location data comes in at about 1 per second. After that, it claims to succeed in starting deferrals, and the callback for the finish trigger also succeeds after the specified time period expires. However during the time, the location handler still runs once per second. I've read that this is because the phone hasn't deemed itself ready to enter the background, and that testing in Xcode does this. Note, AppDelegate's "didEnterBackground" eventhandler got called immediately when turning off the screen, and resumed when reopening app.

I ran the same code with the phone disconnected as another test, near the window with GPS, screen off, or switching to entirely different apps, and it still never actually defers the updates. I can tell because the networked update still comes in once every 30 seconds, instead of the interval of 120 seconds which is desired in the code sample below.

What else is needed to actually get deferrals to work, since there is no error occurring in starting them and they do get their finish callback? Why do location updates continue at 1 per second even when the app goes to background?

Iphone 5s, IOS 7.1.1

// .h file (partial)
@interface MotionTracker : NSObject<CLLocationManagerDelegate, UIAccelerometerDelegate> 

@property (strong, nonatomic) CLLocationManager *locationManager;

@end

// .m file (parial)
- (id) init {
    if(self = [super init]){
        _locationManager = [[CLLocationManager alloc] init];
        _locationManager.delegate = self;
        _locationManager.distanceFilter = kCLDistanceFilterNone; 
        _locationManager.desiredAccuracy = kCLLocationAccuracyBest;

        // if set to YES (default), app stops logging location at some point and doesn't resume in any timely fashion, all data points lost in between
        _locationManager.pausesLocationUpdatesAutomatically = NO;

        _locationManager.activityType = CLActivityTypeFitness; 
    }
    return self;
}

// called early in program after login confirmed
- (void) startCollectingLocation {
    [_locationManager startUpdatingLocation];
}    

- (void)locationManager:(CLLocationManager *)manager
     didUpdateLocations:(NSArray *)locations {

    // logs to file when device is not in debug
    // always returns 1
    NSLog(@"Location update count: %d",[locations count]);

    // some code here to handle location updates
    // - collect key location day in NSDictionary
    // - every N seconds send Network call to server to save (have tried 30 seconds, 15 minutes, 30 minute network intervals). Have also tried turning off network calls completely.


    // deferred updates starter
    if (!self.deferringUpdates) {
        if([CLLocationManager deferredLocationUpdatesAvailable]){
            [_locationManager allowDeferredLocationUpdatesUntilTraveled:500 timeout:(NSTimeInterval)120]; // (have also tried large numbers, and "Infinite"
            self.deferringUpdates = YES;
            NSLog(@"Deferred updates start");
        } else {
            NSLog(@"Deferred updates not available");
        }
    }
}

- (void)locationManager:(CLLocationManager *)manager didFinishDeferredUpdatesWithError:(NSError *)error {
    if(!error){
        _deferringUpdates = NO;
        NSLog(@"Deferred updates: finished");
    } else {
        _deferringUpdates = NO;
        NSLog(@"Deferred updates: %@", [error localizedDescription]);
    }
}
like image 290
Miro Avatar asked Apr 28 '14 21:04

Miro


3 Answers

I have been struggling with the same issue, and I may have found an answer that solves this problem for many - at least it solves my problem and gets deferred updates working consistently for me. I followed all of the steps in this list and no matter what I did, location updates would not defer. It occurred to me that I might have other apps running that were not allowing the system to sleep, so I killed all other apps in the multitasking tray. I ran my sample app again and ... it worked! But the story doesn't end there. I tried again a little later, and even though there were no other apps running in the multitasking tray I couldn't get location services to defer. Then it occurred to me that I have an app on my phone called "Moves" which manages to keep itself alive even after you manually kill it. I'm not entirely sure how Moves comes magically back to life when you kill it, but it does (perhaps using bluetooth and the app preservation / restoration service). Even though it is alive and tracking your location it doesn't appear in the multitasking tray. I think that only apps that are manually launched appear in the tray - if the OS launches an app it doesn't appear in the tray. But I digress ... I was able to get deferred location services to work consistently in my app by disallowing Moves to use location services. When I did, Moves complained even though it wasn't in multitasking tray. It seems that if another app is using location services (and not deferring) your app won't defer either.

like image 35
Colin Phillips Avatar answered Oct 04 '22 17:10

Colin Phillips


Hi recently with the iOS 9 GM seed version out,I have seen location update(allowDeferredLocationUpdatesUntilTraveled:timeout:) not getting deferred.The same code used to work in iOS 8.4 and below versions.Its draining my device's battery by a huge margin.

Is there anything we need to explicitly set or mention for iOS 9?Didn't find anything from Apple documentation

Here is the code that I implemented.

-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {

if (!self.deferringUpdates) {

[self.locationManager allowDeferredLocationUpdatesUntilTraveled:CLLocationDistanceMax timeout:30]; self.deferringUpdates = YES; } }

-(void)locationManager:(CLLocationManager *)manager didFinishDeferredUpdatesWithError:(NSError *)error { // Stop deferring updates self.deferringUpdates = NO;

}

I also set the allowsBackgroundLocationUpdates property but even that didn't help. self.locationManager.allowsBackgroundLocationUpdates=YES;

In iOS 9 and later, regardless of deployment target, you must also set the allowsBackgroundLocationUpdatesproperty of the location manager object to YES in order to receive background location updates. By default, this property is NO, and it should remain this way until a time when your app actively requires background location updates.

https://developer.apple.com/library/prerelease/ios/documentation/Performance/Conceptual/EnergyGuide-iOS/LocationBestPractices.html

Please let me know what additional I need to make

Thanks

like image 39
IOSDevops Avatar answered Oct 04 '22 16:10

IOSDevops


If the device is connected to a debugger or on a charger, the device will remain powered (not sleep) and therefore will not enter deferred mode. Deferred mode is a power optimization allowing the device to sleep. If the device is not scheduled to sleep for other reasons, enabling deferred mode will not force it to sleep otherwise. Try your test by ensuring no other apps are using location services, and disconnecting it from a charger with the screen off. After running for some time, plug back in and check your logs, you should see that the device slept and deferred updates.

From Apple's allowDeferredLocationUpdatesUntilTraveled:timeout: documentation:

Deferred updates are delivered only when the system enters a low power state. Deferred updates do not occur during debugging because Xcode prevents your app from sleeping and thus prevents the system from entering that low power state.

It is also worth noting that deferred updates are only available when locationManager.desiredAccuracy is set to kCLLocationAccuracyBest OR kCLLocationAccuracyBest; locationManager.distanceFilter must also be set to kCLDistanceFilterNone.

From Apple's documentation:

...the location manager allows deferred updates only when GPS hardware is available on the device and when the desired accuracy is set to kCLLocationAccuracyBest or kCLLocationAccuracyBestForNavigation.

and

...the distanceFilter property of the location manager must be set to kCLDistanceFilterNone.

like image 144
user4138380 Avatar answered Oct 04 '22 16:10

user4138380