here's yet another question on having GPS run in the background. Apparently, there are specific conditions where my app can run (seemingly indefinitely) in the background, and sometimes it will terminate after 3 minutes. My iPad is currently running on 9.3.2.
TL;DR: Did the necessary code and project configurations, but does not always run in background for longer than 3 minutes. Why?
My post will be lengthy. I have tried to keep it concise.
My app will need to send the GPS locations at every interval: 60 seconds if the user is "Logged in," and 900 seconds (15 minutes) if the user is "Logged out." I need these requirements; The program requirements are not decided by me. This app is not published to the app store either.
I understand that I need to add this in my plist:
<key>NSLocationAlwaysUsageDescription</key>
<string>Location information from this device is required for tracking purposes.</string>
Under the project capabilities, I have Background Modes -> Location updates selected, and also in the plist:
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>
In my AppDelegate, I also have these 2 (located inside application:didFinishLaunchingWithOptions:):
if ([locationManager respondsToSelector:#selector(requestAlwaysAuthorization)])
{
[locationManager requestAlwaysAuthorization];
NSLog(#"===>locationManager responds to requestAlwaysAuthorization<===");
}
else
{
NSLog(#"===>locationManager not responding to requestAlwaysAuthorization! :(<===");
}
if([locationManager respondsToSelector:#selector(setAllowsBackgroundLocationUpdates:)])
{
[locationManager setAllowsBackgroundLocationUpdates:YES];
NSLog(#"===>locationManager responds to setAllowsBackgroundLocationUpdates<===");
}
else
{
NSLog(#"===>locationManager not responding to setAllowsBackgroundLocationUpdates! :(<===");
}
In my applicationDidEnterBackground, I have the beginBackgroundTaskWithExpirationHandler function as follows:
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"ending background task. Background time remaining: %f", [[UIApplication sharedApplication] backgroundTimeRemaining]);
//Do I need to uncomment the 2 lines below?
//[[UIApplication sharedApplication] endBackgroundTask:bgTask];
//bgTask = UIBackgroundTaskInvalid;
}];
So now, my biggest question is: What have I still not done yet/done wrong, that doesn't allow for background execution?
My app has a csv log file (locally) that records what GPS coordinates are being sent over to a tracking web service. In my logs, I also record events like "App to Background" and "App to Foreground" so when I retrieve the logs, I will know if it terminated after 3 minutes. Also, I log down the [[UIApplication sharedApplication] backgroundTimeRemaining] remaining time.
I have CLLocationManager *locationManager;. When my app goes into the background, I make [locationManager setDesiredAccuracy:kCLLocationAccuracyThreeKilometers]; and then [locationManager startUpdatingLocation]; just to keep the app running in the background. A timer changes [locationManager setDesiredAccuracy:kCLLocationAccuracyBest]; when at the interval as stated above, and change it back to [locationManager setDesiredAccuracy:kCLLocationAccuracyThreeKilometers] after that. All this is when the app is in the background, [locationManager stopUpdatingLocation]; only if the app is in the foreground. Do I actually need to keep the locationManager running so the app remains active in the background?
The strange part is as follows: I realised that when I send my app to the background by: (a.) Home button (b.) power button (c.) closing the iPad case, (a.) will always have the app running in the background, while (b.) and (c.) may allow the app running for more than 3 minutes ??!? Is there really a difference? Because I know the delegate functions applicationWillResignActive and applicationDidEnterBackground will be called nonetheless. Whether or not it runs more than 3 minutes seems non-deterministic.
Did I put beginBackgroundTaskWithExpirationHandler in the correct place?
Did I put [locationManager setAllowsBackgroundLocationUpdates:YES]; in the correct place (currently in the AppDelegaate)?
Running the app with xcode debugger will always work perfect, but running it without (i.e. actual use conditions) might not let the app run. With the debugger running, I NSLog the background time remaining. It might show something like 179.348015 seconds but after 3 minutes the app will continue to run. Is this issue faced by any others?
Help is very much appreciated. o/
Alright, so I have asked this 1 year ago and have not received any responses...
When I first asked this question, my intention was to turn off the GPS once the app has obtained the coordinates. So in pseudo code, the logic should go something like this:
applicationStart()
{
startTimer();
}
startTimer()
{
timer repeats: TRUE;
timer cycle: 60 seconds;
function to call: getGPS();
}
getGPS()
{
GPS start;
retrieve coordinates;
send coordinates to server;
GPS stop;
}
By doing this, I intended to save battery power, as GPS is a battery draining feature. However, by doing this, there is no guarantee that the GPS would constantly run in the background. On top of that, as I have mentioned, the results of running such logic is non-deterministic; sometimes it would run, sometimes it would not.
In the end, my current code goes as such (in pseudo code):
applicationStart()
{
GPS start;
GPS accuracy 3 kilometers;
startTimer();
}
startTimer()
{
timer repeats: TRUE;
timer cycle: 60 seconds;
function to call: getGPS();
}
getGPS()
{
GPS accuracy best;
retrieve coordinates;
send coordinates to server;
GPS accuracy 3 kilometers;
}
Notice that I do not actually turn off the GPS; in lieu of turning it off, I make it run constantly, but changed to [locationManager setDesiredAccuracy:kCLLocationAccuracyThreeKilometers] when I do not need it.
Of course, I am still open to a better solution. At the point of asking this question, I was running iOS9. My above solution works on iOS10 right now, and with iOS11 round the corner, I'm not sure what other changes Apple will be bringing to the API relating to GPS.
Battery consumption? Well, so far so good; My users are not complaining.
Related
How can i stop and start the GPS tracking when an application running in background. i Can able to stop tracking in the application, but didn't got any way to start the application. I looking for answers for followings.
1.whether i can use push notification silently to start GPS tracking.
2.I tried with local notification, but it require user interaction to start the process. Is there any best ways to do this.
My problem: Initially i can able start tracking the user location and can stop that after some time in background. I wanted to start the tracking next day. Im looking for a way to start tracking in background.
Actually my application is basically the location tracking application. if the launches the application, the app starts tracking until the time 8:00 pm (stops automatically). Again started tracking next day 8:00 am.
If you started location updates while your app is running in background, these updates will not last longer than whatever time iOS granted for your background task (currently 180 sec).
Location updates must be started while the app is active, doing this ensures the updates keep coming even after your app goes to background, assuming you configured your app background modes properly - see Capabilities/Background Modes/Location updates in Xcode.
Even though, if your app is terminated, the delivery of new location events stops altogether.
If you want to temporary suppress standard location updates, please use
allowDeferredLocationUpdatesUntilTraveled:timeout:
When updates are deferred, GPS receiver is still powered, but no CPU activity happens, as the receiver will not trigger events. I did not test power consumption in this mode myself, but people mention ~2% battery drain per hour.
Have you tried this
-(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];
}
Set intervalBackground Update to 1 day
So, i want my app to do background execution for only a fixed amount of time, this is in case the user does not manually stop the app, and the app therefore in theory could run in background forever(is that even possible?).
I'm using the code below (just a test app) to test how long exactly a background task can run before ending. I read somewhere that 10 minutes is the longest we can do background execution, and there is no way to get beyond that(?). However, my code will only execute in the background for 3 minutes.
So to sum up my questions:
Is it possible to tell the app to execute in the background for x > 10 minutes?
2.Do i have any other options for something similar? (the actual app i need this implemented in, receives location updates in the background, the user could have the phone in the background for as long as 30 minutes, and suddenly not receiving updates would be bad)
- (void)viewDidLoad {
[super viewDidLoad];
counterTask = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:^{
// do something }];
count=0;
theTimer=[NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:#selector(countUp)
userInfo:nil
repeats:YES];
}
- (void)countUp {
if (count==10000) {
[theTimer invalidate];
[[UIApplication sharedApplication] endBackgroundTask:counterTask];
} else {
NSLog(#"asd");
count++;
NSString *currentCount;
currentCount=[[NSString alloc] initWithFormat:#"%d",count];
_theCount.text=currentCount;
long seconds = lroundf([[UIApplication sharedApplication] backgroundTimeRemaining]);
NSLog([NSString stringWithFormat:#"%ld",seconds]);
}
}
I read somewhere that 10 minutes is the longest we can do background execution, and there is no way to get beyond that(?). However, my code will only execute in the background for 3 minutes.
yes you are right before iOS 7 iOS allowed 10 minutes max for apps to execute in background , however since iOS 7 they have reduced this time to 180 seconds.
But if you want to get Location Updates in background than you can add Required Background modes property in your info.Plist file. Using this you will be able to run your app in background for getting location updates Apple will review your request while reviewing your app for app store submission so be sure to use this mode only if you using it for its actual purpose.
Following are various modes for which apple allows background execution you can take a look at it at Apples Doc on background execution
Edit
If you wish to stop getting location Updates after specific time once user goes to backGround you can do this
- (void)applicationDidEnterBackground:(UIApplication *)application {
[self performSelector:#selector(stopGettingLocationUPdates) withObject:nil afterDelay:1800];
}
-(void)stopGettingLocationUPdates{
[self.locationManager stopUpdatingLocation]
}
This will stop updates after 30 mins.
Your code is not running in the background. It is not testing what you want to test.
Apple's docs say:
Executing a Finite-Length Task in the Background Apps that are
transitioning to the background can request an extra amount of time to
finish any important last-minute tasks. To request background
execution time, call the
beginBackgroundTaskWithName:expirationHandler: method of the
UIApplication class.
The actual time you get is not specified and is probably decided ad hoc based on power consumption, memory needs and so on. They may be a maximum.
They go on to say:
Implementing Long-Running Background Tasks For tasks that require more
execution time to implement, you must request specific permissions to
run them in the background without their being suspended. In iOS, only
specific app types are allowed to run in the background:
Apps that play audible content to the user while in the background, such as a music player app
Apps that record audio content while in the background.
Apps that keep users informed of their location at all times, such as a navigation app
Apps that support Voice over Internet Protocol (VoIP)
Apps that need to download and process new content regularly
Apps that receive regular updates from external accessories
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.
Overview
My compagny is asking me to release an application that can check location of the device every two hours. The app would send these location data through a TCP/IP socket to my server and then receive information accordingly to these very data (straight away, and through the same TCP/IP socket). So I'm not trying to make my application running in background mode continuously (which, actually, seems to be a hot topic in iOS, and it's also not suitable for my project).
Question
In order to be reliable, what is the best practice to achieve this? So, I would like to know:
Since my app is suspended (= inactive), does Apple allow to open a socket to send location when it's woken up by didUpdateToLocation?
How long do I have to perform my send/receive task via my socket?
Should I create a real background task with beginBackgroundTaskWithExpirationHandler and use the 10 minutes allowed by Cocoa to perform my send/receive task?
It is possible (and allowed by Apple) to ask for a 10 mins background task every 2 hours, without human interaction (ie. the user should re-open the app, etc)?
What I achieved/found so far
I added the location key in my Info.plist to be able to run the didUpdateToLocation handler when my app is inactive.
I am able to send and received data through a socket I have opened when my application was in foreground (= active).
I tried to check the backgroundTimeRemaining when didUpdateToLocation is called. I got a very large result number, which seems to be normal because, at this point, the applicationState is not in UIApplicationStateBackground but in UIApplicationStateActive.
These points are not very clear in the official documentation, and I did not found topics related to my specific case.
Thanks for your help.
According to Apple's documentation, you can achieve these by using a very similar approach to the one you described. What I would do is something similar to what is explained in this post by mindsizzlers:
As a recommendation, turn on significant location updates when the app enters in background, so you save battery. You can do this when the app goes to background:
- (void) applicationDidEnterBackground:(UIApplication *) application
{
// Create the location manager if you do not already have one.
if (nil == locationManager)
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
[locationManager startMonitoringSignificantLocationChanges];
}
Then, the system will wake your app when location changes, as explained in the documentation.
If you leave this service running and your app is subsequently suspended or terminated, the service automatically wakes up your app when new location data arrives. At wake-up time, your app is put into the background and given a small amount of time to process the location data. Because your app is in the background, it should do minimal work and avoid any tasks (such as querying the network) that might prevent it from returning before the allocated time expires. If it does not, your app may be terminated.
In order to avoid the action of sending the new location to the server from being highly unreliable (it may work sometimes) you should tell iOS in advance that you are doing a background task that should be allowed to run to completion.
Change your location manager delegate (didUpdateToLocation) to handle background location updates.
- (void) locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
// Send the new location to your server in a background task
// bgTask is defined as an instance variable of type UIBackgroundTaskIdentifier
bgTask = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:
^{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
}];
// Make a SYNCHRONOUS call to send the new location to our server
// Close the task
if (bgTask != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
} else {
// Handle location updates in the normal way
}
}
This way, you will not need to have the timer running. Since you will be waken up automatically and sending the updated location to your server every time it changes significantly.
Of course, if you want to make sure this happens in a specific interval, you can still go with the approach of setting a timer to start receiving location updates and as soon as you get it, you send it to the server. Take a look to this post talking about Background Modes in iOS (section: Receiving Location Updates) and this other questions to see how to do this in detail.
One requirement for our iOS app is to monitor location changes in the background. We have implemented "locationManager startMonitoringForRegion:region", unfortunately we realised when testing the app thoroughly that it is too little precise to fulfil our needs.
We then used "locationManager startUpdatingLocation" to increase accuracy of location. In the foreground everything worked out quite good, this location precision is pretty much what we want.
Now the problem is when the app is in the background, the event "didUpdateLocations" is not called on regular basis. We observed this by looking into log file. What happens is pretty much always the same, the first 15 minutes the app updates the location regularly and then suddenly abruptly stops. After about a 5 minute break it carries on with updating the location for again about 15 minutes. And this continues on in the same way.
We probably made the mistake in the implementation in background handling.
UIApplication *app = [UIApplication sharedApplication];
UIBackgroundTaskIdentifier bgTask = 0;
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
if(rootViewController._selectedLocation == nil)
{
NSLog(#"selected location is nil and app entered background");
[rootViewController.locationManager stopUpdatingLocation];
}
else
{
NSLog(#"selected location is not nil and app entered background");
[rootViewController.locationManager startUpdatingLocation];
}
[app endBackgroundTask:bgTask];
}];
This seem to be called about every 20 minutes and the location is updated again. What are we doing wrong and how can we get a regular location update without any interruptions? We tested this with iPhone 5 and also important to know it was stationary.
You should add to your application plist file the following value:
Required background modes
It is an array telling the system what you app need when in background. For the position at item 0, you should inform the value:
App registers for location updates
In case of doubt, you can see in this link what I'm talking about: iOS Multitasking: Background Location
To save power in my app I have decided to use a mix of startUpdatingLocation when the app is active and go into startMonitoringSignificantLocationChanges mode when the app is in the background. Basically I do the following when the app goes into the background:
-(void)applicationDidEnterBackground:(UIApplication *)application{
[myLocationManager startMonitoringSignificantLocationChanges];
}
And when the app comes back into the foreground I do the following:
-(void)applicationDidBecomeActive:(UIApplication *)application{
//Other irrelevant code
[myLocationManager stopMonitoringSignificantLocationChanges];
[myLocationManager startUpdatingLocation];
}
This seems logical, to me anyways. My question is, should I be calling the stopUpdatingLocation method in the applicationDidEnterBackground event? Like so:
-(void)applicationDidEnterBackground:(UIApplication *)application{
[myLocationManager stopUpdatingLocation];
[myLocationManager startMonitoringSignificantLocationChanges];
}
Where exactly should I be calling the stopUpdatingLocation method?? Please tell me if there is more than one place where this should be done. I'm assuming any error event should stop the updating?
I don't see anything wrong with what you are doing. Note that I have a commercial app that heavily uses location services, and I'm in the midst of rewriting it to improve it's performance and minimize battery usage.
My released version uses sigLocationChanges predominantly (in background & foreground), but switches to using startUpdatingLocation when unhappy with the quality of the location sigLocationChanges gives me, since my UI has to display the users location roughly accurately. I call stopUpdatingLocation immediately after each event to minimize battery drain. In my shipping version this seems to work okay, but my log files have found a tiny subset of users who seem to constantly get poor locations and I'm spinning up the GPS hardware more than I like.
Also in Privacy Settings, the type of location icon displayed for your app will be determined by when you last used the full GPS location mode. Mine always shows the location icon that indicates a heavy battery impact, even if I'm only using startUpdatingLocation briefly a couple times per day, which can make my users paranoid about how my app affects their battery life.
In my new release, to minimize the battery drain of using of startUpdatingLocation, I've cut it's use back to hopefully nil. When the app activates, I now get the current location directly from the location manager, cLLocMgr.location. Typically that's an accurate location, and my UI can be instantly drawn (or refreshed) correctly. I also check it again when certain views are activated to ensure if the user is moving while keeping my app open the display keeps up. Now I only spin up the GPS hardware if the phone has a bad location in a specific situation where a good location is absolutely required in the app. In that case, I limit it's use to 2 minutes (I'm assuming 2 minutes is long enough to get best location from GPS hardware), and wait at least 10 minutes before allowing it's use again.
Your question doesn't give me enough info to tell how accurate you need to be and how dynamic your location display is. But unless you need super accuracy and dynamic display, you should consider just using the current location without spinning up the GPS hardware to save battery.
Edit: Here is the actual code I used for Jeraldo, cleaned up a bit. Note it's been a year since I touched it, so I'm a little rusty on it, hope I didn't clean up anything.
// Called at start to ask user to authorize location data access.
- (void) requestIOSLocationMonitoring {
#if TARGET_IPHONE_SIMULATOR
// If running in siumaltor turn on continuous updating (GPS mode)
// This is for testing as significant change isn't useful in simulator
// Set a movement threshold for new events. This is only used by continiuous mode, not sig change events
// Keep it as low as possible,but not so low as to generate spurious movements.
cLLocMgr.distanceFilter = 30;
// Use continuous location events in simulator.
// desired accuracy only works in continuious (high power) mode.
cLLocMgr.desiredAccuracy = kCLLocationAccuracyBest;
[cLLocMgr startUpdatingLocation];
#else
// If not in simulator app's default is significant change monitoring
[cLLocMgr startMonitoringSignificantLocationChanges];
#endif //TARGET_IPHONE_SIMULATOR
}
// Toggle back and forth between continius updates (GPS on) and
// significant change monitoring
- (void) setGPSMode: (bool) useGPSMode {
// Keep track of time since we last changed GPS mode
NSTimeInterval secsInThisMode = [[NSDate date] timeIntervalSinceDate: lastModeChange];
// inGPSMode is an object instance variable app uses to track mode it is in.
if (inGPSMode != useGPSMode) {
lastModeChange = [NSDate date];
if (!useGPSMode) {
// Tell app to operate as if continuous updating is off
inGPSMode = false;
#if TARGET_IPHONE_SIMULATOR
// But keep using continuous location events in simulator for testing.
cLLocMgr.distanceFilter = 30;
#else
// Turn off continious updates for app on actual devices
[cLLocMgr stopUpdatingLocation];
#endif
} else if (secsInThisMode > cMinGPSModeBreak) {
// Only turn on continuous updating again if it's been off long enough
// Prevents GPS mode from running continiously and killing battery life
inGPSMode = true;
cLLocMgr.desiredAccuracy = kCLLocationAccuracyBest;
cLLocMgr.distanceFilter = kCLDistanceFilterNone;
[cLLocMgr startUpdatingLocation];
}
}
}