66 votes

iPhone - la semi-finition à l'interrogation des événements

Pendant très longtemps, j'avais été à la recherche dans un dans mon application iPhone pour interroger toutes les X minutes pour vérifier les compteurs de données. Après beaucoup de lecture de l'Exécution en arrière-plan de la documentation et quelques procès des applications que j'avais rejeté cela comme impossible sans abuser de l'arrière-plan des Api.

La semaine dernière j'ai trouvé cette application qui fait exactement cela. http://itunes.apple.com/us/app/dataman-real-time-data-usage/id393282873?mt=8

Il fonctionne dans le fond et assure le suivi de la numération Cellulaire/WiFi les données que vous avez utilisées. Je soupçonne que le développeur est l'enregistrement de son application, au suivi des modifications de l'emplacement, mais l'emplacement des services de l'icône n'est pas visible, tandis que l'application est en cours d'exécution, j'ai pensé que c'était une exigence.

Quelqu'un aurait-il des indices quant à la façon dont ceci peut être accompli?

92voto

JackPearse Points 1795

J'ai vu ce comportement, trop. Après avoir essayé beaucoup, j'ai découvert deux choses, ce qui pourrait l'aider. Mais je ne suis toujours pas sûr de savoir comment cela peut influencer le processus de révision.

Si vous utilisez l'un de la semi-finition des fonctionnalités, l'application sera lancée par iOS en arrière-plan encore une fois, c'était quitter (par le système). Cela nous permettra d'abus.

Dans mon cas, j'ai utilisé la VoIP, la semi-finition activé dans mon plist. Tout le code ici est fait dans votre AppDelegate:

// if the iOS device allows background execution,
// this Handler will be called
- (void)backgroundHandler {

    NSLog(@"### -->VOIP backgrounding callback");
    // try to do sth. According to Apple we have ONLY 30 seconds to perform this Task!
    // Else the Application will be terminated!
    UIApplication* app = [UIApplication sharedApplication];
    NSArray*    oldNotifications = [app scheduledLocalNotifications];

     // Clear out the old notification before scheduling a new one.
    if ([oldNotifications count] > 0) [app cancelAllLocalNotifications];

    // Create a new notification
    UILocalNotification* alarm = [[[UILocalNotification alloc] init] autorelease];
    if (alarm)
    {
        alarm.fireDate = [NSDate date];
        alarm.timeZone = [NSTimeZone defaultTimeZone];
        alarm.repeatInterval = 0;
        alarm.soundName = @"alarmsound.caf";
        alarm.alertBody = @"Don't Panic! This is just a Push-Notification Test.";

        [app scheduleLocalNotification:alarm];
    }
}

et que l'enregistrement est fait dans

- (void)applicationDidEnterBackground:(UIApplication *)application {

    // This is where you can do your X Minutes, if >= 10Minutes is okay.
    BOOL backgroundAccepted = [[UIApplication sharedApplication] setKeepAliveTimeout:600 handler:^{ [self backgroundHandler]; }];
    if (backgroundAccepted)
    {
        NSLog(@"VOIP backgrounding accepted");
    }
}

Maintenant que la magie se produit: je n'ai pas encore utiliser la VoIP-Sockets. Mais ces 10 Minutes de rappel fournit un bel effet: au bout de 10 Minutes (parfois plus tôt), j'ai découvert que mon minuteries et de fonctionnement des bandes de roulement en cours d'exécution pendant un court instant. Vous pouvez le voir, si vous placez certains NSLog(..) dans votre code. Cela signifie, que ce petit "réveil" exécute le code pour un certain temps. Selon Apple, nous avons 30 secondes de temps d'exécution à gauche. Je suppose, que l'arrière-plan d'un code comme les threads en cours d'exécution pendant près de 30 secondes. Ceci est utile de code, si vous devez "parfois" vérifier quelque chose.

