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 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.
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.
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.
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 in applicationDidBecomeActive to stop the task from running. In my case this just meant setting another flag to get the while loop to exit.
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 on GitHub. I’d love any feedback or contributions.