There are several thread on this topic and nothing which works for me. I don't have to deploy the app in the app store, so I can do hacks in the app to keep it running. Any way I can keep the app going even when it is backgrounded? Any pointers appreciated.
I found this github project solving this problem: https://github.com/voyage11/Location
Obviously Apple wouldn't approve this hack, but I guess it would work in your case.
Basically what you have to do is:
Use the location background mode capability in info.plist
Always have background task running, but don't let it run for longer than a minute. Create a new background task every minute and stop old task.
Apart from the previous task rolling, also keep a long running background task. I'm not sure though if that is really needed.
Start the location manager every minute and requestAlwaysAuthorization.
Some important snippets from the referenced code:
Background task:
bgTaskId = [application beginBackgroundTaskWithExpirationHandler:^{
[self.service debugLog:[NSString stringWithFormat:#"BG....background task %lu expired", (unsigned long)bgTaskId]];
}];
And start the location manager:
if(IS_OS_8_OR_LATER) {
[_locationManager requestAlwaysAuthorization];
}
[_locationManager startUpdatingLocation];
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.
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.
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.
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.
For the bounty, I am not interested in GPS or audio background modes as the former uses too much of the battery and the latter prevents any other audio from being used, otherwise facing audio interruption, thus ending background processes. I will need a way to be continuously processing in the background, so background modes that trigger occasionally are also out of the question.
If there is some way to run the application in the background, even by ignoring Apple's rules, I am interested in trying it. If the answer is VOIP, I am unsure where to begin the implementation, as all of my research has come up too high level or as a failure. How will my application be able to run in the background using the VOIP background mode. Without any added code, the application refuses to run in the background.
I know that with iOS 7, background modes have changed again. I would like to be able to run my application (that will never need to be approved on the iOS App Store) in the background. I would also like to be able to stop execution in the background until a specific time in the future.
For example, I would like it to run a process for 15 minutes, schedule the next task and then sleep until that time. For now, I've had to run a silent track in the background for background processing, but I would like to be able to have the application truly sleep during that time - also, playing real music or making a phone call are "handy features" of the iPhone and I don't like losing them.
I know there is also GPS, but that consumes an enormous amount of battery. The other background modes don't seem to give full control of background processing and timing to the application and leave a large portion of the timing and execution duration to the OS.
What I need is to be able to have my application process in the background for minutes at a time and then sleep until a fairly specific interval and continue processing. Is this possible with a better approach than I am currently using?
I've seen that VOIP used to be a possibility, but I'm not sure that it will work, as I don't need the application to run one simple task in the background, but rather to continue whatever was running in the foreground before the application was pushed to the background. Also, individual tasks could take upwards of 1 hour to complete, so they won't be able to transfer when the background task expires. All of my assumptions are based off this thread.
Edit: There seems to be a terrible drop off rate with this method. At random, the recursion will seemingly fail for seemingly no reason (maybe a system timeout on execution?). If I place the recursion before ending the background task, the OS kills my application, but if I put it after, it occasionally seems to stop the background tasks at some point. I have seen it stop in the middle of my "allotted background time", as well.
In short, the below method does seem to run indefinitely, but not infinitely. Is there either a way to make the runtime guaranteed to be infinite or another solution?
It seems that using VOIP was leagues easier than I had first thought.
All that is required to run my application indefinitely (unfortunately sleeping is not an option) is to add voip to the selected Background Modes, either in the plist or in Target's Capabilities. After that, adding and running this code once, in an object that is never deallocated (your AppDelegate works nicely here), will allow for infinite background processing time:
- (void)infiniteBackgroundLoop
{
__block UIApplication *applicationBlockReference = [UIApplication sharedApplication];
__block AppDelegate *appDelegateBlockReference = self;
__block UIBackgroundTaskIdentifier backgroundTask = [applicationBlockReference beginBackgroundTaskWithExpirationHandler:^
{
[applicationBlockReference endBackgroundTask:backgroundTask];
backgroundTask = UIBackgroundTaskInvalid;
[appDelegateBlockReference infiniteBackgroundLoop];
}];
}
In order to allow sleeping indefinitely, add a break to the recursion.
I used background fetch to achieve something similar.
You can use this to keep your app active in the background.
I have the a demo, see if it helps you:
Add these properties to your .h file -
#property (nonatomic, strong) NSTimer *updateTimer;
#property (nonatomic) UIBackgroundTaskIdentifier backgroundTask;
Now suppose you have a action on button --> btnStartClicked then your method would be like :
-(IBAction)btnStartClicked:(UIButton *)sender {
self.updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(calculateNextNumber)
userInfo:nil
repeats:YES];
self.backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"Background handler called. Not running background tasks anymore.");
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid;
}];
}
-(void)calculateNextNumber{
#autoreleasepool {
// this will be executed no matter app is in foreground or background
}
}
and if you need to stop it use this method,
- (IBAction)btnStopClicked:(UIButton *)sender {
[self.updateTimer invalidate];
self.updateTimer = nil;
if (self.backgroundTask != UIBackgroundTaskInvalid)
{
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid;
}
i = 0;
}
We also played with background modes in our app, and I check all solution that found, and can say that there is only one way to stay active in background and is not "VOIP", because "VOIP" gives your app wake-up every 5-6 minutes not infinity run.
In documentation about setKeepAliveTimeout:handler: you can see that this method will call handler block at minimum every 600 second, and block has a maximum of 10 seconds to perform any needed tasks and exit.
To clean this you can add NSLog(#"time remain: %f", [[UIApplication sharedApplication] backgroundTimeRemaining]); to your infiniteBackgroundLoop implementation. Because second and next call beginBackgroundTaskWithExpirationHandler not get same time of background run as first call.
Another working way is Location Manager, yes is bad because use lot of battery but is get you that you want - app newer stop.
The implementation can be easily found there
You could use background fetch and set the regresh rate to a short NSTimeInterval.
In your didFinishLaunchingWithOptions: try to add:
[[UIApplication sharedApplication] setMinimumBackgroundFetch:1];
I haven't tested this, let me know if this could be a starting point.