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.
Related
This is the example from developer.apple.com
Finite-Length Tasks
Starting a background task at quit time
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithName:#"MyTask" expirationHandler:^{
// 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;
});
}
I want to implement the Long-Running Task, not the "Finite-Length" one. I've not found any examples written with objective-c for current version sdk. Can I start it, say, on application start and run it continuously wheter app is in foreground or background? How do i do that?
I'm a react-native developer, and i've just begun learning objective-c. Therefore i may need just simple example to follow. I've already implemented bridge instance to Cocoa Touch Class, it works fine. All i need is to launch the Long-Running Task in this class. I need it for BLE, but for sake of simplicity, i'd say, let's use location tracking, as it's easier and quicker to test.
My LongRunningTask.m:
#import "LongRunningTask.h"
#implementation LongRunningTask
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(start:(RCTResponseSenderBlock)callback)
{
// start the Long-Running Task here
callback(#[#"done"]);
}
I don't get it, how is long-running task being defined? Seems, there is no specific method or any marker, which would declare a task to be long-running. So, technically, if i'm getting permission from user to run a specific type of long-running task, i can continuously run whatever code i want within applicationDidEnterBackground? Even if it has nothing in common with the permission i've got?
And the only factor that affects if this task will be terminated in 10-ish minutes is if i've got the permission or not?
Thank you!
For the term Long-Running Task means the task which is active until the app has been killed, I am giving you simple example of LocationManager
When you setup your app to receive Location Updates and initialize the LocationManager the app is subjected to receive location updates until you stop the updates in foreground, same is the case with BLE.
See the examples,
_locationManager=[[CLLocationManager alloc] init];
_locationManager.delegate=self;
if ([_locationManager respondsToSelector:#selector(requestWhenInUseAuthorization)]) {
[_locationManager requestWhenInUseAuthorization];
[_locationManager requestAlwaysAuthorization];
}
[_locationManager startUpdatingLocation];
The above code starts the LocationManager for the app to receive GPS location updates if the user has given permission to the app to receive GPS location and if the GPS settings for app is ON to receive updates, the method below will get called till your app is in Foreground
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
Same happens with BLE
If you want your app to be able to receive GPS or, BLE updates in Background you need to turn on related Background Modes from the Project Settings of the app as shown below in the image
The image shows the list of services you can run while app in background, apart from the list you can do certain network activities like downloads and uploads which you have shown in your example, which will run a Long-Running Task, until you kill the app, or the service is interrupted from settings by user manually.
Hope above clears your doubt.
Cheers.
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
I know this is a very common question. I have read many answer but not found out the appropriate answer for me. That's why I post this question and hope someone will show me how to fix my code.
I have function startUpdate to update location using CLLocationManager. In applicationDidEnterBackground method, I write something like below:
[self startUpdate]; // position1
NSLog(#"applicationDidEnterBackground");
__block UIBackgroundTaskIdentifier bgTask;
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
[self startUpdate]; // position2
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self startUpdate]; // position3
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:#"http://google.com"]];
});
I tried to put [self startUpdate] at one of three positions (position1, 2, 3) and sometime it works, sometime not, and I do not know why.
In case it works, updating just run in 3 minutes event. If I call startUpdate when app is in foreground, then put app to background, updating will last 15 minutes with real device, and more than 1hour with simulator ( I don't know exactly, after 1 hour, i thought it would last forever then I stop testing). So what is different between: startupdate in foreground-> go to background vs startupdate in background; simulator vs real device?
right after position3, I called following line to open safari
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:#"http://google.com"]];
But it does not work. So what types of task can be executed here, in background?
I need to keep a service running forever. Some search results say that it's impossible. But some familiar apps did that (Facebook,... keeps service to receive notifications). How can they do that?
Thank you so much,
The exact behaviour of location services has been clarified in the latest update to the Core Location documentation. It now states -
The standard location service delivers events normally while an app is
running in the foreground. When your app is in the background, this
service delivers events only when the location-updates background mode
is enabled for the app. This service does not relaunch iOS apps that
have been terminated.
The significant location change service delivers events normally while
an app is running in the foreground or background. For a terminated
iOS app, this service relaunches the app to deliver events. Use of
this service requires “Always” authorization from the user.
So, it seems that for the best chance of continuing to receive location updates in the background you should switch to significant location change monitoring once you move to the background state and restore full location monitoring once you return to the foreground.
I have a voip app and it needs to run in the background. To my understanding these are the things I need to do:
Flag the app as voip.
Set the 'application does not run in background' flag to NO.
Set an expiration handler, a piece of code that extends the standard 10 minutes of execution time you get.
More?
I set both flags in the info.plist file and I get my 10 minutes. I tried what is suggested in this post. Here is my code:
//in didFinishLaunchingWithOptions:
expirationHandler = ^{
NSLog(#"ending background task");
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
NSLog(#"restarting background task");
bgTask = UIBackgroundTaskInvalid;
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:expirationHandler];
NSLog(#"finished running background task");
};
//in applicationDidEnterBackground
NSLog(#"entering background mode");
bgTask = UIBackgroundTaskInvalid;
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:expirationHandler];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// inform others to stop tasks, if you like
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyApplicationEntersBackground" object:self];
//this while loop is just here for testing
inBackground = true;
while (inBackground) {
NSLog(#"stayin alive!!"); //this keeps going forever
sleep(10);
}
});
The situation:
I use a third party library that handles the communication with our webservice. The service is a CommuniGate pro server. I receive presence updates (online/offline) and instant messages from contacts via the library. The library is CommuniGate's ximss library, a protocol they made which is similar to xmpp and is used for xml-based sip requests, as well as IM and presence. When the user logs in to the app, he sees his contacts (CmmuniGate friends list) and he can choose to call one. After a ximss verification message has been sent and the other side accepted the call it logs the start time of the call and starts a facetime call.
The problem:
When the app enters the background by pressing the home button, I start seeing the 'stayin alive' message in the log and every ten minutes I see that it restarts the background task.
When the app enters the background by pressing the power button, the 'staying alive' messages start showing up for ten minutes, after that it restarts the background task and start restarting it about every 50-100 miliseconds.
I would've been fine with this for now, even it eats battery, because I have time to work on updates and our users don't own the ipads, we do. The problem for me now is that the ximss library loses it's connection (it is session-based). I could restart the session in the library, but this means quite a bit of data transfer to fetch the contacts list and some users use 3g.
I can't edit the library's source, nor can I see it, so I don't know if it creates the sockets the right way.
What do I have to do to handle both situations correctly? I don't even understand why there is a difference.
You cannot re-extend background tasks like this; your app is likely to be terminated. If this is working, it's because you have the background voip mode enabled, not because you are restarting the background task.
Once you have set the voip plist entry, iOS will attempt to keep your app alive as long as possible and restart it if it does get terminated. From Implementing a VoIP App:
Including the voip value in the UIBackgroundModes key lets the system
know that it should allow the app to run in the background as needed
to manage its network sockets. An app with this key is also relaunched
in the background immediately after system boot to ensure that the
VoIP services are always available.
In addition to setting this key, if you need to periodically run code to keep your voip connection alive, you can use the setKeepAliveTimeout:handler: method on UIApplication.
See also Tips for Developing a VoIP App:
There are several requirements for implementing a VoIP app:
Add the UIBackgroundModes key to your app’s Info.plist file. Set the value of this key to an array that includes the voip string.
Configure one of the app’s sockets for VoIP usage.
Before moving to the background, call the setKeepAliveTimeout:handler: method to install a handler to be
executed periodically. Your app can use this handler to maintain its
service connection.
Configure your audio session to handle transitions to and from active use.
To ensure a better user experience on iPhone, use the Core Telephony framework to adjust your behavior in relation to cell-based
phone calls; see Core Telephony Framework Reference.
To ensure good performance for your VoIP app, use the System Configuration framework to detect network changes and allow your app
to sleep as much as possible.
Almost all of the documentation you need is on the Apple developer site.
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