Le doc a dit que toutes les tâches en arrière-plan (VoIP, de l'audio, de l'emplacement des mises à jour) sera redémarré automatiquement en arrière-plan si l'application a été résilié. VoIP applications sera lancé en arrière-plan automatiquement après le démarrage!

D'abuser de ce comportement, vous pouvez rendre votre application d'être à la recherche, comme la course "pour toujours". Inscrivez-vous à un processus d'arrière-plan (c'est à dire VoIP). Ce sera la cause de votre application d'être redémarré après la résiliation.

Maintenant écrire un peu "Tâche doit être terminée" du code. Selon Apple, vous avez un peu de temps (5 secondes?) de gauche à terminer les tâches. J'ai découvert, que ce doit être le temps CPU. Cela signifie: si vous ne faites rien, votre application est toujours d'être exécuté! Apple suggèrent de faire appel à une expirationhandler, si vous avez fini votre travail. Dans le code ci-dessous, vous pouvez voir que j'ai un commentaire à la expirationHandler. Ce sera la cause de votre application qui s'exécute tant que le système permet à votre application d'être en cours d'exécution. Tous les compteurs à zéro et fils reste en cours d'exécution jusqu'à ce que iOS met fin à votre application.

- (void)applicationDidEnterBackground:(UIApplication *)application {

    UIApplication*    app = [UIApplication sharedApplication];

    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];


    // Start the long-running task and return immediately.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // you can do sth. here, or simply do nothing!
    // All your background treads and timers are still being executed
    while (background) 
       [self doSomething];
       // This is where you can do your "X minutes" in seconds (here 10)
       sleep(10);
    }

    // And never call the expirationHandler, so your App runs
    // until the system terminates our process
    //[app endBackgroundTask:bgTask];
    //bgTask = UIBackgroundTaskInvalid;

    }); 
}

Être très rechange avec CPU-Temps ici, et que votre application fonctionne plus! Mais une chose est sûre: votre application sera résilié après un certain temps. Mais parce que vous avez enregistré votre application comme la VoIP ou l'une de l'autre, le système redémarre l'application en arrière-plan, ce qui permet de redémarrer votre processus d'arrière-plan ;-) Avec ce ping-pong que je peux faire beaucoup de semi-finition. mais rappelez-vous d'être très rechange avec le temps CPU. Et d'enregistrer toutes les données, afin de restaurer vos points de vue - votre application va être arrêté quelque temps plus tard. Pour la faire apparaître toujours en cours d'exécution, vous devez revenir en arrière dans votre dernier "état" après le réveil.

Je ne sais pas si c'est l'approche des apps que vous avez mentionné avant, mais cela fonctionne pour moi.

J'espère que je pourrais aider

Mise à jour:

Après la mesure, le temps de la tâche BG, il y avait une surprise. Le BG Tâche est limitée à 600 secondes. C'est exactement le temps minimum de la VoIP minimumtime (setKeepAliveTimeout:600).

Donc CE code mène à "l'infini" exécution en arrière-plan:

En-tête:

UIBackgroundTaskIdentifier bgTask; 

Code:

// if the iOS device allows background execution,
// this Handler will be called
- (void)backgroundHandler {

    NSLog(@"### -->VOIP backgrounding callback");

    UIApplication*    app = [UIApplication sharedApplication];

    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];

    // Start the long-running task 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    while (1) {
        NSLog(@"BGTime left: %f", [UIApplication sharedApplication].backgroundTimeRemaining);
           [self doSomething];
        sleep(1);
    }   
});     

- (void)applicationDidEnterBackground:(UIApplication *)application {

    BOOL backgroundAccepted = [[UIApplication sharedApplication] setKeepAliveTimeout:600 handler:^{ [self backgroundHandler]; }];
    if (backgroundAccepted)
    {
        NSLog(@"VOIP backgrounding accepted");
    }

    UIApplication*    app = [UIApplication sharedApplication];

    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];


    // Start the long-running task
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        while (1) {
            NSLog(@"BGTime left: %f", [UIApplication sharedApplication].backgroundTimeRemaining);
           [self doSomething];
           sleep(1);
        }    
    }); 
}

