CLLocationManager doesn't start location updates when application did enter background - ios

In my app I need to perform location updates in background. For this purpose I registered my location tracking object as observer like this:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(start)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
and this is the method that starts updating location:
- (void)start
{
NSInteger downloadsCount = [[SGDataManager sharedInstance] countOfActiveDownloads];
NSInteger uploadsCount = [[SGDataManager sharedInstance] countOfActiveUploads];
if (downloadsCount + uploadsCount > 0)
{
[self.locationManager startUpdatingLocation];
}
}
However location updates never start. But if I change UIApplicationDidEnterBackgroundNotification to UIApplicationWillResignActiveNotification then the location updates work perfectly in background. So how do I make it work for did enter background notification?
I want to note that location updates background mode is enabled for my app

When your application goes in to background it has finite time to finish tasks. After that time the tasks will be suspended.
Try to put your UpdateLocation into a background task, like here:
http://hayageek.com/ios-long-running-background-task/
This example also uses location updates while going into background.
EDIT:
In my opinion it's a CoreLocation bug. It could be something like: startUpdatingLocation method finishes, the background task finishes, but there is still something happening in some other thread that CoreLocation spawned and the app suspends this thread because it goes into background. It's just a guess though.
Either way, here's a workaround: extend your app lifetime in background. Do not end the background task when startUpdatingLocation finishes, let it run for a couple of seconds.
__block UIBackgroundTaskIdentifier back = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask: back];
back = UIBackgroundTaskInvalid;
}];
[manager startUpdatingLocation];
This code will let your app run in background for a couple of minutes. You can create a timer that will suspend it faster if you like
NSTimer* killBackgroundTaskTimer = [NSTimer scheduledTimerWithTimeInterval: 10 target: self
selector: #selector(killBackgroundTask:) userInfo: nil repeats: NO];
-(void) callAfterSixtySecond:(NSTimer*) t
{
[application endBackgroundTask: back];
back = UIBackgroundTaskInvalid;
}

You need to enable project Background Modes capabilities.
Go to project properties >> Capabilities section.
Select Background Modes and click switch to turn ON.
In background mode check Location updates true.
This will enable location updates in background mode.

Related

Why does beginBackgroundTaskWithExpirationHandler allow an NSTimer to run in the background?

I know there are a lot of questions on similar topics, but I don't think any quite address this question (although I'm happy to be proved wrong!).
First some background; I have developed an app that monitors user location in the background and this is working fine, but battery usage is high. To try and improve this, my objective is to 'wake up' every x minutes, call startUpdatingLocation get a position, then call stopUpdatingLocation.
Consider the following sample that I put together for testing:
#interface ViewController ()
#property (strong, nonatomic) NSTimer *backgroundTimer;
#property (strong, nonatomic) CLLocationManager *locationManager;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Add observers to monitor background / foreground transitions
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
_locationManager = [[CLLocationManager alloc] init];
[_locationManager requestAlwaysAuthorization];
_locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
_locationManager.delegate = self;
_locationManager.distanceFilter=100;
[_locationManager startUpdatingLocation];
NSTimeInterval time = 60.0;
_backgroundTimer =[NSTimer scheduledTimerWithTimeInterval:time target:self selector:#selector(startLocationManager) userInfo:nil repeats:YES];
}
-(void)applicationEnterBackground{
NSLog(#"Entering background");
}
-(void)applicationEnterForeground{
NSLog(#"Entering foreground");
}
-(void)startLocationManager{
NSLog(#"Timer fired, starting location update");
[_locationManager startUpdatingLocation];
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
NSLog(#"New location received");
[_locationManager stopUpdatingLocation];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#end
As expected, when the application enters the background the CLLocationManager is not updating location, so the NSTimer is paused until the application enters the foreground.
Some similar SO questions have asked about keeping NSTimers running in the background, and the use of beginBackgroundTaskWithExpirationHandler so I added this to the viewDidLoad method just before the NSTimer is started.
UIBackgroundTaskIdentifier bgTask = 0;
UIApplication *app = [UIApplication sharedApplication];
NSLog(#"Beginning background task");
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"Background task expired");
[app endBackgroundTask:bgTask];
}];
NSLog(#"bgTask=%d", bgTask);
Over short tests (whether it works long term is yet to be proved) this seems to address the issue, but I don't understand why.
If you take a look at the log output below, you can see that calling beginBackgroundTaskWithExpirationHandler achieves the desired objective as the NSTimer continues to fire when the app enters the background:
2015-03-26 15:45:38.643 TestBackgroundLocation[1046:1557637] Beginning background task
2015-03-26 15:45:38.645 TestBackgroundLocation[1046:1557637] bgTask=1
2015-03-26 15:45:38.703 TestBackgroundLocation[1046:1557637] New location received
2015-03-26 15:45:46.459 TestBackgroundLocation[1046:1557637] Entering background
2015-03-26 15:46:38.682 TestBackgroundLocation[1046:1557637] Timer fired, starting location update
2015-03-26 15:46:38.697 TestBackgroundLocation[1046:1557637] New location received
2015-03-26 15:47:38.666 TestBackgroundLocation[1046:1557637] Timer fired, starting location update
2015-03-26 15:47:38.677 TestBackgroundLocation[1046:1557637] New location received
2015-03-26 15:48:38.690 TestBackgroundLocation[1046:1557637] Timer fired, starting location update
2015-03-26 15:48:38.705 TestBackgroundLocation[1046:1557637] New location received
2015-03-26 15:48:42.357 TestBackgroundLocation[1046:1557637] Background task expired
2015-03-26 15:49:38.733 TestBackgroundLocation[1046:1557637] Timer fired, starting location update
2015-03-26 15:49:38.748 TestBackgroundLocation[1046:1557637] New location received
2015-03-26 15:50:38.721 TestBackgroundLocation[1046:1557637] Timer fired, starting location update
2015-03-26 15:50:38.735 TestBackgroundLocation[1046:1557637] New location received
2015-03-26 15:50:44.361 TestBackgroundLocation[1046:1557637] Entering foreground
From this, you can see that the background task expires after approximately 3 minutes as expected, but the NSTimer continues to fire after that.
Is this behaviour expected? I will run some more tests to see if this solution works in the long term, but I can't help but feel I'm missing something with the use of the background task?
After lots of testing, it turns out that the NSTimer does not run indefinitely after the task has expired. At some point (can be hours, can be immediately) the timer ceases to fire.
In the end, the solution to my problem was reducing the GPS accuracy and increasing the distance filter at times where I did not need to monitor the position, as discussed here:
Periodic iOS background location updates
There is an option to use background task for long running tasks and then iOS doesn't suspednd execution.
According to Background execution document to use it one should add UIBackgroundModes key to Info.plist with value location (for location updates). There could be also another reason:
From section Implementing Long-Running Tasks:
Apps that implement these services must declare the services they support and use system frameworks to implement the relevant aspects of those services. Declaring the services lets the system know which services you use, but in some cases it is the system frameworks that actually prevent your application from being suspended.
Probably it is enough to initialize CLLocationManager to cause background task not to be suspended.

Stop location updates when app terminate

I'm developing an app which sends notifications when you are nearby of promoted places.
My problem is when I go to background and then I quit the app, I don't want the location services working when the app doesn't work (but I want them to work in background).
I saw only 3 apps which close the gps when the app is closed and I want to know how they did that, Facebook, Google Maps and Apple Maps, not Foursquare, not FieldTrips...
Thank you everybody.
you can add an observer for UIApplicationWillTerminateNotification where you start locationManager and than stop location updates
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationWillTerminate:)
name:UIApplicationWillTerminateNotification
object:nil];
method to perform when you receive the notification
- (void)applicationWillTerminate:(NSNotification *)notification {
//stop location updates
}
I found the correct answer to my question becouse of #GuyS second post:
Adding that in your AppDelegate.m applicationDidEnterBackground
- (void)applicationDidEnterBackground:(UIApplication *)application
{
UIApplication *app = [UIApplication sharedApplication];
if ([app respondsToSelector:#selector(beginBackgroundTaskWithExpirationHandler:)]) {
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;
}
});
}];
}
}
And declaring that variable:
UIBackgroundTaskIdentifier bgTask;
After that you only have to stop your location services in applicationWillTerminate...
Thank you for your replies.
The solution provided by #GuyS in this topic should work. I'm getting the UIApplicationWillTerminateNotification in case the app is in background and then I close it by swiping up the snapshot. Please check whether you work correctly with NSNotificationCenter (especially adding and removing notification). Plus, please check the object you subscribed on the notification is alive when the app is in background.
Another similar solution is to place the code that disables GPS in appropriate UIApplicationDelegate callback in your AppDelegate method.
- (void)applicationWillTerminate:(UIApplication *)application {
//stop location updates
}

