如何解决iOS后台应用无法解决的问题?

时间:2016-08-29 03:00:18

标签: ios objective-c xcode background-fetch

我正在尝试将iOS后台应用程序提取到我的应用程序中。在Xcode中进行测试时可以正常运行,但在设备上运行则不行!

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

enter image description here

在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的Debug / SimulateBackgroundFetch

  • 对模拟器和设备进行了测试(成功!)
  • 我已成功通过新方案测试,如另一个SO答案(https://stackoverflow.com/a/29923802/519030

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

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

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

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

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

感谢任何建议!

2 个答案:

答案 0 :(得分:24)

您的问题是您在调用完成处理程序之前从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小时。

我的示例应用是here

答案 1 :(得分:11)

要实现后台提取,您必须执行以下三项操作:

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