web-dev-qa-db-fra.com

Comment dépanner la récupération de l'application d'arrière-plan iOS qui ne fonctionne pas?

J'essaie de faire fonctionner la récupération de l'application d'arrière-plan iOS dans mon application. Pendant les tests dans Xcode, cela fonctionne, mais pas sur l'appareil!

  • Mon appareil de test exécute iOS 9.3.5 (ma cible de déploiement est 7.1)
  • J'ai activé "Récupération d'arrière-plan" sous "Modes d'arrière-plan" sous "Capacités" sur la cible dans Xcode

enter image description here

Dans l'application: didFinishLaunchingWithOptions, j'ai essayé différents 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 l'appareil en utilisant Debug/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/5190 )

  • Mes tests montrent l'exécution de code dans la méthode performFetch en environ 0,3 seconde (donc cela ne prend pas longtemps)
  • J'ai vérifié que l'actualisation en arrière-plan est activée dans les paramètres de l'appareil.
  • Bien sûr, j'ai regardé les autres SO 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 ne s'exécute pas. J'ai ouvert l'application, fermé l'application (pas tué l'application!), Attendu des heures et des jours. J'ai essayé de me connecter aux gestionnaires d'extraction et j'ai également écrit du code pour envoyer des notifications locales.

Une fois, j'ai vu avec succès mon test de notifications locales sur l'appareil, et en fait, iOS a semblé déclencher la récupération trois fois, chacune à une quinzaine de minutes d'intervalle, mais cela ne s'est plus jamais produit.

Je sais que l'algorithme utilisé pour déterminer à quelle fréquence autoriser la récupération en arrière-plan est un mystère, mais je m'attendrais à ce qu'il s'exécute au moins occasionnellement en l'espace de quelques jours.

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

Appréciez tous les conseils!

29
Jason

Votre problème est que vous revenez de performFetchWithCompletionHandler avant d'appeler le gestionnaire d'achèvement, car l'opération de récupération réseau se produit en arrière-plan et vous appelez le gestionnaire d'achèvement dans votre méthode déléguée. Étant donné qu'iOS pense que vous ne respectez pas les règles, cela vous empêchera d'utiliser la récupération en arrière-plan.

Pour résoudre le problème, vous devez appeler beginBackgroundTaskWithExpirationHandler, puis 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 d'abord exécuté une extraction toutes les 15 minutes, mais elle devient moins fréquente au fil du temps. Sans la tâche de fond, elle présentait le même problème que vous voyez.

J'ai trouvé que la définition de l'intervalle de récupération en arrière-plan sur autre chose que UIApplicationBackgroundFetchIntervalMinimum aide également. Mon application de test fonctionne avec un intervalle de récupération en arrière-plan de 3600 (une heure) et se déclenche de manière fiable depuis plusieurs jours maintenant; même après un redémarrage du téléphone et ne pas réexécuter l'application. L'intervalle de déclenchement réel est cependant de 2-3 heures.

Mon exemple d'application est ici

27
Paulw11

Afin d'implémenter la récupération en arrière-plan, vous devez faire trois choses:

  • Cochez la case Récupération en arrière-plan dans les modes d'arrière-plan des capacités de votre application.
  • Utilisez setMinimumBackgroundFetchInterval (_ :) pour définir un intervalle de temps approprié pour votre application.
  • Implémentez l'application (_: performFetchWithCompletionHandler :) dans votre délégué d'application pour gérer la récupération en arrière-plan.

Fréquence de récupération en arrière-plan

La fréquence à laquelle notre application peut effectuer la récupération en arrière-plan est déterminée par le système et dépend de:

  • Si la connectivité réseau est disponible à ce moment particulier
  • Si l'appareil est éveillé, c'est-à-dire en cours d'exécution
  • Combien de temps et de données votre application a consommé lors de la récupération en arrière-plan précédente

En d'autres termes, votre application est entièrement à la merci du système pour planifier une récupération en arrière-plan pour vous. De plus, chaque fois que votre application utilise la récupération en arrière-plan, elle dispose d'au plus 30 secondes pour terminer le processus.

Inclure dans votre AppDelegate (changez le code ci-dessous pour vos besoins de récupération)

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

Pour 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;
}
12
TechSeeko