35

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

  • 我的测试设备正在运行 iOS 9.3.5(我的部署目标是 7.1)
  • 我在 Xcode 中目标的“功能”下的“后台模式”下启用了“后台获取”

在此处输入图像描述

在应用程序中: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;
}

我已经实现了应用程序: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)中所示的新方案

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

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

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

我知道用于确定允许后台获取发生的频率的算法是一个谜,但我希望它至少偶尔会在几天内运行。

我不知道还有什么要测试的,或者如何解决为什么它似乎在模拟器中工作而不是在设备上工作。

感谢任何建议!

4

2 回答 2

27

您的问题是您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 小时。

我的示例应用在这里

于 2016-08-30T05:20:01.223 回答
13

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

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

后台获取频率

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

  • 网络连接在该特定时间是否可用
  • 设备是否唤醒即运行
  • 您的应用程序在之前的后台提取中消耗了多少时间和数据

换句话说,您的应用程序完全由系统来为您安排后台获取。此外,每次您的应用程序使用后台提取时,它最多有 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;
}
于 2016-09-01T10:45:18.780 回答