0
votes

I'm building an iOS app that works in the background, and posts the location of the user to the server every 3 minutes (as that is the maximum background execution time on iOS 7). However, there is one problem, the background service terminate at randomly times. So sometimes it can run for 2 hours in the background, sometimes for 7 hours, then 3 hours and randomly so forth.

The code below produces the error. I have been able to detect when it will terminate, and that is when [UIApplication sharedApplication].backgroundTimeRemaining is below 10 seconds. Can anyone point in some direction, or explain why it is terminating? My guess is that [self.locationManager startUpdatingLocation] is not 100 % safe? (and the method that makes [UIApplication sharedApplication].backgroundTimeRemaining unlimited?)

Here is the "normal" log I receive on my server every 3 minutes;

iPhone 5: 21:06:45 backgroundTimeRemaining before startUpdatingLocation: 11.014648

iPhone 5: 21:06:45 backgroundTimeRemaining after startUpdatingLocation: 999.000000

iPhone 5: 21:06:48 backgroundTimeRemaining before stopUpdatingLocation: 999.000000

iPhone 5: 21:06:48 backgroundTimeRemaining after stopUpdatingLocation: 999.000000

The code:

#import "BetaLocationController.h"
#import "AppDelegate.h"
#import <Parse/Parse.h>
#import "CommunicationController.h"

@interface BetaLocationController () <CLLocationManagerDelegate>

@property UIBackgroundTaskIdentifier bgTask;
@property (strong, nonatomic) CLLocationManager *locationManager;
@property BOOL locationStarted;
@property (strong, nonatomic) CLLocation *myLocation;
@end

@implementation BetaLocationController

- (id)init
{
    self = [super init];
    if (self) {
        self.locationManager = [[CLLocationManager alloc] init];
        self.locationManager.delegate = self;
        self.locationManager.pausesLocationUpdatesAutomatically = NO;
        self.locationManager.activityType = CLActivityTypeOther;
        self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
        self.locationManager.distanceFilter = kCLDistanceFilterNone;

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
    }
    return self;
}

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    CLLocation* location = [locations lastObject];
    self.myLocation = location;
    //    NSLog(@"Location: %f, %f", location.coordinate.longitude, location.coordinate.latitude);

}

- (void)didEnterBackground:(NSNotification *) notification
{
    [self runBackgroundTask:10];
}


-(void)runBackgroundTask: (int) time{
    //check if application is in background mode
    if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {

        //create UIBackgroundTaskIdentifier and create tackground task, which starts after time
        __block UIBackgroundTaskIdentifier bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
            //            [self runBackgroundTask:5];
            [[UIApplication sharedApplication] endBackgroundTask:bgTask];
            bgTask = UIBackgroundTaskInvalid;
        }];

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            NSTimer *t = [NSTimer scheduledTimerWithTimeInterval:time target:self selector:@selector(startTrackingBg) userInfo:nil repeats:NO];
            [[NSRunLoop currentRunLoop] addTimer:t forMode:NSDefaultRunLoopMode];
            [[NSRunLoop currentRunLoop] run];
        });
    }
}

-(void)startTrackingBg{
    [[UIApplication sharedApplication] cancelAllLocalNotifications];
    UILocalNotification *localNotification = [[UILocalNotification alloc] init];
    NSDate *now = [NSDate date];
    localNotification.fireDate = now;

    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"HHmm"];
    int timeofDay = [[formatter stringFromDate:[NSDate date]] intValue];
    localNotification.applicationIconBadgeNumber = timeofDay;
    [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];

    //set default time
    int time = 170;
    //if locationManager is ON
    if (self.locationStarted  == TRUE ) {
        //Here I can detect the error
        if([UIApplication sharedApplication].backgroundTimeRemaining < 10){
            [CommunicationController logToParse:[NSString stringWithFormat:@"%@ : Detected error, trying to restart locationservice", [UIDevice currentDevice].name]];
        }

        //log
        [CommunicationController logToParse:[NSString stringWithFormat:@"%@: %@ bgTime before stopUpdatingLocation: %f", [UIDevice currentDevice].name, [NSDate date], [self getBackgroundTime]]];

        [self.locationManager stopUpdatingLocation];
        self.locationStarted = FALSE;

        //log
        [CommunicationController logToParse:[NSString stringWithFormat:@"%@: %@ bgTime after stopUpdatingLocation: %f", [UIDevice currentDevice].name, [NSDate date], [self getBackgroundTime]]];

    }else{
        //start updating location

        //log
        [CommunicationController logToParse:[NSString stringWithFormat:@"%@: %@ bgTime before startUpdatingLocation: %f", [UIDevice currentDevice].name, [NSDate date], [self getBackgroundTime]]];

        [self.locationManager startUpdatingLocation];
        self.locationStarted = TRUE;

        //Time how long the application will update your location
        time = 3;

        //log
        [CommunicationController logToParse:[NSString stringWithFormat:@"%@: %@ bgTime after startUpdatingLocation: %f", [UIDevice currentDevice].name, [NSDate date], [self getBackgroundTime]]];

    }

    [self runBackgroundTask:time];
}

-(float)getBackgroundTime{
    float bgTime = [[UIApplication sharedApplication] backgroundTimeRemaining];

    //If bgtime is over 180, it returns unlimited. In iOS 7, only 3 minutes backgroundtime is available
    return bgTime > 180 ? 999 : bgTime;
}

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
    [CommunicationController logToParse:[NSString stringWithFormat:@"%@: %@ LocationManagerDidFail %@: %f", [UIDevice currentDevice].name, [NSDate date], error, [[UIApplication sharedApplication] backgroundTimeRemaining]]];

    NSLog(@"Error in locationManager. It Failed: %@", error);
}


-(void)dealloc {
    [CommunicationController logToParse:[NSString stringWithFormat:@"%@: %@ got dealloc'ed", [UIDevice currentDevice].name, [NSDate date]]];

    NSLog(@"Dealloc in location manager is called");
}


@end
4

4 Answers

0
votes

You can in general not be sure your app will be kept alive. A user might want to terminate it actively or the system might be in need of the resources your app is using, and therefore terminate it. I guess you are experiencing this problem due to resource constraints.

You might get around it by using the the Background fetch introduced in iOS7. The OS will at regular interval allow your app to start up again.

Try reading this excellent document describing the feature: http://devstreaming.apple.com/videos/wwdc/2013/204xex2xvpdncz9kdb17lmfooh/204/204.pdf

You can, however, not prevent the user from force your app to close.

0
votes

You cannot start location services in the background in iOS7. That is why it is inconsistent. You need to start it while the app is in the foreground and leave it on.

0
votes

Follow these steps to get the locations in the background mode:

  1. Add a "UIBackgroundModes" to a string "locations" key in the info.plist file in your app enter image description here

  2. Edit/Add your code:

if(self.location==nil){
     self.locationManager = [[CLLocationManager alloc] init];
}
[self.locationManager setDelegate:self];

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

self.locationManager.distanceFilter = 10.0f;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.pausesLocationUpdatesAutomatically=NO;
self.locationManager.activityType=CLActivityTypeAutomotiveNavigation;

if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8) {
[self.locationManager requestAlwaysAuthorization];
}
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9) {
     self.locationManager.allowsBackgroundLocationUpdates = YES;
}

[self.locationManager startUpdatingLocation];
0
votes

App will move on suspended mode that's why you'll not get the location after that. Please check below URL. it'll help you to get location more then 18 min. even app is in suspended mode.

nice post related to background service