Une fois que votre application a expiré, la VoIP expirationHandler sera appelée, où il vous suffit de redémarrer une longue tâche en cours d'exécution. Cette tâche sera terminée après 600 secondes. Mais il y aura de nouveau un appel à l'expiration d'un délai de gestionnaire, qui commence une longue tâche en cours d'exécution, etc. Maintenant, vous n'avez qu'à vérifier la météo de l'Application est de revenir au premier plan. Puis fermez le bgTask, et vous avez terminé. Peut-être on peut faire qqch. comme ça à l'intérieur de la expirationHandler de la longue tâche en cours d'exécution. Il suffit de l'essayer. Utilisez votre Console, pour voir ce qui se passe... amusez-vous!

Mise à jour 2:

Parfois, permet de simplifier les choses. Ma nouvelle approche est celle-ci:

- (void)applicationDidEnterBackground:(UIApplication *)application {

    UIApplication*    app = [UIApplication sharedApplication];

    // it's better to move "dispatch_block_t expirationHandler"
    // into your headerfile and initialize the code somewhere else
    // i.e. 
    // - (void)applicationDidFinishLaunching:(UIApplication *)application {
//
// expirationHandler = ^{ ... } }
    // because your app may crash if you initialize expirationHandler twice.
    dispatch_block_t expirationHandler;
    expirationHandler = ^{

        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;


        bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];
    };

    bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];


    // Start the long-running task and return immediately.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // inform others to stop tasks, if you like
        [[NSNotificationCenter defaultCenter] postNotificationName:@"MyApplicationEntersBackground" object:self];

        // do your background work here     
    }); 
}

