In my iOS app I am download a fairly large amount of data in a background thread. What I need to do is somehow check when the user reopens the app if this task is still running or if it has been canceled.
If the user hits the home button for example, and then comes back to the app, the thread will continue which is good. But what if somehow the thread gets torn down by the OS. How can I on app resume know if a thread is running.
Thanks!
Some Code:
// Starts a background thread and also allows it to run in the background if the user exits app
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIBackgroundTaskIdentifier bgTask = 0;
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask:bgTask];
}];
// Code here to run in background
// Tell the main thread its done, and also remove itself from background execution
dispatch_async( dispatch_get_main_queue(), ^{
NSLog(#"Done with background thread");
[self endBackgroundUpdateTask:bgTask];
});
First off any thread, foreground or background will be suspended when your App goes into the background. There are some things you can do to get a small amount of time as you transition into the background to finish up and cleanup.... but your definitely getting shut down, "background" thread or not, when your app leaves the screen.
That means your download will have been suspended and you will have to gracefully recover and resume where you left off. For that you should investigate the techniques iOS offers for being notified your going into the background and being allowed some extra time to clean up and save some state that can be used to resume.
Investigate this:
beginBackgroundTaskWithExpirationHandler
Then use backgroundTimeRemaining to check how much time is left as you try to finish up in the extra time granted. When it's clear you can't finish in the allotted time save state and set a flag that indicates you need to continue or start over when the you re-launch. There is really no other way as iOS doesn't have any way of knowing if you finished what you were trying to do and it is up to you to
Related
My app makes use of UIBackgroundMode, i.e. it gets relaunched by iOS when Bluetooth events occur even while the app is not active. Therefore Bluetooth events are dispatched by iOS onto a background queue (queue is specified by me).
Can I however dispatch code back to the main queue, i.e.
DispatchQueue.main.async { } (Swift)
dispatch_async(dispatch_get_main_queue(), ^{ }) (Objective-C)
and assume that its run loop is running, that is my blocks get dispatched? Or is the main queue suspended in background mode and thus I should avoid adding dispatching blocks to it?
It should be safe to dispatch to the main queue while in the background.
When your app is in the background, everything it does is technically done on background threads because the system marks your app as a lower priority. However, in order for the system to let your code run it needs to at least have a main queue. Therefore, it is safe to assume you will have access to the main queue. You can create other work queues from there if you want, but everything will most likely be shoved onto the one background thread for execution, so you may not see much benefit.
Also note that testing background threading can be a little tricky. Background threads will always be executed and seem to never be shut down while in the simulator. The same happens when testing on a device if Xcode is connected and debugging. The system also lets your app run continuously for 10 minutes (last I checked, it's possible this has changed in the last year or two) after entering the background if needed, and after it will require something like the bluetooth event you mentioned to get additional time in the background.
Source: An unfortunate amount of experience dealing with backgrounded apps.
I am developing an application for a custom wearable communicating through BLE.
I have subscribed to the UI background modes in the info.plist file for Bluetooth-central.
I am transferring a firmware file of around 600 kb by dividing into chunk sizes of 200 bytes each. The process is going fine but as I am pressing the Home button the app is entering into background state and thus terminating the process after 1-2 minutes.
If my screens dims after certain amount of time then the firmware transfer continues but as soon as the home button is pressed the app stops transferring the data after few minutes.
Please help me out of this scenario.
Thanks.
To run task in background mode, you need to follow the belowed steps.
Step 1: Declare __block UIBackgroundTaskIdentifier bgTask as global variable.
Step 2: To add following code in applicationDidEnterBackground.
- (void)applicationDidEnterBackground:(UIApplication *)application {
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
bgTask = UIBackgroundTaskInvalid;
}];
}
Step 3: Stop background task handler once apps come in foreground mode.
- (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.
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
}
I am using beginBackgroundTaskWithExpirationHandler method in the applicationDidEnterBackground delegate method for keeping the NSTimer to keep running. But then the application gets killed after a long time if left in background for a long time (in my case 7-10 mins). I don't want my app to be getting killed and also I want the timer to run if in background. How do I get rid of this issue. Following is the code that I have written in the applicationDidEnterBackground method
- (void)applicationDidEnterBackground:(UIApplication *)application {
if ([application respondsToSelector:#selector(setKeepAliveTimeout:handler:)]) {
[application setKeepAliveTimeout:600 handler:^{
DDLogVerbose(#"KeepAliveHandler");
// Do other keep alive stuff here.
}];
}
/*
* The following code is used to make the app running in background state as certain features
* (eg: NSTimer) doesn not run if its in background or if the phone is locked
*/
UIBackgroundTaskIdentifier locationUpdater =[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:locationUpdater];
} ];
}
The beginBackgroundTaskWithExpirationHandler is only intended to let you complete a finite length task of a few minutes after the app leaves foreground. iOS, quite deliberately, doesn't let your app run in background perpetually except for very narrow situations (e.g. VOIP, audio app, navigation app) or for narrow functional needs (significant change location services, background fetch, etc.). But you cannot just run arbitrary code perpetually in the background.
For a discussion of the options, see the Background Execution section of the App Programming Guide for iOS.
I have a media player app that is playing music with MPMoviePlayerController. I need to update the UI based on the playback position. The best I can tell, there is no way to actively receive this info from the player with a callback or something, and I basically need to poll for it myself.
So I thought I would use a simple timer, run every second, for that. The code is such:
Somewhere in the setup code:
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updatePlaybackProgressFromTimer:) userInfo:nil repeats:YES];
And then:
- (void) updatePlaybackProgressFromTimer:(NSTimer *)timer {
if (([UIApplication sharedApplication].applicationState == UIApplicationStateActive) && (player.playbackState == MPMoviePlaybackStatePlaying)) {
CGFloat progress = player.currentPlaybackTime / player.duration;
// do something useful with this info
}
}
The timer is run every second, even when the app is in the background. The method first sees if the app is active and the player is playing, and then does some UI updating.
Is there any battery life implication to running a timer every second in this fashion? Should I be more diligent and try to tear down the timer when entering the background and reactivating it when activating the app? I’m sure there’s some battery life effect, but realistically, how serious is it? Or is there any other recommended ways of doing this kind of thing?
I can't imagine using an NSTimer will significantly impact battery life - unless the work being done when it is triggered impacts battery life. The timer is simply being added to the current run loop:
A timer is not a real-time mechanism; it fires only when one of the
run loop modes to which the timer has been added is running and able
to check if the timer’s firing time has passed.
NSTimer Class Reference
According to the documentation, you should be pausing any timers when your application is about to resign its active status:
In response to this change, your app should do the following in its
applicationWillResignActive: method:
Stop timers and other periodic tasks.
Stop any running metadata queries.
Do not initiate any new tasks.
Pause movie playback (except when playing back over AirPlay).
Enter into a pause state if your app is a game.
Throttle back OpenGL ES frame rates.
Suspend any dispatch queues or operation queues executing non-critical code. (You can continue processing network requests and
other time-sensitive background tasks while inactive.)
When your app is moved back to the active state, its
applicationDidBecomeActive: method should reverse any of the steps
taken in the applicationWillResignActive: method. Thus, upon
reactivation, your app should restart timers, resume dispatch
queues, and throttle up OpenGL ES frame rates again. However, games
should not resume automatically; they should remain paused until the
user chooses to resume them.
iOS App Programming Guide
I am building a cinema listing app, where the user can drill down thru the dataset to finally end up with a listing for a specific movie/theater/etc.
Now assume the user pauses using the app for 7 days. When reopening the app what he should not see are the listings from 7 days ago. But if the user just puts the app in background for a few minutes, the user should continue just where he left. I thought I could solve this issue by killing the app after a certain amount of time in background. This is the code:
static BOOL goingToQuit = NO;
#define KILL_IN_BACKGROUND_AFTER_SECS 300
- (void)applicationDidEnterBackground:(UIApplication *)application
{
goingToQuit = YES;
UIApplication* app = [UIApplication sharedApplication];
UIBackgroundTaskIdentifier __block bgTask;
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
if(UIBackgroundTaskInvalid != bgTask) {
// Start the long-running task to kill app after some secs and return immediately.
dispatch_after( dispatch_time(DISPATCH_TIME_NOW, KILL_IN_BACKGROUND_AFTER_SECS * 1e09),
dispatch_get_main_queue(), ^{
if(goingToQuit) exit(0);
[app endBackgroundTask: bgTask];
});
}
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// cancel ongoing background suicide.
goingToQuit = NO;
}
What I see on my device is this: after KILL_IN_BACKGROUND_AFTER_SECS the app gets killed. After restarting the device logs show that the app got a new PID, entries showing the restart etc. Yet the device does not show the default.png startup image, but the screenshot of where the user has been before.
On the other hand if the user kills the application explicitely (double click on home button, tap & hold, click - on app) before he is restarting it the application starts with its default.png start up screen. This is the behaviour I want when killing the app programmatically.
Does anyone have an idea how to accomplish this? Any idea is highly appreciated.
BTW: As a workaround I tried to hide the main window during applicationDidEnterBackground and show it again on applicationWillEnterForeground. This, however, is highly confusing to the user when he is switching between apps.
In your app delegate's applicationDidEnterBackground you can display a view in front of the rest of your views, which the OS will grab as the last visible thing and which will be displayed when the app becomes active again (if it's still alive).
In your app delegate's applicationDidBecomeActive you can check to see if the data needs updating; if not then simply dismiss the view (animation is nice), and if so then first update (or just clear out) your data and then dismiss the view.
This is fairly common in apps. Many just use the default.png startup image, since users are accustomed to seeing it when the app launches normally.