Keep Your iOS App Running in Background Forever

来源:互联网 发布:力控网络节点怎么设置 编辑:程序博客网 时间:2024/06/05 05:37

原文:http://blog.dkaminsky.info/2013/01/27/keep-your-ios-app-running-in-background-forever/


I’ve known many people in my life who have a nasty habit of leaving stuff they’re not using plugged into the wall, just wasting energy. My wife would be a prime example. She’s just not in the habit of unplugging it and changing your habits can be hard. Thus, I set out to create an iOS app to remind her to unplug the charger from the wall every time she unplugs the device from a power source.

One without knowledge of iOS development would imagine this to be a simple task. The logic is certainly simple. Notice the battery state go from “charging” or “full” to “unplugged?” Great, send a simple local notification. Otherwise, just keep monitoring the battery state. So what’s the issue? Apple doesn’t want to keep apps running in background.

The logic behind this is pretty reasonable. Apps running in the background drain battery life faster. Apps draining your battery life gives you a bad perception of the device, not the app, because the average consumer has no idea that it’s an app, yet alone which specific app, that’s making their battery life crappy. Thus, iOS won’t let your app run in the background but it does give you some options, such as push notifications, to provide some data to the user and prompt them to open the app again to get the full details.

This is reasonable, but the problem is that push notifications are driven by data coming from the internet. What if you just want to monitor something on the device (i.e. battery state)? Well, as far as I can tell you’re out of luck without using a hack.

The notable hacks are using location services, playing an inaudible sound, and VOIP. Location services really can drain your battery as GPS is power hungry. Playing an inaudible sound is a good option but your app’s going to die if you actually use your device to play audio/video (and most of us do). So the only option in my mind is to use the VOIP hack.

The logic behind allowing VOIP apps to run in the background forever is that they need to be able to receive calls at all times. Thus, generally iOS tries not to close these apps and as a failsafe if it does (due to running out of memory or a crash or just restarting your device) it’ll automatically relaunch it in the background without making the user do anything.

The VOIP hack takes advantage of this to work with any application that wants to run in the background forever. There’s scattered information online, particularly on stack overflow on how to do this but it’s poorly documented and I couldn’t even get it to work for my application without some changes. Thus, I wanted to put it out here as clearly as possible to help anyone else working on an application like mine that needs to monitor the device in the background.

The configuration:
The first thing you need to do is modify your app’s plist. Add the following settings.

  • Application does not run in background: NO
  • Required background modes: VOIP

This lets iOS know that your app will be running in the background and thus will ensure that if it’s terminated, iOS restarts it automatically for you.

The code:
First, we need to set up the code for when the application launches. In here we set up our background task (bgTask) and the block that’s called when our background task expires after 10 minutes (expirationHandler). The expiration handler block ends our background task and restarts it. Additionally, you’ll notice that we start the background task immediately (even if we’re about to go into active mode) because when the app is terminated and relaunched by iOS it doesn’t tell the app that it’s going into background mode.

12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSLog(@"Starting app");
self.powerHandler = [[PowerHandler alloc] init];
self.lastBatteryState = UIDeviceBatteryStateUnknown;
UIApplication* app = [UIApplication sharedApplication];
self.expirationHandler = ^{
[app endBackgroundTask:self.bgTask];
self.bgTask = UIBackgroundTaskInvalid;
self.bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];
NSLog(@"Expired");
self.jobExpired = YES;
while(self.jobExpired) {
// spin while we wait for the task to actually end.
[NSThread sleepForTimeInterval:1];
}
// Restart the background task so we can run forever.
[self startBackgroundTask];
};
self.bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];
// Assume that we're in background at first since we get no notification from device that we're in background when
// app launches immediately into background (i.e. when powering on the device or when the app is killed and restarted)
[self monitorBatteryStateInBackground];
return YES;
}
view rawgistfile1.txt This Gist brought to you byGitHub.

Next, we set up the background job. The key piece here is the dispatch_async block. This is the work we’ll be doing in the background. In my case, the work is just a while loop that checks the battery and then sleeps for 1 second. For 10 minutes this while loop executes and then we see the expiration handler called.

What’s interesting is that even though the expiration handler is ending our task, the dispatch_async block keeps running. Thus, we set a flag in the expiration handler saying the job has expired and use that to trigger the while loop’s end. Finally, we have the expiration handler spin while it waits for the old job to do this and exit the dispatch_async block. If you don’t do this you’ll have multiple dispatch_async blocks running at once and your app will be terminated more quickly.