Ce travail est sans la VoIP hack. Selon la documentation, à l'expiration d'un délai de gestionnaire (dans ce cas, mon 'expirationHandler" bloquer) sera exécutée si le temps d'exécution est plus. En définissant le bloc dans un bloc de variable, on peut de manière récursive commencer le long de la tâche en cours d'exécution dans le gestionnaire d'expiration. Cela conduit dans l'infini de l'exécution, aussi.

Sachez pour terminer la tâche, si votre application passe en premier plan à nouveau. Et de mettre fin à la tâche si vous en avez le plus besoin.

Pour ma propre expérience, j'ai mesuré à quelque chose. L'aide de l'emplacement des rappels d'avoir le GPS, la radio est en train de sucer ma batterie descend très rapidement. L'utilisation de l'approche que j'ai posté dans la mise à Jour 2 prend presque pas d'énergie. Selon le "userexperience" c'est une meilleure approche. Peut-être que d'autres Applications de ce genre, cachant son comportement derrière la fonctionnalité GPS ...

17voto

bendytree Points 2684

Ce qui Fonctionne Et Ce qui Ne fonctionne pas

Il n'est pas entièrement clair, laquelle de ces réponses de travail et j'ai perdu beaucoup de temps à essayer tous. Voici donc mon expérience avec chaque stratégie:

  1. VOIP hack fonctionne, mais les va vous obtenir rejetée, si vous n'êtes pas une application VOIP
  2. Récursive beginBackgroundTask... - ne fonctionne pas. Il quittera au bout de 10 minutes. Même si vous essayez de les correctifs dans les commentaires (au moins les commentaires jusqu'au 30 Novembre 2012).
  3. Audio silencieux - fonctionne, mais les gens ont été rejetés pour cette
  4. Local/Notifications Push - exiger l'interaction de l'utilisateur avant que votre application sera réveillé
  5. À l'aide d'arrière-plan de Localisation des œuvres. Voici les détails:

Fondamentalement, vous utilisez la "location" mode d'arrière-plan pour garder votre application en cours d'exécution en arrière-plan. Il fonctionne, même si l'utilisateur ne permettent pas l'emplacement des mises à jour. Même si l'utilisateur appuie sur le bouton home et lance une autre application, votre application sera toujours en cours d'exécution. C'est aussi une batterie égouttoir et peut-être exagéré dans le processus d'approbation si votre application n'a rien à voir avec l'emplacement, mais pour autant que je sais que c'est la seule solution qui a une bonne chance d'être approuvé.

Voici comment il fonctionne:

Dans votre plist ensemble:

  • L'Application ne s'exécute pas en arrière-plan: NON
  • Contexte modes: emplacement

Alors faire référence au framework CoreLocation (dans les Phases de construction) et ajouter ce code quelque part dans votre application (avant qu'il ne passe à l'arrière-plan):

#import <CoreLocation/CoreLocation.h>

CLLocationManager* locationManager = [[CLLocationManager alloc] init];
[locationManager startUpdatingLocation];

Remarque: startMonitoringSignificantLocationChanges aura pas de travail.

Il est également intéressant de mentionner que si votre application se bloque, puis iOS va pas le ramener à la vie. La VOIP hack est le seul qui peut le ramener.

6voto

Kashif Shaikh Points 61

Il existe une autre technique pour rester à jamais dans l'arrière-plan - démarrage/arrêt de l'emplacement de manager dans votre tâche en arrière-plan, permettra de réinitialiser le fond de la minuterie de didUpdateToLocation: est appelé.

Je ne sais pas pourquoi cela fonctionne, mais je pense que didUpdateToLocation est également appelé comme un devoir, ce qui permet de réinitialiser le compteur.

Basé sur des tests, je crois que c'est ce que DataMan Pro.

Voir ce post http://stackoverflow.com/a/6465280 où je me suis tromper.

Voici quelques résultats de notre application:

2012-02-06 15:21:01.520 **[1166:4027] BGTime left: 598.614497 
2012-02-06 15:21:02.897 **[1166:4027] BGTime left: 597.237567 
2012-02-06 15:21:04.106 **[1166:4027] BGTime left: 596.028215 
2012-02-06 15:21:05.306 **[1166:4027] BGTime left: 594.828474 
2012-02-06 15:21:06.515 **[1166:4027] BGTime left: 593.619191
2012-02-06 15:21:07.739 **[1166:4027] BGTime left: 592.395392 
2012-02-06 15:21:08.941 **[1166:4027] BGTime left: 591.193865 
2012-02-06 15:21:10.134 **[1166:4027] BGTime left: 590.001071
2012-02-06 15:21:11.339 **[1166:4027] BGTime left: 588.795573
2012-02-06 15:21:11.351 **[1166:707] startUpdatingLocation
2012-02-06 15:21:11.543 **[1166:707] didUpdateToLocation
2012-02-06 15:21:11.623 **[1166:707] stopUpdatingLocation
2012-02-06 15:21:13.050 **[1166:4027] BGTime left: 599.701993
2012-02-06 15:21:14.286 **[1166:4027] BGTime left: 598.465553

1voto

iOSTester Points 81

J'ai essayé la mise à Jour 2, mais il ne fonctionne tout simplement pas. Lors de l'expiration gestionnaire est appelé, il termine la tâche en arrière-plan. Ensuite, à partir d'une nouvelle tâche de fond, juste des forces d'un appel immédiat à l'expiration d'un délai de gestionnaire de nouveau (la minuterie n'est pas réinitialisé et est encore expiré). Donc j'ai 43 démarre/arrête de tâches de fond avant l'application a été suspendue.

1voto

Stephen Darlington Points 33587

Si ce n'est pas de GPS, je pense que la seule autre façon de faire est la fonction de musique de fond, c'est à dire, jouer 4"33" toutes les fois qu'il est activé. Comme un peu d'un abus de traitement en arrière-plan Api et donc potentiellement soumis aux caprices du processus d'examen.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X