如何解决iOS后台应用程序取消无法解决?

我想让iOS后台应用程序获取在我的应用程序中工作。 在Xcode中进行测试时,它可以正常运行,但在设备上运行却没有!

  • 我的测试设备运行的是iOS 9.3.5(我的部署目标是7.1)
  • 我在Xcode中的目标“Capabilities”下的“Background modes”下启用了“Background fetch”

在此处输入图像描述

在应用程序中:didFinishLaunchingWithOptions我用setMinimumBackgroundFetchInterval尝试了各种间隔,包括UIApplicationBackgroundFetchIntervalMinimum

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // tell the system we want background fetch //[application setMinimumBackgroundFetchInterval:3600]; // 60 minutes [application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum]; //[application setMinimumBackgroundFetchInterval:1800]; // 30 minutes return YES; } 

我已经实现了application:performFetchWithCompletionHandler

 void (^fetchCompletionHandler)(UIBackgroundFetchResult); NSDate *fetchStart; -(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { fetchCompletionHandler = completionHandler; fetchStart = [NSDate date]; [[NSUserDefaults standardUserDefaults] setObject:fetchStart forKey:kLastCheckedContentDate]; [[NSUserDefaults standardUserDefaults] synchronize]; [FeedParser parseFeedAtUrl:url withDelegate:self]; } -(void)onParserFinished { DDLogVerbose(@"AppDelegate/onParserFinished"); UIBackgroundFetchResult result = UIBackgroundFetchResultNoData; NSDate *fetchEnd = [NSDate date]; NSTimeInterval timeElapsed = [fetchEnd timeIntervalSinceDate:fetchStart]; DDLogVerbose(@"Background Fetch Duration: %f seconds", timeElapsed); if ([self.mostRecentContentDate compare:item.date] < 0) { DDLogVerbose(@"got new content: %@", item.date); self.mostRecentContentDate = item.date; [self scheduleNotificationWithItem:item]; result = UIBackgroundFetchResultNewData; } else { DDLogVerbose(@"no new content."); UILocalNotification* localNotification = [[UILocalNotification alloc] init]; localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:60]; localNotification.alertBody = [NSString stringWithFormat:@"Checked for new posts in %f seconds", timeElapsed]; localNotification.timeZone = [NSTimeZone defaultTimeZone]; [[UIApplication sharedApplication] scheduleLocalNotification:localNotification]; } fetchCompletionHandler(result); } 
  • 我已经(使用Xcode的Debug / SimulateBackgroundFetch)对模拟器和设备进行了测试(成功!)

  • 我已经成功测试了一个新方案,如另一个SO答案所示( https://stackoverflow.com/a/29923802/519030 )

  • 我的测试显示在大约0.3秒内在performFetch方法中执行的代码(所以它不需要很长时间)
  • 我已validation设备在设置中启用了后台刷新。
  • 当然,我已经看过其他SO问题,希望其他人经历同样的事情。 🙂

在设备上运行但未连接到Xcode时,我的代码没有执行。 我打开了应用程序,关闭了应用程序(没有杀死应用程序!),等待了几个小时和几天。 我已经尝试登录fetch handers,还编写了代码来发送本地通知。

我曾经成功地在设备上看到了我的本地通知测试,事实上iOS似乎触发了三次获取,每次大约相隔大约十五分钟,但之后它再也没有发生过。

我知道用于确定允许背景提取发生频率的算法是一个谜,但我希望它能在一段时间内偶尔运行。

我无法测试其他什么,或者如何解决为什么它似乎在模拟器中工作但不在设备上工作。

感谢任何建议!

您的问题是您在调用完成处理程序之前从performFetchWithCompletionHandler返回,因为网络提取操作正在后台进行,您在委托方法中调用完成处理程序。 由于iOS认为您不遵守规则,因此它将拒绝您使用后台获取的能力。

要解决此问题,您需要调用beginBackgroundTaskWithExpirationHandler ,然后在调用完成处理程序后结束该任务。

就像是:

 UIBackgroundTaskIdentifier backgroundTask -(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { fetchCompletionHandler = completionHandler; fetchStart = [NSDate date]; self.backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^{ [application endBackgroundTask:self.backgroundUpdateTask]; self.backgroundTask = UIBackgroundTaskInvalid; }]; [[NSUserDefaults standardUserDefaults] setObject:fetchStart forKey:kLastCheckedContentDate]; [FeedParser parseFeedAtUrl:url withDelegate:self]; } -(void)onParserFinished { DDLogVerbose(@"AppDelegate/onParserFinished"); UIBackgroundFetchResult result = UIBackgroundFetchResultNoData; NSDate *fetchEnd = [NSDate date]; NSTimeInterval timeElapsed = [fetchEnd timeIntervalSinceDate:fetchStart]; DDLogVerbose(@"Background Fetch Duration: %f seconds", timeElapsed); if ([self.mostRecentContentDate compare:item.date] < 0) { DDLogVerbose(@"got new content: %@", item.date); self.mostRecentContentDate = item.date; [self scheduleNotificationWithItem:item]; result = UIBackgroundFetchResultNewData; } else { DDLogVerbose(@"no new content."); UILocalNotification* localNotification = [[UILocalNotification alloc] init]; localNotification.alertBody = [NSString stringWithFormat:@"Checked for new posts in %f seconds", timeElapsed]; [[UIApplication sharedApplication] scheduleLocalNotification:localNotification]; } fetchCompletionHandler(result); [[UIApplication sharedApplication] application endBackgroundTask:self.backgroundUpdateTask]; self.backgroundTask = UIBackgroundTaskInvalid; } 

我使用这种方法的测试应用程序最初每15分钟执行一次获取,但随着时间的推移它会变得不那么频繁。 如果没有后台任务,它会显示您遇到的相同问题。

我发现将背景提取间隔设置为UIApplicationBackgroundFetchIntervalMinimum以外的其他内容也有帮助。 我的测试应用程序正在以3600(一小时)的后台获取间隔运行,并且已经可靠地触发了几天; 即使手机重启后又没有再运行应用程序。 但实际的触发间隔是2-3小时。

我的示例应用就在这里

为了实现后台提取,您必须做三件事:

  • 选中应用function的后台模式中的背景提取框。
  • 使用setMinimumBackgroundFetchInterval(_ :)设置适合您的应用程序的时间间隔。
  • 在您的app委托中实现应用程序(_:performFetchWithCompletionHandler :)以处理后台提取。

背景提取频率

我们的应用程序执行背景提取的频率由系统决定,它取决于:

  • 网络连接是否在该特定时间可用
  • 设备是否处于唤醒状态即运行状态
  • 您的应用程序在之前的Background Fetch中消耗了多少时间和数据

换句话说,您的应用程序完全受制于为您安排后台获取的系统。 此外,每次应用程序使用后台提取时,最多只需30秒即可完成此过程。

包含在AppDelegate中 (将以下代码更改为您的提取需求)

 -(void) application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { NSLog(@"Background fetch started..."); //---do background fetch here--- // You have up to 30 seconds to perform the fetch BOOL downloadSuccessful = YES; if (downloadSuccessful) { //---set the flag that data is successfully downloaded--- completionHandler(UIBackgroundFetchResultNewData); } else { //---set the flag that download is not successful--- completionHandler(UIBackgroundFetchResultFailed); } NSLog(@"Background fetch completed..."); } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum]; return YES; } 

要检查您的应用是否启用了后台刷新,请使用以下代码:

 UIBackgroundRefreshStatus status = [[UIApplication sharedApplication] backgroundRefreshStatus]; switch (status) { case UIBackgroundRefreshStatusAvailable: // We can do background fetch! Let's do this! break; case UIBackgroundRefreshStatusDenied: // The user has background fetch turned off. Too bad. break; case UIBackgroundRefreshStatusRestricted: // Parental Controls, Enterprise Restrictions, Old Phones, Oh my! break; }