12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(@"Entered background");
[self monitorBatteryStateInBackground];
}
 
- (void)monitorBatteryStateInBackground
{
NSLog(@"Monitoring battery state");
self.background = YES;
self.lastBatteryState = UIDeviceBatteryStateUnknown;
[self startBackgroundTask];
}
 
- (void)startBackgroundTask
{
NSLog(@"Restarting task");
// Start the long-running task.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// When the job expires it still keeps running since we never exited it. Thus have the expiration handler
// set a flag that the job expired and use that to exit the while loop and end the task.
while(self.background && !self.jobExpired)
{
[self updateBatteryState:[self.powerHandler checkBatteryState]];
[NSThread sleepForTimeInterval:1];
}
self.jobExpired = NO;
});
}
view rawgistfile1.txt This Gist brought to you byGitHub.

Lastly, remember that we started the background task as soon as the app launched. If you don’t want this to run when the app is active then do something inapplicationDidBecomeActive to stop the task from running. In my case this just meant setting another flag to get the while loop to exit.

12 3 4 5 6 7 8 9
- (void)applicationDidBecomeActive:(UIApplication *)application
{
/*
Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
*/
NSLog(@"App is active");
self.background = NO;
}
view rawgistfile1.txt This Gist brought to you byGitHub.

That’s it! From my limited testing this seems to work well and not have a huge effect on battery life. Let me know if it works for your app.

The one huge drawback of this approach (as well as any I’m aware of to create this sort of application) is that Apple would reject it from the app store. Thus, if you’re looking to create an app for yourself, your wife, your friends, etc. then this’ll get the job done, but if you want to get it out into the world you’re out of luck.

Thus, I’m doing the best I can by putting my app onGitHub. I’d love any feedback or contributions.

This entry was posted in Technology on January 27, 2013.

Post navigation

The Past Year’s Gadgets Were Boring… Except for the Wii UGuess Who’s Going to Warped Tour?

8 thoughts on “Keep Your iOS App Running in Background Forever

  1. zhuyingminMay 23, 2013 at 3:44 am

    Code in this article is different from the demo in GitHub,

  2. adminPost authorMay 23, 2013 at 6:48 am

    The github project gets updated with new features and fixes. This blog post doesn’t. Not sure why this would be surprising. It’s been 5 months since I wrote this.

  3. jpJune 8, 2013 at 10:38 pm

    Its seems like it not working on iOS 6…

    Any ideas how to fix it?

  4. adminPost authorJune 9, 2013 at 7:52 pm

    Unfortunately no. This did work on iOS6 but somewhere around the 6.01 or 6.02 release Apple broke the hack. Haben’t spent any time trying to figure out a new way around it so I’d be interested in knowing if you find one. Perhaps in iOS7 they’ll allow for something like this in a “legal” way finally.

  5. jpJune 11, 2013 at 8:54 am

    I can give the short updated on what I was able to figure out.

    It works in general however there are some conditions:
    - your device has to be actively used
    - when your device is not being actively used – after a while (dont know exactly whats the period) it is terminated

    not being actively used means that the device is “turned off”, the display is not in an active mode

    The questions is:

    - is it because in loops there is constantly being checked battery status
    - is it because iOS terminates all processes that it can in order to save battery

    ?

  6. adminPost authorJune 12, 2013 at 8:47 pm

    So I think it gets terminated eventually because iOS terminates all processes to save battery, get more memory, etc. However, it’s supposed to respawn the application as soon as it’s killed due to it being marked as a VOIP app. What I don’t understand is why this how now broken.

  7. ZAINUB SAEED June 19, 2013 at 1:08 am

    i want to run my application in background.when it get some Bluetooth signals ,it starts working on foreground.when does not get any signals ,it reenter in background mode.how i can do this?

  8. adminPost authorJune 23, 2013 at 4:05 pm

    I’m not sure. As has been discussed the hack I was using to keep it in the background isn’t quite working anymore on the latest version of iOS 6. Additionally, I’m not aware of any way to force an app into the foreground without manual user interaction (although I haven’t really looked into it so there may be some way). What you may be able to do is listen in the background for bluetooth signals and when you get one send a local notification to try to get the user to open the app in the foreground. Just an idea.

原创粉丝点击