如何解决 iOS 后台应用程序提取不工作的问题?

How to troubleshoot iOS background app fetch not working?

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

在 application: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 时,我的代码未执行。我已经打开应用程序,关闭应用程序(不是杀死应用程序!),等待数小时和数天。我已经尝试登录获取处理程序,还编写了发送本地通知的代码。

我曾经成功地在设备上看到我的本地通知测试,实际上 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(一小时)并且已经可靠地触发了好几天;即使在 phone 重新启动而不是再次 运行 应用程序之后。不过实际触发间隔是2-3小时

我的示例应用程序是 here

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

  • 在应用程序功能的后台模式中选中后台获取复选框。
  • 使用 setMinimumBackgroundFetchInterval(_:) 设置适合您应用的时间间隔。
  • 在您的应用程序委托中实施应用程序(_: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;
}