How to run a process in background thread continuously for every n secs in ios

I am trying to run the process in background thread . I want that process to finish in 60 secs and start running again.No matter application is in foreground or background. I don't know how to implement and where to implement it.I'm using ios7.In that process I'm also taking location updates.
I read about the background tasks, but it wasn't giving proper idea of the process. Can someone provide me with good source or link?
There is no such api given by ios for background process unlike android which use service for that.You can use timer for continuos background process .Also there is dispatch_async ,selector in background for efficient background processing.
Hope this helps.
you can use something like this for background processing,but remember apple has put restriction of 10-15 min to complete the processing.
UIApplication* app = [UIApplication sharedApplication];
task = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:task];
task = 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.
NSLog(#"Started background task timeremaining = %f", [app backgroundTimeRemaining]);
if (connectedToNetwork) {
// do work son...
}
[app endBackgroundTask:task];
task = UIBackgroundTaskInvalid;
});
Also you can check the following :
**BOOL backgroundSupported = NO;
if ([device respondsToSelector:#selector(isMultitaskingSupported)])
backgroundSupported = device.multitaskingSupported;**

Call a localNotification if user enters/exits region while app is in background

I need to perform the simple task of calling a localNoification if the user enters or exits a region while the app is in background mode. Only a single set of coordinates will trigger the notification. For example:
Lat: 41.8500
Lon: 87.6500
Radius: 300
I know how to call the localNotification, and how to use the basic functionality of the locationManager, but cannot seem to track the location in the background. Any help would be great!
Have you read up on CLLocationManager's startMonitoringForRegion: method? I think this will do exactly what you want. The code to set it up would look something like this:
CLRegion * region = [[CLRegion alloc] initCircularRegionWithCenter: CLLocationCoordinate2DMake(41.8500, 87.6500) radius: 300 identifier: #"regionIDstring"];
CLLocationManager * manager = [[CLLocationManager alloc] init];
[manager setDelegate: myLocationManagerDelegate];
[manager startMonitoringForRegion: region];
After that, the device will monitor for entrances/exits to the specified region, even when your app is in the background. When a region is crossed, the delegate will receive a call locationManager:didEnterRegion: or locationManager:didExitRegion:. You can use this opportunity to post a UILocalNotification. If your app is not running at the time the region is crossed, it will be launched in the background, and you will need to look for the appropriate key in application: didFinishLaunchingWithOptions:. Use code like the following:
if ([launchOptions objectForKey: UIApplicationLaunchOptionsLocationKey] != nil) {
// create a location manager, and set its delegate here
// the delegate will then receive the appropriate callback
}
Be aware that the app will only have a short amount of time to execute while running in the background (a few seconds); if you need to perform a longer task, call the beginBackgroundTaskWithExpirationHandler: method that Nebs mentioned in his/her answer immediately after your app is notified of the region crossing. This will give you about 600 seconds to run in the background.
Have a look at the beginBackgroundTaskWithExpirationHandler: method of UIApplication. It allows you to request additional time to run a task when the app is in the background.
For more information I suggest you read the "Background Execution and Multitasking" section of the iOS app programming guide. It explains in detail what happens when the app goes in background and what you're allowed to do.
Specifically it shows this sample code of running a long task when the app goes in the background:
[ This code is taken from the Apple guide linked above ]
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
// Clean up any unfinished task business by marking where you.
// stopped or ending the task outright.
[application 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, preferably in chunks.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}

iOS full-time background-service based on location tracking

I'm currently writing an application which depends on location tracking and sending data about the position to the server. The problem, however, is that it has to run 24/7 and currently I'm experiencing random crashes which occur every 2-3 days. What I have done to make the application run constantly in the background is I put a NSTimer in a beginBackgroundTaskWithExpirationHandler method right iside the applicationDidEnterBackground method. The timer executes each minute and stops/starts the location service.
Here is a sample crash log
The code basically looks like this:
UIApplication *app = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTaskId = 0;
bgTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
NSTimer *t = [NSTimer scheduledTimerWithTimeInterval: 1 * 60.0 target: self selector: #selector(onTick) userInfo: nil repeats: YES];
[t fire];
if (bgTaskId != UIBackgroundTaskInvalid){
[app endBackgroundTask: bgTaskId];
bgTaskId = UIBackgroundTaskInvalid;
}
}];
I am using GCDAsyncSockets for connection purposes, each call having a timeout of approximately 30 seconds.
I'm really out of ideas, what might be the reason the crashes occur?
Your timer is probably firing off AFTER the task is invalidated (after [UIApplication sharedApplication].backgroundTimeRemaining gets to 0.
The thing is that you can't make the application run constantly in the background. If you want to execute code every once in a while, your only option is going to be using the background location API, setting that your app is using the location background mode in its plist.
You would be getting the CLLocationManagerDelegate callbacks, and you have some time to do some work when those methods are called.
See the Apple documentation regarding background modes: http://developer.apple.com/library/ios/#DOCUMENTATION/iPhone/Conceptual/iPhoneOSProgrammingGuide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html
And the location-awarness manual: http://developer.apple.com/library/ios/#DOCUMENTATION/UserExperience/Conceptual/LocationAwarenessPG/Introduction/Introduction.html#//apple_ref/doc/uid/TP40009497

Resources