Keep Your iOS App Running in Background Forever

本文介绍了一种利用VOIP背景模式让iOS应用无限期在后台运行的方法,通过监测电池状态来展示实现细节,并讨论了苹果审核政策的影响。

原文: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.

1 2 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;
}

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.

1 2 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;
});
}

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.

1 2 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;
}

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.

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. zhuyingmin May 23, 2013 at 3:44 am

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

  2. admin Post 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. jp June 8, 2013 at 10:38 pm

    Its seems like it not working on iOS 6…

    Any ideas how to fix it?

  4. admin Post 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. jp June 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. admin Post 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. admin Post 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.

内容概要:本文详细介绍了“秒杀商城”微服务架构的设计与实战全过程,涵盖系统从需求分析、服务拆分、技术选型到核心功能开发、分布式事务处理、容器化部署及监控链路追踪的完整流程。重点解决了高并发场景下的超卖问题,采用Redis预减库存、消息队列削峰、数据库乐观锁等手段保障数据一致性,并通过Nacos实现服务注册发现与配置管理,利用Seata处理跨服务分布式事务,结合RabbitMQ实现异步下单,提升系统吞吐能力。同时,项目支持Docker Compose快速部署和Kubernetes生产级编排,集成Sleuth+Zipkin链路追踪与Prometheus+Grafana监控体系,构建可观测性强的微服务系统。; 适合人群:具备Java基础和Spring Boot开发经验,熟悉微服务基本概念的中高级研发人员,尤其是希望深入理解高并发系统设计、分布式事务、服务治理等核心技术的开发者;适合工作2-5年、有志于转型微服务或提升架构能力的工程师; 使用场景及目标:①学习如何基于Spring Cloud Alibaba构建完整的微服务项目;②掌握秒杀场景下高并发、超卖控制、异步化、削峰填谷等关键技术方案;③实践分布式事务(Seata)、服务熔断降级、链路追踪、统一配置中心等企业级中间件的应用;④完成从本地开发到容器化部署的全流程落地; 阅读建议:建议按照文档提供的七个阶段循序渐进地动手实践,重点关注秒杀流程设计、服务间通信机制、分布式事务实现和系统性能优化部分,结合代码调试与监控工具深入理解各组件协作原理,真正掌握高并发微服务系统的构建能力。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值