We're currently developing an iOS app that needs to check location in the background. At first, we tried to use significant location changes, but they aren't accurate enough/don't trigger often enough. We considered using region monitoring, but from what I've read online, that isn't always accurate either, and you also have the problem of a limited number of regions to monitor. (We may eventually try region monitoring.) At the moment, however, we're attempting to use the standard location updates to track the user location in the background, with a plan to have it at check at intervals of 5 minutes, or so.
The app is registered for location updates in the background (using 'App registers for location updates' for 'Required background modes'), and we start a background task which checks the location once, stops location updates, then uses NSThread sleepForTimeInterval: to (at the moment, while we're in development) pause the task for 10 seconds. It then checks the location once again, stops location updates, pauses for 10 seconds, etc.
This appears to work as expected... When the app goes into the background, we receive a log/notification with our location update every 10 seconds, and when the app is reopened, the logs/notifications stop. However, the problem is that when the app then goes into the background for a second time, it appears the original background task was never cancelled, and a new one is created, so there are now two tasks running, each checking location at 10 sec on intervals. If the app is opened/sent to the background multiple times, then a background task is started for each of them.
I thought about setting a flag to say "has the app been sent to the background at least once?", and only run the task if it's the first time it's sent to the background, but this seems to cause additional problems, and (as a relatively new iOS developer) I'm curious as to why the background tasks aren't being cancelled when the app enters the foreground.
The AppDelegate.h file contains...
#interface AppDelegate : UIResponder <UIApplicationDelegate, CLLocationManagerDelegate> {
UIWindow *window;
UINavigationController *navigationController;
UIBackgroundTaskIdentifier bgTask;
BOOL inBackground;
}
The AppDelegate.m file contains...
- (void)applicationDidEnterBackground:(UIApplication *)application {
inBackground = YES;
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (inBackground == YES) {
NSLog(#"%#", #"Check location...");
[locationManager startUpdatingLocation];
[NSThread sleepForTimeInterval:10];
}
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
inBackground = NO;
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
The location updates are working as expected, I just can't work out why the background tasks aren't being cancelled/ended when the app enters the foreground. I do wonder if it's anything to do with the NSThread sleepForTimeInterval:, but I'm not sure if it is, or how to fix it (if indeed, it is). Thanks, in advance, for any help!
You don't manage location updates by sleeping and then requesting them. You manage location updates by setting "location" in UIBackgroundMode (as you do), and then implementing a CLLocationManagerDelegate. This has nothing to do with beginBackgroundTaskWithExpirationHandler:. That's for requesting additional time (up to about 10 minutes) to finish a given operation. You shouldn't be calling that at all just to get location updates.
Once you've registered as a location app in UIBackgroundMode, you will automatically get updates whenever the location changes within the accuracy you specified for your location manager. The system will do all the work for you.
What you're describing may actually hurt battery life because it frustrates the OS's ability to manage the multiple location sensors (of which the GPS is just one). Tell the OS what you need by setting the correct accuracy (if significant changes is too coarse), and let it do its job. Getting really accurate location from the GPS is expensive. You should do battery testing before assuming that it's cheaper to do every 5 minutes than to leave on. The best thing you can do to preserve power is to reduce the required accuracy. You might turn it down to a coarse level, and then when you come to the foreground move it to an accurate level. But keeping track of precisely where the user is every 5 minutes is going to be expensive. It's hard to fix that.
BTW, what you're really trying to do here is get to run "something" every 5 minutes. There is no mechanism for that in iOS. You can either ask for location services or not (and configure it in various ways). You can't ask for "I want to wake up every five minutes and ... do anything." After about 10 minutes you're going to be killed if you don't call endBackgroundTask:.
To your question of why the tasks aren't being cancelled, see How to use beginBackgroundTaskWithExpirationHandler for already running task in iOS. As I said, this "background tasks" is not the tool you want for this problem. It's completely unrelated.
I'm fairly certain that your instance variable bgTask is being reallocated when the app comes back into the foreground, so the value doesn't contain the identifier you're looking to kill. Consider saving this identifier in NSUserDefaults or something a little more permanent and retrieving it later.
Related
I know there is NSURLSession class which is launched in iOS7 and it does have NSURLSessionUploadTask for uploading data to server, But I need to know whether it will continue uploading even after in background mode if no? then what to do for continue that task in background mode too for complete uploading.
previously we can use beginBackgroundTaskWithExpirationHandler: and endBackgroundTask: which will continue that task to max 10 minutes for iOS6 but in iOS 7 and above it will hardly run upto 3 minutes (as per my knowledge).
So can you please help me guys for any solution or example if available.
The answer for your first question is probably YES, you can continue uploading for the tim e that is permitted by the iOS but for that you need use UIBackgroundTaskIdentifier to get hold of that extra time.
And for the second part of your question for uploading the remaining images when the app is not running you can use Background fetch. When you enable Background fetch the app regularly downloads and processes small amounts of content from the network. So probably by combining UIBackgroundTaskIdentifier and Background fetch you can achieve uploading the images in background.
Below mentioned are the steps to combine UIBackgroundTaskIdentifier and Background fetch.
First let's initialise UIBackgroundTaskIdentifier
-(void) beginBackgroundUploadTask
{
if(self.backgroundTask != UIBackgroundTaskInvalid)
{
[self endBackgroundUploadTask];
}
self.backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUploadTask];
}];
}
Once the background task runs out of time from the stipulated allotted time, we should invalidate and end the background task.
-(void) endBackgroundUploadTask
{
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask ];
self. backgroundTask = UIBackgroundTaskInvalid;
}
Do not forget to enable Background Modes in Xcode → Select Project File → Capabilities → Check the Background fetch checkbox.
For further info you can refer the link below:
http://mobisoftinfotech.com/resources/mguide/background-fetch-ios/
I'm looking for a way to get a background location updates forever. I'm using iOS 7.1.
Current Scenario : It is running fine in background but not forever. After every 22 to 30 minutes, GPS Location updates gets suspended.
Note : We get GPS update forever as long as we are moving but when we stop for more than an hour the location update get suspended automatically by OS...again when we start moving the OS does not resume/detect the location update automatically...
So first question is :
How to re-invoke location service from that 22nd or 30th minute so that it can run continuously and forever?
Below set of properties we have set our application plist file :
Required Background Modes :
App registers for location updates
App provides Voice over IP services
App plays audio or streams audio/video using AirPlay
And also we have done this :
UIApplication *app = [UIApplication sharedApplication];
_bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:_bgTask];
_bgTask = UIBackgroundTaskInvalid;
}];
where _bgTask is UIBackgroundTaskIdentifier.
Secondly we also want to capture the GPS Location update in every 1 second. For that we have written a separate timer to capture that location update. please note that once the GPS location update gets suspended by the OS it also suspend our timer.
So second question is : How can I run the timer forever in the background?
I'm still pretty green with iOS, but the code below seems like it should work. I have a bunch of work to do in the background so a "busy indicator" is supposed to be shown to the user while the work is done. But what happens is the busy indicator does not appear for several seconds. It only appears once most of the work (but not all) has completed.
- (void) fetchDataTest {
// next line will create and display a busy indicator
__block MRProgressOverlayView *overlay = [MRProgressOverlayView showOverlayAddedTo:self.view title:#"Loading…" mode:MRProgressOverlayViewModeIndeterminate animated:YES];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^(void) {
[self fetchData];
dispatch_async(dispatch_get_main_queue(), ^(void) {
[overlay hide:YES];
});
});
}
I have lots of logging (not shown above) that runs so slowly I can watch the output log (including thread ids) and see overlay has been created and background work started - yet no overlay on my device appears for several seconds.
How can I get the overlay to appear immediately?
Observations:
you shouldn't use GCD to make synchronous data fetches look asynchronous — just use the asynchronous fetches directly. They're much better for overall scheduling and for battery life;
it's the bit that occurs before the dispatch_asyncs that isn't happening, not the stuff within them;
Jack Wu's guess is probably right: on what thread or queue is fetchDataTest being performed? It should be the main queue.
On the last point: UIKit is not generally thread safe. With a few exceptions, you should always use it from the main queue. That's a large part of what the main queue is for nowadays: all user interaction. Then heavy work should be passed off elsewhere.
When you open up an app and press home screen again, the app is obviously in te background. When you open other apps and wait a time, the views of my app have been unloaded (like UITableView reloads data).
Is there some sort of notification or how do I know whether my app is about to release their views? Is it just viewDidUnload?
This link should help: iPhone Development - Simulate Memory Warning
Basically you received a memory warning and parts of the view got unloaded.
unfortunately, when you app is put in the background it is frozen and it will not receive events. Unless you have requested some background processing time and have provided the system with a background processing task Expiration Handler:
backgroundTask_ = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
// Peform clean up work
// Mark the task now as invalid
[[UIApplication sharedApplication] endBackgroundTask:backgroundTask_];
backgroundTask_ = UIBackgroundTaskInvalid;
}];
in which case, after the extra, undetermined amount of processing time is over, the expiration handler will be called.
Some good background docs can be found here App States and Multitasking. But even then you won't be able to do much in the way of cleanup.
Good luck
My goal is to run a repeating timer every five seconds if and only if the application is running in the background. I've tried a couple of ideas, but they don't seem to work.
Idea 1: Doesn't run even once.
- (void)applicationDidEnterBackground:(UIApplication *)application {
[NSTimer scheduledTimerWithTimeInterval:(5.0/5.0) target:self selector:#selector(check_expiry) userInfo:nil repeats:YES];
}
Idea 2: Runs every five seconds, but I can't seem to stop the loop.
- (void)applicationDidEnterBackground:(UIApplication *)application {
counter = YES;
while (counter) {
sleep(5);
[self check_expiry];
}
// Counter is set to NO in willEnterForeground and didBecomeActive, but this loop continues to run due the sleep();
}
How can I get this loop to run properly?
Thanks!
When an application "enters the background" in iOS, that's not like normal operating systems, where it continues to run. The application enters a suspended state. It doesn't keep running; only application state is preserved, and even that's not guaranteed - if the device is running low on memory, iOS will happily terminate your application to give the memory to the active application. If you attempt to block in applicationDidEnterBackground:, like you are doing with sleep(), iOS will simply terminate your application for not returning from that method promptly.
Your application is woken up periodically if it's configured for background processing GPS events, VOIP, etc., but abusing those just to get your app to run will stop you from getting App Store approval.
This is all covered in The iOS Application Programming Guide.
For anyone looking for a workaround, I merely created a system that schedules timers at a later date when the applicationDidEnterBackground: and changed/cancelled them when they were edited/deleted. Information on timer scheduling was stored in a local dictionary.