上周我发现了这个应用程序,它完全做到了这一点。它在后台运行并跟踪您使用的移动数据/ WiFi 数据的数量。我怀疑开发人员正在将他的应用程序注册为跟踪位置更改,但是当应用程序运行时,位置服务图标不可见,这让我感到困惑。
有人知道如何实现这一点吗? http://itunes.apple.com/us/app/dataman-real-time-data-usage/id393282873?mt=8
我也见过这种情况。尝试了很多后,我发现两件事可能会有所帮助。但我仍然不确定这会如何影响审核过程。
如果您使用其中一种后台功能,应用程序将在被系统退出后(通过系统)再次由iOS在后台启动。稍后我们将滥用它。
在我的情况下,我在我的plist中启用了VoIP后台。所有的代码都在你的AppDelegate中完成:
// if the iOS device allows background execution,
// this Handler will be called
- (void)backgroundHandler {
NSLog(@"### -->VOIP backgrounding callback");
// try to do sth. According to Apple we have ONLY 30 seconds to perform this Task!
// Else the Application will be terminated!
UIApplication* app = [UIApplication sharedApplication];
NSArray* oldNotifications = [app scheduledLocalNotifications];
// Clear out the old notification before scheduling a new one.
if ([oldNotifications count] > 0) [app cancelAllLocalNotifications];
// Create a new notification
UILocalNotification* alarm = [[[UILocalNotification alloc] init] autorelease];
if (alarm)
{
alarm.fireDate = [NSDate date];
alarm.timeZone = [NSTimeZone defaultTimeZone];
alarm.repeatInterval = 0;
alarm.soundName = @"alarmsound.caf";
alarm.alertBody = @"Don't Panic! This is just a Push-Notification Test.";
[app scheduleLocalNotification:alarm];
}
}
注册完成后
- (void)applicationDidEnterBackground:(UIApplication *)application {
// This is where you can do your X Minutes, if >= 10Minutes is okay.
BOOL backgroundAccepted = [[UIApplication sharedApplication] setKeepAliveTimeout:600 handler:^{ [self backgroundHandler]; }];
if (backgroundAccepted)
{
NSLog(@"VOIP backgrounding accepted");
}
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
UIApplication* app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// you can do sth. here, or simply do nothing!
// All your background treads and timers are still being executed
while (background)
[self doSomething];
// This is where you can do your "X minutes" in seconds (here 10)
sleep(10);
}
// And never call the expirationHandler, so your App runs
// until the system terminates our process
//[app endBackgroundTask:bgTask];
//bgTask = UIBackgroundTaskInvalid;
});
}
UIBackgroundTaskIdentifier bgTask;
代码:
// if the iOS device allows background execution,
// this Handler will be called
- (void)backgroundHandler {
NSLog(@"### -->VOIP backgrounding callback");
UIApplication* app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (1) {
NSLog(@"BGTime left: %f", [UIApplication sharedApplication].backgroundTimeRemaining);
[self doSomething];
sleep(1);
}
});
- (void)applicationDidEnterBackground:(UIApplication *)application {
BOOL backgroundAccepted = [[UIApplication sharedApplication] setKeepAliveTimeout:600 handler:^{ [self backgroundHandler]; }];
if (backgroundAccepted)
{
NSLog(@"VOIP backgrounding accepted");
}
UIApplication* app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (1) {
NSLog(@"BGTime left: %f", [UIApplication sharedApplication].backgroundTimeRemaining);
[self doSomething];
sleep(1);
}
});
}
当你的应用程序超时后,VoIP expirationHandler将被调用,在那里你只需重新启动一个长时间运行的任务。此任务将在600秒后终止。但是会再次调用到到期处理程序,它会启动另一个长时间运行的任务,以此类推。现在你只需要检查应用程序是否返回前台。然后关闭bgTask,完成操作。也许你可以在长时间运行的任务中的expirationHandler内执行这样的操作。试一下吧。使用控制台查看发生了什么...玩得开心!
更新2:
有时候简化事情会有所帮助。我的新方法是这样的:
- (void)applicationDidEnterBackground:(UIApplication *)application {
UIApplication* app = [UIApplication sharedApplication];
// it's better to move "dispatch_block_t expirationHandler"
// into your headerfile and initialize the code somewhere else
// i.e.
// - (void)applicationDidFinishLaunching:(UIApplication *)application {
//
// expirationHandler = ^{ ... } }
// because your app may crash if you initialize expirationHandler twice.
dispatch_block_t expirationHandler;
expirationHandler = ^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];
};
bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// inform others to stop tasks, if you like
[[NSNotificationCenter defaultCenter] postNotificationName:@"MyApplicationEntersBackground" object:self];
// do your background work here
});
}
这是在没有VoIP黑客的情况下工作的。根据文档,如果执行时间超过限制,则会执行到期处理程序(在本例中为我的“expirationHandler”块)。通过将块定义为块变量,可以在到期处理程序内递归地重新启动长时间运行的任务。这也会导致无限执行。
请注意,在应用程序再次进入前台时终止任务。如果不再需要任务,请终止任务。
根据我自己的经验,我测量了一些东西。使用启用GPS电台的位置回调会非常快地耗尽电池。使用我在更新2中发布的方法几乎不消耗能量。从“用户体验”角度来看,这是一种更好的方法。也许其他应用程序也是这样工作的,将其行为隐藏在GPS功能背后...
目前还不清楚这些答案中哪些能起作用,我已经浪费了很多时间尝试它们。以下是我对每种策略的经验总结:
beginBackgroundTask...
- 无效。它将在10分钟后退出。即使您尝试了评论中的修复方法(至少截至2012年11月30日的评论)。基本上,您可以使用“位置”后台模式来让您的应用程序在后台运行。即使用户禁止了位置更新,它也会起作用。即使用户按下主页按钮并启动另一个应用程序,您的应用程序仍将在后台运行。但它会耗电,并且如果您的应用程序与位置无关,审核过程可能会有点棘手。但据我所知,这是唯一一个被批准的解决方案。
以下是它的工作原理:
在您的 plist 中设置:
然后引用 CoreLocation 框架(在 Build Phases 中),并在应用程序进入后台之前的某个位置添加此代码:
#import <CoreLocation/CoreLocation.h>
CLLocationManager* locationManager = [[CLLocationManager alloc] init];
[locationManager startUpdatingLocation];
注意: startMonitoringSignificantLocationChanges
不会起作用。
值得一提的是,如果您的应用程序崩溃,则iOS将不会将其恢复。 VOIP hack是唯一可以将其恢复的方法。
startMonitoringSignificantLocationChanges
会在后台运行,但不能防止应用程序在一段时间后进入睡眠状态。 - bendytree还有一种技术可以永久停留在后台 - 在您的后台任务中启动/停止位置管理器,当调用didUpdateToLocation:时,会重置后台计时器。
我不知道为什么它有效,但我认为didUpdateToLocation也被称为任务,从而重置了计时器。
根据测试,我相信DataMan Pro正在使用这个技术。
请参阅此帖子https://dev59.com/8mw15IYBdhLWcg3w3vle#6465280,其中我学到了这个技巧。
以下是我们应用程序的一些结果:
2012-02-06 15:21:01.520 **[1166:4027] BGTime left: 598.614497
2012-02-06 15:21:02.897 **[1166:4027] BGTime left: 597.237567
2012-02-06 15:21:04.106 **[1166:4027] BGTime left: 596.028215
2012-02-06 15:21:05.306 **[1166:4027] BGTime left: 594.828474
2012-02-06 15:21:06.515 **[1166:4027] BGTime left: 593.619191
2012-02-06 15:21:07.739 **[1166:4027] BGTime left: 592.395392
2012-02-06 15:21:08.941 **[1166:4027] BGTime left: 591.193865
2012-02-06 15:21:10.134 **[1166:4027] BGTime left: 590.001071
2012-02-06 15:21:11.339 **[1166:4027] BGTime left: 588.795573
2012-02-06 15:21:11.351 **[1166:707] startUpdatingLocation
2012-02-06 15:21:11.543 **[1166:707] didUpdateToLocation
2012-02-06 15:21:11.623 **[1166:707] stopUpdatingLocation
2012-02-06 15:21:13.050 **[1166:4027] BGTime left: 599.701993
2012-02-06 15:21:14.286 **[1166:4027] BGTime left: 598.465553
我尝试了更新2,但它根本不起作用。当到期处理程序被调用时,它会结束后台任务。然后启动新的后台任务只会再次强制立即调用到期处理程序(计时器未重置且仍处于过期状态)。因此,在应用程序被挂起之前,我得到了43次后台任务的启动/停止。
这是一个相当老的问题,但现在正确的方法是通过后台应用程序刷新来完成,该方法得到了操作系统的全面支持,不需要任何黑客技巧。
请注意,您仍然取决于操作系统何时实际进行后台刷新。您可以设置最小轮询时间,但不能保证它将以该频率被调用,因为操作系统将应用其自己的逻辑来优化电池寿命和无线电使用。
首先,您需要在“功能”选项卡中配置项目以支持后台刷新。这将向您的Info.plist
添加必要的键:
然后你需要在你的AppDelegate
中添加一些内容,它需要实现URLSessionDelegate
和URLSessionDownloadDelegate
:
private var _backgroundCompletionHandler: ((UIBackgroundFetchResult) -> Void)?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
application.setMinimumBackgroundFetchInterval(5 * 60) // set preferred minimum background poll time (no guarantee of this interval)
...
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
_backgroundCompletionHandler = completionHandler
let sessionConfig = URLSessionConfiguration.background(withIdentifier: "com.yourapp.backgroundfetch")
sessionConfig.sessionSendsLaunchEvents = true
sessionConfig.isDiscretionary = true
let session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: OperationQueue.main)
let task = session.downloadTask(with: url)
task.resume()
}
func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
NSLog("URLSession error: \(error.localizedDescription)")
_backgroundCompletionHandler?(.failed)
_backgroundCompletionHandler = nil
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
NSLog("didFinishDownloading \(downloadTask.countOfBytesReceived) bytes to \(location)")
var result = UIBackgroundFetchResult.noData
do {
let data = try Data(contentsOf: location)
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers)
// process the fetched data and determine if there are new changes
if changes {
result = .newData
// handle the changes here
}
} catch {
result = .failed
print("\(error.localizedDescription)")
}
_backgroundCompletionHandler?(result)
_backgroundCompletionHandler = nil
}