How to correctly save iOS application state? - ios

I am developing a music player application which is able to play music in the background. As long as the music is playing the application won't get terminated anyway, but if the playback is stopped and the application is in background it may get terminated. To get the application more user-friendly I want to save the playback queue and state when the app is being terminated by the system so I implemented the following lines in the app delegate:
- (BOOL)application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder
{
/*
Archive the current queue controller to be able to continue the playback like the app never has been terminated
when user is launching it again.
*/
SMKQueueController *queueController = [[UIApplication sharedApplication] queueController];
[coder encodeObject:queueController forKey:#"queueController"];
return YES;
}
- (BOOL)application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder
{
/*
Restore the recently used queue controller to be able to continue the playback like the app never has been
terminated when use is launching it again.
*/
SMKQueueController *queueController = [coder decodeObjectForKey:#"queueController"];
[[UIApplication sharedApplication] setQueueController:queueController];
return YES;
}
Sometimes (especially when I kill it manually via the double-tap-home-button menu) it works as I would expect it to work. But sometimes this methods aren't called when the application is being terminated.
So my question is: Did I misunderstood how the methods work or what they are for? Or is there any better place to implement such a functionality?

I attended a WWDC 2012 session describing exactly how this should be done. If you are a registered Apple developer, then you can access the videos from the sessions. This one is titled, "Saving and Restoring Application State on iOS". I found this to be a very informative session on this topic.

Related

How to restore bluetooth connection on iOS

I have xamarin project. I would like to pair bluetooth button with an app and keep connection alive in suspended state. I successfully subscribed to characteristic event which represent the click in foreground state. The main use case is to handle the event in suspended state and send data to a server.
I read the documentation here, but I am having the difficulties implement restoring the CBCentralManager especially translating these methods into Xamairn.iOS.
Opt In to State Preservation and Restoration
myCentralManager =
[[CBCentralManager alloc] initWithDelegate:self queue:nil
options:#{ CBCentralManagerOptionRestoreIdentifierKey:
#"myCentralManagerIdentifier" }];
Reinstantiate Your Central and Peripheral Managers
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSArray *centralManagerIdentifiers =
launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey];
...
Implement the Appropriate Restoration Delegate Method
- (void)centralManager:(CBCentralManager *)central
willRestoreState:(NSDictionary *)state {
NSArray *peripherals =
state[CBCentralManagerRestoredStatePeripheralsKey];
...
Questions:
Does app handle the events in suspended state? (technically in backgrounded)
Do I have to reconnect the device after app gets restored?
I think you can find the answer in the document.
1.Does app handle the events in suspended state? (technically in
backgrounded)
You need to enable a Core Bluetooth background execution mode in the info.plist to ensure your app keep running in background.
Also, an iOS app linked on or after iOS 10.0 must include in its Info.plist file the usage description keys for the types of data it needs to access or it will crash. InfoPlistKeyReference
You can read: Core Bluetooth Background Execution Modes
2.Do I have to reconnect the device after app gets restored?
If you gets restored here means you restart your app, I think you need to reconnect since it will lose the connection after the app is terminated.
If you gets restored here means enter foreground from background, I don't think you need to reconnect if you enabled background mode as your app is still running in the background.

Maintain a Multipeer Connectivity session in Background via BackgroundTask?

I am trying to maintain a MultipeerConnectivity "session" when the application enters temporarily in the background, so I thought about using a background task as I've seen few times here ... The problem is I have no idea how to "maintain" the session with the UIBackgroundTask, can someone please post a hint
I don't care about the advertisers/browsers, it's okay to stop them, but I'd like the session to not disconnect as reconnecting is super buggy for the moment.
As per apple documentation "If the app moves into the background, the framework stops advertising and browsing and disconnects any open sessions. Upon returning to the foreground, the framework automatically resumes advertising and browsing, but the developer must reestablish any closed sessions" Refer: Apple doc
One way of extending the connection is as follows
Answering my own question, hoping it would help people in the same situation.
For people new to iOS development, "using a background service" simple means turning on the "Background Modes" option in the "Capabilities" tab of your target.
That alone should give your app around 10 minutes life in the background before it gets killed.
But, when the app goes to background, I use the "backgroundTimeRemaining" to know how much time I have left, it just starts at 180 (in sec, so 3 minutes), yet, the printing loop did continue to work passed three minutes, which means there is a need to manually code what should happen when the time is reached.
For Multipeer Connectivity, this is enough to maintain the connection alive when the app enters background, and it will still receive all messages/streams without a problem.
For the sake of stability, I do some cleaning as follow:
In the appDelegate.h
#property (nonatomic) UIBackgroundTaskIdentifier backgroundTask; //declaring a background task
In the appDelegate.m
- (void)applicationDidEnterBackground:(UIApplication *)application
{
self.backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^
{
//This is called 3 seconds before the time expires
//Here: Kill the session, advertisers, nil its delegates,
// which should correctly send a disconnect signal to other peers
// it's important if we want to be able to reconnect later,
// as the MC framework is still buggy
[application endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid; //Invalidate the background task
}];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Here: We should init back the session, start the advertising and set the delegates from scratch
// This should allow the app to reconnect to the same session with more often than not
self.backgroundTask = UIBackgroundTaskInvalid; //Here we invalidate the background task if the timer didn't end already
}
I've asked this same question once on the apple developer forums. One of the Apple employees told me that basically all of the Multipeer connectivity should be considered off-limits when your app is not int the foreground.

Running iOS app in the background forever

I have a requirement where my app connects to a country channel (USA) and starts playing records from the channel. This is basically a channel which is run by users, the users upload their records to channel and they are played one by one. The user who connects to channel they start listening to channel.
The server sends the iOS app the URLs for the record that needs to be played via sockets, the iOS app creates AVQueuePlayer to play the URL's (using AVPlayerItems) one by one.
If I keep app in background when the channel is full of records for almost 1 day or so, the app keep running and keep playing all the records one by one. I know that AVQueuePlayer takes care of running the app all the time without killing as it receives new player items to play.
But if there are no records in channel and if user connects to channel, then app doesn't play the records in background if the idle time of the app exceeds 10 minutes.
I have written code with background task identifier which keeps my socket connect open so that new record URLs can be received all the time.
I see some of the crash reports in my device which says "AppName(my app) has active assertions beyond permitted time"
So can I know what wrong is going on here.
I am posting the background task code as well
- (void)keepBroadcastPersistentConnection {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if(self._bgTaskIdentifier)
self._bgTaskIdentifier = UIBackgroundTaskInvalid;
self._bgTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler: ^{
[[UIApplication sharedApplication] endBackgroundTask:self._bgTaskIdentifier];
self._bgTaskIdentifier = UIBackgroundTaskInvalid;
CGLog(#"========================================end bg task at time %#", [NSDate date]);
CGLog(#"Time taken by app to run in bg is %f seconds", [[NSDate date] timeIntervalSinceDate:self.date]);
}];
[[BroadcastSocketConnecter sharedSocketConnecter].socketIO sendHeartbeat]; // this keep the socket alive
self.date = [NSDate date];
CGLog(#"========================================begin bg task at time %#", self.date);
});
}
Thanks
From the audio session programming guide:
Why a Default Audio Session Usually Isn’t What You Want
Scenario 3. You write a streaming radio application that uses Audio
Queue Services for playback. While a user is listening, a phone call
arrives and stops your sound, as expected. The user chooses to ignore
the call and dismisses the alert. The user taps Play again to resume
the music stream, but nothing happens. To resume playback, the user
must quit your application and restart it.
To handle the interruption of an audio queue gracefully, implement
delegate methods or write an audio session callback function to allow
your application to continue playing automatically or to allow the
user to manually resume playing. See “Responding to Audio Session
Interruptions.”
Shortly, the solution would be to implement the AVAudioSessionDelegate protocol'a beginInterruption and endInterruption methods. However, the delegate property of the AvAudioSession class was deprecated in iOS6 and Notifications should be used instead. Namely, you are interested in the AVAudioSessionInterruptionNotification
Solution. According to this story if the playback stops, then you should explicitly activate the audio session again to prevent your app from being terminated.
Below is the source for the delegate implementation but the logic doesn't change much with the notifications so I feel it's still a good source for info.
- (void) beginInterruption {
if (playing) {
playing = NO;
interruptedWhilePlaying = YES;
[self updateUserInterface];
}
}
NSError *activationError = nil;
- (void) endInterruption {
if (interruptedWhilePlaying) {
BOOL success = [[AVAudioSession sharedInstance] setActive: YES error: &activationError];
if (!success) { /* handle the error in activationError */ }
[player play];
playing = YES;
interruptedWhilePlaying = NO;
[self updateUserInterface];
}
}
Old response which is still valid but not an elegant solution
You cannot start playing audio in the background. This answer explains what I mentioned in my comment above: https://stackoverflow.com/a/16568437/768935 Doing tricks with the AudioSession does not seem to have an effect on this policy.
As a solution, you need to keep playing audio. If there is no item in the queue, then insert the "silence" audio track. However, I have my doubts that the app with this trick will be admitted in the App Store. It may be better to inform the user that audio playback will be resumed on if the app is started again.

What happens if the user closes an app that is already in the background?

During a background task execution what will happen if the user kills the app (which already is in background mode)?
Picture this:
The app starts task X (with 10 min background support and a expiration handler that has to be called).
Then, the app goes to background and the user kills the app.
I'm confused on what will happen to task X after the app is killed. Does it still have that background time to execute? Does the expiration handler ever gets called?
If the app is "already in background", the user has already "closed the app"! So what can your question mean? You've already gone into the background, and if you called beginBackgroundTaskWithExpirationHandler: as you did so, things proceed normally.
Do you mean the user forcibly kills the app in the background, by summoning the "recent apps" interface and going into jiggy mode and deleting the app from the "recent apps" interface? Then the app is summarily killed; you get no notification and whatever you were doing is interrupted.
Moreover, the only thing the expiration handler block is supposed to do is call endBackgroundTask:. If you are summarily killed, the fact that you are unable to make this call is unimportant!
Ok so this is the result
In this case OS will send a SIGKILL signal to your app's process and applicationWillTerminate method is not called.
Below is just my interpretation from Apple docs, guess work and Google results.
In such case below method of your application delegate will get called
- (void)applicationWillTerminate:(UIApplication *)application
Quote from Apple Docs
For applications that do not support background execution or are
linked against iOS 3.x or earlier, this method is always called when
the user quits the application. For applications that support
background execution, this method is generally not called when the
user quits the application because the application simply moves to the
background in that case. However, this method may be called in
situations where the application is running in the background (not
suspended) and the system needs to terminate it for some reason.
So you have to UIApplicationExitsOnSuspend value to YES in your plist file otherwise there is no guarantee that applicationWillTerminate: will ever get called. That is why the doc has may used in.
I dont think expiration handler block will be called, though I am not sure.
This is pretty easy to test, so I just did (on an iPhone 4S running iOS 6.1.3) using code I'll paste at the end, which starts a background task in the app delegate's applicationDidEnterBackground method.
The result is surprising.
When the user exits the app by clicking the Home button, then manually kills the app (by double-clicking Home, putting things in jiggy mode and hitting the app's close icon) the following occurs:
applicationWillTerminate is called.
When applicationWillTerminate exits, background execution is terminated, regardless of how much execution time the background task had left. The app has been killed.
HOWEVER..
If you arrange things so that applicationWillTerminate doesn't exit after being called, as in my code below, the following happens -- at least it does on my test setup -- when the app is manually killed:
The app's background task continues to run in the background.
Even when the allocated background execution time has expired, the background task continues to run, as does the code in applicationWillTerminate, until that method exits.
This is clearly a bug -- you shouldn't be able to continue to run code forever -- and I wouldn't rely on it always working. But those who've been using various hacks around playing audio in the background to keep an app alive might want to investigate. I'd be interested if other people try the code on different iOS versions/devices and get the same results.
Code for AppDelegate.m in my test project:
//
// BTAppDelegate.m
// BackgroundTest
//
// Created by David Fearon on 07/05/2013.
// Copyright (c) 2013 David Fearon. All rights reserved.
//
#import "BTAppDelegate.h"
#implementation BTAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSLog(#"application didFinishLaunchingWithOptions called");
// Override point for customization after application launch.
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
NSLog(#"applicationWillResignActive: called");
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSLog(#"applicationDidEnterBackground: called");
UIApplication* thisApp = [UIApplication sharedApplication];
UIBackgroundTaskIdentifier __block task = [thisApp beginBackgroundTaskWithExpirationHandler:^{
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self printTimeRemaining];
while(YES) {
[NSThread sleepForTimeInterval:1.0];
[self printTimeRemaining];
}
//[thisApp endBackgroundTask:task];
});
}
-(void)printTimeRemaining{
NSLog(#"Background task time remaining: %f", [[UIApplication sharedApplication] backgroundTimeRemaining]);
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
NSLog(#"applicationWillEnterForeground: called");
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
NSLog(#"applicationDidBecomeActive: called");
}
- (void)applicationWillTerminate:(UIApplication *)application
{
NSLog(#"applicationWillTerminate: called");
while(YES) {
[NSThread sleepForTimeInterval:1.0];
NSLog(#"Still executing code in applicationWillTerminate.");
}
}
#end

Run app for more than 10 minutes in background

I am trying to keep the iOS app in active state for more than 10 mins when it enters in background state.
How can I implement this.
See "Background Execution" section of the iPhoneAppProgrammingGuide. In short, your app must be one of these types:
Apps that play audible content to the user while in the background, such as a music player app
Apps that keep users informed of their location at all times, such as a navigation app
Apps that support Voice over Internet Protocol (VoIP)
Newsstand apps that need to download and process new content
Apps that receive regular updates from external accessories
And you must add to the Info.plist as follows:
Add the UIBackgroundModes key to your
Info.plist file and set its value to an array containing one or more of the following strings:
audio—The app plays audible content to the user while in the background. (This content includes streaming audio or video content using AirPlay.)
location—The app keeps users informed of their location, even while it is running in the background.
voip—The app provides the ability for the user to make phone calls using an Internet connection.
newsstand-content—The app is aNewsstand app that downloads and processesmagazine or newspaper
content in the background.
external-accessory—The app works with a hardware accessory that needs to deliver updates on a
regular schedule through the External Accessory framework.
bluetooth-central—The app works with a Bluetooth accessory that needs to deliver updates on a
regular schedule through the CoreBluetooth framework
Note that part of the review process will be checking to make sure that your app does what it says it's doing with regard to background processing.
Here's what I've done using beginBackgroundTaskWithExpirationHandler.
Write a method that starts a background task.
Inside that background task, run a NSTimer with a scheduled (non repeating) time that is under 10 minutes. For the purposes of my situation I was using 5 minutes.
Once the NStimer's selector fires, end the background task and then instantly call the method that you wrote earlier to start off another background task.
If you want to schedule methods to run at specific times, you will have to check for them in the background task.
This solution isn't really ideal and is still power hungry but will do what you want.
Edit: Since iOS7, I suggest you read this excellent post. Note that this article was last updated in 2013 and is probably irrelevant now.
Only certain types of apps are allowed to run in the background. See the "Implementing Long-Running Background Tasks" section of this guide.
If you aren't requesting permissions to do background processing you can use UIApplication's beginBackgroundTaskWithExpirationHandler but you cannot get extra time.
This code makes your iOS app run indefinitely in the background. Copy and paste the below methods into a singleton / manager which handles the tasks you need to perform in the background.
// #interface
// Declare Private property
#property (nonatomic) UIBackgroundTaskIdentifier backgroundTask;
//#end
// ...
// Copy into
//#implementation
- (void)setupBackgrounding {
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(appBackgrounding:)
name: UIApplicationDidEnterBackgroundNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(appForegrounding:)
name: UIApplicationWillEnterForegroundNotification
object: nil];
}
- (void)appBackgrounding: (NSNotification *)notification {
[self keepAlive];
}
- (void) keepAlive {
self.backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid;
[self keepAlive];
}];
}
- (void)appForegrounding: (NSNotification *)notification {
if (self.backgroundTask != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid;
}
}
You can't. Unless your app uses audio, voip or gps. What you can do is notify the user (via local notifications) that the time is almost up and ask him to open/close the app.
Also if you just need to notify the user, you can use push notifications.
https://github.com/yarodevuci/backgroundTask Check my code here I am using audio player that plays blank wav file Works perfectly on IOS 8 Battery usage around 10% in 24 hour period
How to use:
var backgroundTask = BackgroundTask()
backgroundTask.startBackgroundTask() //Starts playing blank audio file. You can run NSTimer() or whatever you need and it will continue executing in the background.
backgroundTask.stopBackgroundTask() //Stops the task
Warning: Apple will reject this if you try to submit it!
If your App type is not one of VOIP/Audio/Location....(check Background Modes),
or you don't want to specify your App as a background App, you can implement beginBackgroundTaskWithName:expirationHandler or beginBackgroundTaskWithExpirationHandler to ask for more time to run your process in background. You can find the detailed description here
Apps moving to the background are expected to put themselves into a quiescent state as quickly as possible so that they can be suspended by the system. If your app is in the middle of a task and needs a little extra time to complete that task, it can call the beginBackgroundTaskWithName:expirationHandler: or beginBackgroundTaskWithExpirationHandler: method of the UIApplication object to request some additional execution time. Calling either of these methods delays the suspension of your app temporarily, giving it a little extra time to finish its work. Upon completion of that work, your app must call the endBackgroundTask: method to let the system know that it is finished and can be suspended.
Each call to the beginBackgroundTaskWithName:expirationHandler: or beginBackgroundTaskWithExpirationHandler: method generates a unique token to associate with the corresponding task. When your app completes a task, it must call the endBackgroundTask: method with the corresponding token to let the system know that the task is complete. Failure to call the endBackgroundTask: method for a background task will result in the termination of your app. If you provided an expiration handler when starting the task, the system calls that handler and gives you one last chance to end the task and avoid termination.

Resources