With UIBackgroundRefreshStatusAvailable, my app receives silent push notifications as expected, that is both in background as well as in foreground.
The name of this feature -- Background App Refresh -- suggests to me that it does not affect Foreground app behaviour when it is disabled.
Unfortunately, my app does not receive silent push notifications while it is in the foreground, with background app refresh disabled, i.e. UIBackgroundRefreshStatusDenied.
I'm not using user-visible push notitfications, hence no involvement of UNUserNotificationCenter etc.
Is it possible to receive silent push-notifications with background app refresh disabled, i.e. UIBackgroundRefreshStatusDenied?
Objective-C, Xcode 11.3.1, deployment target iOS 10.3. iPhone 6 with iOS 12.4.5 installed.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[application registerForRemoteNotifications];
return YES;
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// not called when app in foreground but bg app refresh turned off
}
Here is an example userInfo dict that didReceiveRemoteNotification received when bg app refresh was enabled:
{
aps = {
"content-available" = 1;
};
ck = {
ce = 2;
cid = "iCloud.de.udo-thiel.DiskBench";
ckuserid = "_56bd97c2eb1e52d09756163efaab6b02";
nid = "e70e4a8d-d77b-4315-8b3e-d9de229cf083";
qry = {
dbs = 2;
fo = 2;
rid = "Res-iPad 2-16";
sid = "public-results2";
zid = "_defaultZone";
zoid = "_defaultOwner";
};
};
}
This is an interesting question as it basically addresses the issue of naming this quite complex functionality.
In short, I am afraid the answer is no. While I haven't had to deal with this myself I have witnessed many others in the same position as you.
To me it helps to think of the entire "silent remote notification feature" as having less to do with the app being in foreground or background, and more with "remote app input":
If it is activated, i.e. you have UIBackgroundRefreshStatusAvailable, your server can send it, silently, in this case, messages on which is reacts. Basically, the server gives input in a similar manner as a user taps (albeit via different called functions, obviously). It doesn't matter whether the app is in foreground or background, this input happens.
If the feature is inactive, i.e. you have UIBackgroundRefreshStatusDenied or UIBackgroundRefreshStatusRestricted, the entire feature is switched off. That means this way of receiving input doesn't work, application:didReceiveRemoteNotification:fetchCompletionHandler: isn't called at all. This method name reflects the issue better than the state enum cases do, admittedly.
Two workarounds:
The most obvious one: register and deregister for remote notifications in applicationDidBecomeActive: and applicationWillResignActive:. Unfortunately this might turn ugly as it results in your server having constantly changing tokens for users, but if you want to avoid getting notifications in background at any cost, go this way.
Register for notifications once and indeed let your logic handle notifications in the foreground and background as you (seem to) already do, but simply make it so it ignores notifications while it is in background (using UIApplication.shared.applicationState). Technically this "wastes" a bit of runtime as your app might get woken up and then not do anything meaningful, but I think that's not too bad.
I'd go with option 2 myself as I rarely see a case where it hurts to receive a silent notification in background.
Generally I would not do anything that relies on notifications being enabled (background or foreground). To put it differently: Yes, if my app is in the foreground and I require to react to something happening on my server, I'm afraid I need to "check" said server, i.e. pull from it in some form.
Or I would, depending on the scenario, inform the user that they should enable it as otherwise the app won't make much sense... Hm...
As a side note: Yup, the Apple SDK is kind of confusing with naming and explaining all the different background things and notifications. Even the app state itself (active, inactive, background, foreground, suspended, ...) is more complex than the names make it seem. I think the reason for that is historical in nature. Before we had background modes and notifications at all, people were just polling data to get something like "silent foreground notifications", basically the thing you want. So eventually they wanted to be able to also do that when the app was not in foreground, asking loudly for background execution. Apple didn't really want to grant that without limits, so slowly the notification concept evolved, but since it was kind of related, the term "background" sneaked in there (besides, we got background fetch as well...), even if it doesn't necessarily make sense.
One could also argue that it's still kind of valid, because it is just more important for when the app is in background/suspended. The use case to "get a silent notification only while in foreground" can still be covered by simple polling (although I agree that this is ugly), and if you use push it doesn't hurt to also get those in background.
Related
I am building an app that needs to speak data while the app is in the background (or screen is off). The speaking part is done via AVSpeechSynthesizer.
My Android app launches a service that listens for the data and whenever it needs to say something, I say it and its done. Now iOS doesn't allow these kind background tasks from what I read. The closet thing is called 'Executing Finite-Length Tasks' which looks like has a time limit of 10 mins max. So this will not work for me. Instead it looks like I need to use Push Notifications.
Looking at the documentation for Push Notifications, if I understand it correctly, if my app is in the Foreground, then my app receives the Push Notification instantly. While if my app is in the Background, a notification is set (notification center), and once the user hits the notification, my app launches with the payload.
The question here is, is there anyway to make a push notification wake my app immediately so I can speak some info?
Or what are some of the other alternative approaches that should be used in this case?
My other idea would be to implement some sort of mp3 stream per user, that would stream audio which I could play in the background. But this seems excessive for what I am trying to accomplish?
I'm not sure if you'll be able to invoke speech synthesis from the background, but you can have you app notified when the push arrives. The trick is to implement the
- (void) application: (RMApp *) application didReceiveRemoteNotification: (NSDictionary *) userInfo fetchCompletionHandler: (void (^)(UIBackgroundFetchResult result)) completionHandler
UIApplicationDelegate method (note the completionHandler parameter).
You must also set the Remote Notifications Background Mode capability.
And you must set content-available in your push notification payload.
A decent writeup on these steps found here:
http://samwize.com/2015/08/07/how-to-handle-remote-notification-with-background-mode-enabled/
I have implemented silent push notifications but I have noticed some strange behaviour. The silent push notifications are handled via:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
The silent push messages only seem to be received if the device is charging (ie cable connected) and/or if my app is foreground.
If I disconnect the device from the charger (or Mac) then the silent push notifications are no longer received unless the app is foreground.
I get non-silent push notifications normally in both cases.
If I plug in the USB cable again, then I get the expected behaviour and silent push notifications are received irrespective of whether the app is foreground or background.
I am using UILocalNotification so I know what is being received.
The fact that it all works fine with the device connected suggests that my silent pushes notifications are configured correctly and that the app has the correct background modes set in the plist etc.
This behaviour is repeatable on iPhone 5s, 6 and iPad 2 all running either IOS 8 or 8.1.
Has anyone else experienced this? It should be easy to reproduce. Why should the simple act of plugging a device into a charger change the ability to receive silent push notifications?
We have experienced the same behavior and have been trying to understand why iOS decides to deliver some notifications and not others.
What we have worked out so far is:
Messages will get received more reliably in background when on wifi than on cellular data. In fact, when on a cellular network (3g/4g), if your signal strength isn't strong enough, iOS receives the push message, but will not wake up your app. We posted in the apple forums about it here: https://devforums.apple.com/message/1069814#1069814. We also opened up a support ticket, and the support team told us to lodge it as a bug report, which we did a couple of weeks ago and are still waiting to hear back.
When you receive a push message, you need to call the fetchCompletionHandler as soon as possible. Technically you have 30 seconds to perform background processing, but iOS has in place a formula whereby the more frequently you send push messages and depending on the amount of time you spend processing those message before returning the app to suspended state, iOS can reduce the amount of times your app gets woken up in the future.
See here from Apple didReceiveRemoteNotification:fetchCompletionHandler: documentation:
As soon as you finish processing the notification, you must call the
block in the handler parameter or your app will be terminated. Your
app has up to 30 seconds of wall-clock time to process the
notification and call the specified completion handler block. In
practice, you should call the handler block as soon as you are done
processing the notification. The system tracks the elapsed time, power
usage, and data costs for your app’s background downloads. Apps that
use significant amounts of power when processing push notifications
may not always be woken up early to process future notifications.
In our testing, we have been sending frequent silent push notifications to our app (every 10 - 30 seconds). And the app is awake for about 3 seconds before we put it back to sleep. We have definitely noticed over time a degradation in the frequency in which our app gets woken up to the point where iOS will only wake up the app every 15 - 30 minutes. So there seems to be some sort of decay/throttling formula in place, but we cannot find any documentation on how it exactly works. We have requested this formula and the variables from apple as a support request, but they said "The information you are requesting is not publicly available" and again asked us to file a bug report.
So, hopefully this is helpful? We are still trying to learn more ourselves which is why I found this question :)
With iOS8 background push delivery to apps has changed. A background push will now only be delivered to the app under certain circumstances. Apple have not stated explicitly what these circumstances exactly are but from my extensive experimentation it basically comes down to if the phone is being charged or not. There are some other variables at play (such as network type, device type, wifi enabled) but the major major major factor is whether the device is being charged or not when the push arrives.
If the phone is being charged via a direct mains power supply or indirectly by being connected by USB to a computer then the background pushes will get delivered to the app the vast majority of the time. But disconnect the phone from the power supply or USB and the background push will almost never get delivered to the app, even if the phone's batter has a 100% charge.
You can very easily test this for yourself just by sending some pushes while the phone is being charged versus while its not. BUT you must take into account that background pushes with a development build and using the sandbox environment DO NOT behave the same as background pushes with a production build and a production environment, the background pushes are actually more likely to be delivered to the app in development then they are in production so it is vital you test using a production build and Apple's production environment to see the actual results.
Note there are two steps the push delivery, the first is it needs to get delivered to the phone itself, the second is once the phone has it, it then needs to get delivered by the OS to the app. In iOS7 things such as turing on Wifi made the chances of the push getting to the phone increase. With iOS8 however even though the push is successfully being delivered to the phone, the OS is not forwarding it on to a background app if the phone isn't being charged. This means the phone gets the notification and holds on to it, sometimes for several hours, before it might forward it to the app if the phone isn't being charged.
I had experienced the same problem and the reason behind not receiving push notification while the app is not charging is that when the Low Power Mode is enabled from Settings > Battery it disables background-fetch feature for all applications.
Which prevents device from receiving push notification.
This link might be useful.
Apple Documentation
I also noticed the same and wasted some time figuring out. See https://stackoverflow.com/a/31237889/1724763
If you turned off Bg App Refresh, silent remote push will be dropped silently (the irony).
However, my observation is that if you connect to Xcode via cable, somehow the Bg App Refresh setting is ignored and all silent push for your app works.
I highly suspect this is an undocumented feature: charging causes the Bg App Refresh setting to be ignored.
It's not working because you have enabled the wrong background mode in the plist. You need to enable the remote-notification tag (App downloads content in response to push notifications), not fetch. Fetch is used for something else. You may also need to use the content available key in your JSON payload, e.g.,
{
"aps": {
"content-available": 1
},
"yourdatakey":{data}
}
I hope you are using APNS delivering priority as "CONSERVE_POWER" (5), try to change it as "IMMEDIATE" (10)
I've been experiencing this problem for some time, and am very grateful for this question and #Kevin D. sharing their understanding. I'm beginning to think that https://stackoverflow.com/a/30834566/1449799 and https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW4 (see priority in one of the tables) are describing why my app is having trouble:
It is an error to use this priority for a push that contains only the content-available key.
To send the notifications, I'm using node-apn where the default (which i also need) is to set the priority to max (10 [beware, it looks like only 10 and 5 are correct values at this time]), but since i wanted a silent notification, i don't have alert, badge, or sound set.
IF YOUR APP IS NOT VoIP YOU CAN'T FOLLOW THIS ANSWER [your app will be rejected]
I found another solution that is worked for me using PushKit Framework
VoIP pushes provide additional functionality on top of the standard push that is needed to VoIP apps to perform on-demand processing of the push before displaying a notification to the user
When I send VOIP Push the App wakes up whatever the state of the Application and can perform any operations
Register for VOIP PushNotification in didFinishLaunchingWithOptions
PKPushRegistry *pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
pushRegistry.delegate = self;
pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type{
if([credentials.token length] == 0) {
NSLog(#"voip token NULL");
return;
}
NSString *originalToken=[NSString stringWithFormat:#"%#",credentials.token];
NSString *token = [originalToken stringByTrimmingCharactersInSet: [NSCharacterSet characterSetWithCharactersInString:#"<>"]];
token = [token stringByReplacingOccurrencesOfString:#" " withString:#""];
NSLog(#"PushCredentials: %#",token);}
then you can handle any background fetch in this function once you receive VOIP PushNotification
-(void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type
NOTE: you must use certificate that enable VoIP Services Certificate
I have requirement to initiate sync on every night at 1 AM or every 2 weeks. How will I achieve this in iOS? Is there a way in iOS that my application can say remind me at this time and my app should be reminded at that particular mentioned time.
Background Fetch is an opportunity provided by OS (iOS 7 onwards) to the apps who requested to perform an operation when in background, however leaving on OS to decide the timing. The OS will silently awake your app (actually a handler method) in background after learning about the user's usage of an app.
Feature usage: As of now, this feature has been introduced in iOS7 to enhance usability of social media, newspaper etc daily/frequent/heavy content refreshing apps.
Note- its just a request that is not guaranteed to be fulfilled but an attempt of underlying OS to better user experience)
From coding/implementation perspective, there are primarily 2-3 key steps -
A)
Turn ON Background Modes for Background Fetch attribute (Select App Target > Capabilities > Background Modes > Background Fetch)
B) Implement this app delegate method -
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler NS_AVAILABLE_IOS(7_0);
C) Define your fetch interval, typically when app launches - i.e appDidFinishLaunchingWithOptions
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
Since it's an event fired by OS automatically, how to test this behavior during development -
Well, you can simulate/test the background fetch behavior in your app through XCode 5+ using menu Debug > Simulate Background Fetch
Here is the link to the tutorial with sample project.
Also, you can checkout session 204 from Apple's WWDC 2013 videos - What's New with Multitasking
EDIT -
As of today (iOS 7.0.3), in lack of proper official documentation, the practical usage tells that -
The enforced minimumBackgroundFetchInterval is between than 5 to 15 minutes (anything less than it is not honored).
The background fetch doesn't happen when device is locked.
The background fetch is triggered immediately after the device is unlocked by user.
However, in upcoming versions, I feel this feature would be enhanced and giving more power to developers/users.
You can use NSLocalNotification. See an example (taken from here):
UILocalNotification *notification = [[UILocalNotification alloc] init];
// Activate in 5 segundos
notification.fireDate = [[NSDate alloc] initWithTimeIntervalSinceNow:5];
// message to show
notification.alertBody = self.nameTextField.text;
// default sound
notification.soundName = UILocalNotificationDefaultSoundName;
// button title
notification.alertAction = #"Ahora te lo cuento";
notification.hasAction = YES;
// activa la notificación
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
The point is that the behaviour depends on the application state when the local notification fires. If the app is running on the foreground, you can handle the notification silently. But if the app is not running, or it's in the background, user interaction is required.
You may want to try a combination of Apple Push Notifications and Background Transfer Service. If you watch the WWDC 2013 Video to which Ashok referred, in the video they alluded to having silent push notifications wake the app which will then let you configure a background transfer.
You could register your app to have Push Notifications, have your server send a request for notification to Apple, then when the push notification is received, your app wakes from background, you configure your download/upload using NSURLSession, then start the session. When the data transfer is complete, run whatever code you require to sync your app, then notify the OS that your background transfer is complete so that it can put your app back to sleep.
Note that the timing is still not really under your control, but this is about as close as I can see to creating a solution to your problem.
I've successfully implemented #Steve's solution - e.g. implementing Apple Push Notifications without the Background Transfer Service. My app relies on creating EKCalendar events which then will trigger the creation of a push notification for the specific start/end date/time. The app uses the didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler function in the app delegate which handles the receipt of the remote notification and then subsequently executes some code to perform the app's necessary functionality based on the event. Note: the remote notification message must include the content-available set to 1. The only downside is the reliance upon the timely delivery of the remote notifications, which so-far during my testing has proven to be about 95+% effective/accurate to within 2-30+ seconds, but I've also seen cases where the notifications are never received or > 1-5+ minutes delayed. This is infrequent and for my purposes acceptable for my app's functionality.
I wrote a simple messaging system, which allows sending brief messages from web interface to devices, in form of push notification.
On android, everything went well, once device receives notification is sends delivery confirmation receipt back to server, then read acknowledgement. Obviously, delivery confirmation often happens while app is running in background or phone is asleep.
I wrote similar app for iOS. How surprised I was that application: didReceiveRemoteNotification is not called when app is not active!
Is it really impossible to track message delivery without user interaction when app is not active? Others have suggested keeping log of messages on server and sending them when app opens, but this still requires user interaction.
Is there a way around apple restriction on background services? Can I somehow make my app use sound or location service, to allow simple POST request while in background?
In iOS7 you can use push notifications with background fetch, which is a remote notification with the content-available flag set. Example payload: {aps: {content-available: 1}}.
In this case iOS will wake up your app (there are some limitations see: Will iOS launch my app into the background if it was force-quit by the user?).
After your app is woken up you have 30 seconds to send to your server the push notification receipt confirmation.
You'll also have to enable the "Background fetch" capability in the Target background modes and update the AppDelegate to include this method:
- (void)application: (UIApplication *)application didReceiveRemoteNotification:
(NSDictionary *)userInfo fetchCompletionHandler:
(void (^)(UIBackgroundFetchResult))completionHandler
So this requires a bit of work from your side.
I hope this helps.
For iOS there isn't any direct way that provides any info regarding the actual delivery, but I found one workaround which I tried and it is working fine.
Use "Notification service extension", the main use of this is to provide rich notification, so it basically wakes our app whenever new push comes and gives around 30 seconds of time perform our task like download an image to show in the notification. We can use this app wake up feature to call our backend.
So send some unique id with payload and from this method call your backend server with the push id, by this way you can be sure that push notification is delivered into the device.
This will work all state of the application, even in the killed state, so this is full proof and we can rely on this workaround for delivery info.
Reference link: https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension
Mis-using background services is a good way to get your app rejected. Apple are pretty strict on what an app can do in the background.
As a user, if I found out that an app I'd installed was making web requests in the background with no good reason, it would be swiftly deleted!
Push notifications are a one-way message - there is no guarantee that a notification has even been delivered, never mind read. I suggest you read up on the APNS here.
When application is not in Active state application: didReceiveRemoteNotification method won't be called .
If you want track the notification information when application is not in active state follow the below procedure.
application didFinishLaunchingWithOptions: method will be called every time when we open the application
from this method we are getting NSDictionary object called launchOptions. From this launchOptions dictionary we will get the notification data in the form of dictionary for the key UIApplicationLaunchOptionsRemoteNotificationKey
find the code from below:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
NSDictionary *remoteNotify = [launchOptions objectForKey: UIApplicationLaunchOptionsRemoteNotificationKey];
//Accept push notification when app is not open
if (remoteNotify) // it is only true when you get the notification{
// use the remoteNotify dictionary for notification data}}
http://cydia.saurik.com/package/backgrounder
Check that out, for th source, click on Developers Page.
If its not apples way, there is no way.
That's where jailbreaking comes in. You might have to make your app jailbreak compatible and take advantage of a lot more power.
I'd say for what your looking for, make a new version of Backgrounder that works they way you need it.
The idea behind this app is very simple: download a file. However this app will be for people who are not always within internet access range, so I need it to know that at, say 9:00 AM, to download a file to the hard drive. There will be a button within the app to do it manually as well, but I've already got that working.
As I understand it, this will be difficult if it is even possible. I know that iOS doesn't like multitasking, but I am also aware that it does allow for background timer functions. I am open to any sort of suggestions anyone might have to accomplish this, even if it means writing a separate app. Thanks.
Edit: I see there is the possibility of working with Notifications, or even maybe the Calendar. Ideas in that category as also welcomed.
Edit 2: I also read something about an external server initiating an app, but it gave no description.
Here's the situation regarding background execution and notifications and timers etc. in relation to an app scheduling some activity to happen periodically.
An app cannot execute in the background unless:
It requests extra time from the OS to do so. This is done using beginBackgroundTaskWithExpirationHandler. It is not specified (intentionally) by Apple how long this extra time is, however in practice it is around 10 minutes.
An app has a background mode, the modes are: voip, audio, location, newstand. Even if it has one of these types an app cannot execute without some restrictions. The rest of this discussion assumes the app does not have a background mode.
When an app is suspended it cannot do ANYTHING to rouse itself directly. It cannot previously have scheduled an NSTimer, it cannot make use of something like performSelector:afterDelay. etc.
The ONLY way the app can become active again is if the USER does something to make it active. The user can do this from via of the following:
Launch the app directly from its icon
Launch the app in response to a local notification that was previously scheduled by the app while it was active.
Launch the app in response to a remote notification sent by a server.
A few others: such as URL launching if the app is registered to deal with launching via a url; or if its registered to be capable of dealing with a certain type of content.
If an app is in the foreground when a local/remote notification fires then the app receives it directly.
If the app is not currently in the foreground when a local/remote notification fires then the app DOES NOT receive it. There is no code that is executed when the notification fires!
Only IF the user selects the notification will the app become active and it can execute.
Note that the user can disable notifications, either for the entire device, or just for a specific application, in which case the user will never see them. If the device is turned off when a notification is due to fire then it is lost.
You could use local notifications. They execute code when the user opens the notification that is presented. You can set the local notification to recur at a specified interval (e.g. daily, hourly, weekly, etc). This still requires the user to open the app to get the process started.
UILocalNotification Class Reference
Once the delegate method fires, you only get a few seconds to execute code. Register for a long running background task, and download whatever you need to do. If it can't finish downloading in the 10 minutes you get for the task, then you need to rethink your download strategy.
Apple Multitasking and Backgrounding
We are using this same concept on iOS apps where I work, so this will work if you set it up right.
UPDATE
For those curious how this will work, you just need to implement the UILocalNotification delegate methods. They inherit from the UIApplicationDelegate that should already be in place.
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
// start your long running bg task here and update your file
}
** UPDATE 2 **
Martin H's answer is the most correct so far. But this begs the question, if the user never opens the app, what is the point of downloading data they are never going to see? A recurring local notification reminding them to open the app and update may be the best way, but still requires the user to interact with your app if they want it to remain current and up-to-date.
Background applications have a set time limit (I believe 10 minutes, but don't quote me on that, it could be less) to complete whatever they are working on. You will not be able to use background tasks to do what you want.
What you can do is set an NSUserDefault with the date of the last download. On launch check the date saved, if the date is not the current date, and it is after 9:00am, initiate the download programatically.
I am pretty sure this is not possible, at most what you can do is send push notifications to the user, so that they manually update when required.
Have you tried the following?
if ([[[UIDevice currentDevice] systemVersion] floatValue] >=7.0)
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:600];
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
completionHandler(UIBackgroundFetchResultNewData);
}