Comment résoudre le problème iOS background app fetch ne fonctionne pas?

j'essaie de faire fonctionner iOS background app fetch dans mon application. En testant dans Xcode il fonctionne, en tournant sur l'appareil il ne fonctionne pas!

  • mon dispositif d'essai exécute iOS 9.3.5 (ma cible de déploiement est 7.1)
  • j'ai activé "Background fetch" sous "Background modes" sous "Capabilities" sur la cible dans Xcode

enter image description here

dans l'application:did finishlaunchingwithoptions j'ai essayé divers intervalles avec setMinimumBackgroundFetchInterval, y compris 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;
}

j'ai implémenté l'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);
}
  • j'ai (avec succès!) testé avec le simulateur et le dispositif à L'aide du débug/SimulateBackgroundFetch de Xcode

  • j'ai testé avec succès avec un nouveau schéma comme indiqué dans une autre réponse SO (https://stackoverflow.com/a/29923802/519030)

  • mes tests montrent que l'exécution du code dans la méthode performFetch s'effectue en environ 0,3 seconde (donc cela ne prend pas beaucoup de temps)
  • j'ai vérifié que la mise à jour de l'arrière-plan du périphérique est activée dans les paramètres.
  • bien sûr, j'ai regardé les autres questions en espérant que quelqu'un d'autre ait vécu la même chose. :)

lors de l'exécution sur l'appareil et non connecté à Xcode, mon code n'est pas en cours d'exécution. J'ai ouvert l'application, fermé l'application (pas tué l'application!), ont attendu pendant des heures et des jours. J'ai essayé la journalisation dans les fetch handers, et j'ai aussi écrit du code pour envoyer des notifications locales.

j'ai vu une fois avec succès mes notifications locales tester sur l'appareil, et en fait iOS a semblé déclencher le fetch trois fois, chacun à environ une quinzaine de minutes d'intervalle, mais alors il ne s'est jamais produit à nouveau.

je connais l'algorithme utilisé pour déterminer la fréquence à laquelle fond de chercher à se produire, c'est un mystère, mais je m'attends à exécuter, au moins occasionnellement, dans un laps de jours.

Je ne sais pas quoi tester d'autre, ou comment résoudre pourquoi cela semble fonctionner dans le simulateur mais pas sur l'appareil.

appréciez tous les conseils!

25
demandé sur Community 2016-08-29 06:00:18

2 réponses

votre problème est que vous revenez de performFetchWithCompletionHandler avant d'appeler le gestionnaire d'achèvement, puisque l'opération network fetch se produit à l'arrière-plan et que vous appelez le gestionnaire d'achèvement dans votre méthode de délégué. Comme iOS pense que vous ne jouez pas selon les règles, il va nier votre capacité à utiliser background fetch.

Pour résoudre le problème, vous devez appeler beginBackgroundTaskWithExpirationHandler et ensuite terminer cette tâche après avoir appelé le gestionnaire d'achèvement.

quelque Chose comme:

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;
}

mon application de test utilisant cette approche a été l'exécution d'un fetch toutes les 15 minutes initialement, mais il devient moins fréquent au fil du temps. Sans la tâche de fond, il a présenté le même problème que vous voyez.

j'ai trouvé que le réglage de l'intervalle de fetch de fond à quelque chose d'autre que UIApplicationBackgroundFetchIntervalMinimum aide aussi. Mon application de test fonctionne avec un background fetch intervalle de 3600 (une heure) et a été déclenchée de manière fiable depuis plusieurs jours maintenant; même après un téléphone redémarrez et ne plus lancer l'application. L'intervalle de déclenchement réel est de 2 à 3 heures.

Mon exemple d'application est ici

20
répondu Paulw11 2016-09-07 04:43:16

pour implémenter background fetch il y a trois choses que vous devez faire:

  • cochez la case Background fetch dans les Modes Background des capacités de votre application.
  • utilisez setMinimumBackgroundFetchInterval ( _ :) pour définir un intervalle de temps approprié pour votre application.
  • implémenter l'application ( _ : performFetchWithCompletionHandler:) dans votre délégué app pour gérer le fetch d'arrière-plan.

Fetch En Arrière-Plan Fréquence

la fréquence à laquelle notre application peut effectuer le background Fetch est déterminée par le système et dépend de:

  • Si la connectivité réseau est disponible à ce moment précis
  • Si l'appareil est éveillé, c'est à dire en
  • combien de temps et de données votre application a consommé dans le précédent Arrière-Plan Fetch

En d'autres termes, votre application est entièrement à la merci du système de programme arrière-plan fetch pour vous. De plus, chaque fois que votre application utilise background fetch, elle a au plus 30 secondes pour compléter le processus.

Inclure dans votre AppDelegate(changer le code ci-dessous pour récupérer votre besoin)

-(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;
}

À vérifier si l'Actualisation en arrière-plan est activée pour votre application, utilisez le code ci-dessous:

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;
}
10
répondu TechSeeko 2016-09-01 10:45:18