iPhone – Contexte pour interroger des événements

Pendant un bon moment, je cherchais dans mon application iPhone à interroger toutes les X minutes pour vérifier les compteurs de données. Après avoir beaucoup lu la documentation sur l’exécution en arrière-plan et quelques applications d’essai, j’ai rejeté cette proposition comme impossible sans abuser des API d’arrière-plan.

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 en arrière-plan et assure le suivi du nombre de données cellulaires / WiFi que vous avez utilisées. Je soupçonne que le développeur enregistre son application en tant que suivi de l’emplacement, mais l’icône des services de localisation n’est pas visible lorsque l’application est en cours d’exécution.

Quelqu’un at-il des indices sur la façon dont cela peut être accompli?

J’ai aussi vu ce comportement. Après avoir essayé beaucoup, j’ai découvert deux choses qui pourraient aider. Mais je ne sais toujours pas comment cela peut influencer le processus de révision.

Si vous utilisez l’une des fonctionnalités d’arrière-plan, l’application sera à nouveau lancée par iOS en arrière-plan une fois qu’elle aura été fermée (par le système). Ceci nous abusera plus tard.

Dans mon cas, j’ai utilisé le background VoIP 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 l’inscription est faite en

 - (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, la magie se produit: je n’utilise même pas les sockets VoIP. Mais ce rappel de 10 minutes a un effet secondaire intéressant: après 10 minutes (parfois plus tôt), j’ai découvert que mes timers et les bandes de roulement précédentes étaient en cours d’exécution pendant un court instant. Vous pouvez le voir si vous placez du NSLog (..) dans votre code. Cela signifie que ce bref “réveil” exécute le code pendant un moment. Selon Apple, il nous rest 30 secondes d’exécution. Je suppose que ce code de fond comme les threads est exécuté pendant près de 30 secondes. C’est un code utile, si vous devez parfois “vérifier” quelque chose.

Le document indique que toutes les tâches en arrière-plan (VoIP, audio, mises à jour de l’emplacement) seront automatiquement redémarrées en arrière-plan si l’application était terminée. Les applications VoIP seront lancées en arrière-plan automatiquement après le démarrage!

En abusant de ce comportement, vous pouvez faire en sorte que votre application ait l’air de fonctionner “pour toujours”. Inscrivez-vous pour un processus d’arrière-plan (par exemple, VoIP). Cela entraînera le redémarrage de votre application après la résiliation.

Maintenant, écrivez du code “La tâche doit être terminée”. Selon Apple, il vous rest du temps (5 secondes?) Pour terminer les tâches. J’ai découvert que cela devait être du temps CPU. Cela signifie donc que si vous ne faites rien, votre application est toujours en cours d’exécution! Apple suggère d’appeler un expirationhandler, si vous avez terminé votre travail. Dans le code ci-dessous, vous pouvez voir que j’ai un commentaire à l’expirationHandler. Cela entraînera l’exécution de votre application tant que le système permettra à votre application de s’exécuter. Toutes les timers et tous les threads restnt actifs jusqu’à ce que iOS termine 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; }); } 

Soyez très libre avec le temps CPU ici, et votre application fonctionne plus longtemps! Mais une chose est sûre: votre application sera fermée au bout d’un moment. Mais parce que vous avez enregistré votre application en tant que VoIP ou l’un des autres, le système redémarre l’application en arrière-plan, ce qui redémarrera votre processus d’arrière-plan 😉 Avec ce PingPong, je peux faire beaucoup de mise à jour. mais rappelez-vous être très libre avec le temps CPU. Et enregistrez toutes les données pour restaurer vos vues – votre application sera terminée quelque temps plus tard. Pour le faire paraître en cours d’exécution, vous devez revenir à votre dernier “état” après le réveil.

Je ne sais pas si c’est l’approche des applications que vous avez mentionnées auparavant, mais cela fonctionne pour moi.

J’espère que je pourrais aider

Mettre à jour:

Après avoir mesuré le temps de la tâche BG, il y a eu une surprise. La tâche BG est limitée à 600 secondes. C’est l’heure minimum exacte de la durée minimum VoIP (setKeepAliveTimeout: 600).

Donc, ce code conduit à une exécution “infinie” en arrière-plan:

Entê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é, le paramètre expirationHandler de VoIP sera appelé et vous pourrez simplement redémarrer une tâche longue. Cette tâche sera terminée après 600 secondes. Mais il y aura à nouveau un appel au gestionnaire d’expiration, qui démarrera une autre tâche longue, etc. Maintenant, il ne vous rest plus qu’à vérifier la météo de retour de l’application au premier plan. Fermez ensuite le bgTask et vous avez terminé. Peut-être on peut faire sth. comme ceci à l’intérieur du gestionnaire d’expiration de la longue tâche en cours. Essayez-le. Utilisez votre console pour voir ce qui se passe … Amusez-vous!

Mise à jour 2:

Parfois, simplifier les choses aide. 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 // ie // - (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 }); } 

Cela fonctionne sans le piratage VoIP. Selon la documentation, le gestionnaire d’expiration (dans ce cas, mon bloc ‘expirationHandler’) sera exécuté si le temps d’exécution est terminé. En définissant le bloc dans une variable de bloc, on peut recommencer récursivement la longue tâche d’exécution dans le gestionnaire d’expiration. Cela conduit à une exécution sans fin aussi.

Soyez conscient de terminer la tâche, si votre application entre à nouveau au premier plan. Et terminez la tâche si vous n’en avez plus besoin.

Pour ma propre expérience, j’ai mesuré quelque chose. Utiliser les rappels de localisation avec la radio GPS activée aspire très rapidement ma batterie. L’utilisation de l’approche que j’ai publiée dans la mise à jour 2 ne nécessite presque pas d’énergie. Selon l’expérience de l’utilisateur, il s’agit d’une meilleure approche. Peut-être que d’autres applications fonctionnent comme ça, cachant son comportement derrière la fonctionnalité GPS …

Ce qui fonctionne et ce qui ne fonctionne pas

Il est difficile de savoir laquelle de ces réponses fonctionne et j’ai perdu beaucoup de temps à les essayer toutes. Voici donc mon expérience avec chaque stratégie:

  1. VOIP hack – fonctionne, mais vous serez rejeté si vous n’êtes pas une application VOIP
  2. Récursive beginBackgroundTask... – ne fonctionne pas. Il quittera après 10 minutes. Même si vous essayez les corrections dans les commentaires (au moins les commentaires jusqu’au 30 novembre 2012).
  3. Silent Audio – fonctionne, mais les gens ont été rejetés pour cela
  4. Notifications locales / Push – nécessite une interaction de l’utilisateur avant que votre application ne soit réveillée
  5. Utilisation de l’emplacement d’arrière-plan – fonctionne . Voici les détails:

Fondamentalement, vous utilisez le mode d’arrière-plan “emplacement” pour maintenir votre application en cours d’exécution en arrière-plan. Cela fonctionne, même si l’utilisateur n’autorise pas les mises à jour d’emplacement. Même si l’utilisateur appuie sur le bouton d’accueil et lance une autre application, votre application sera toujours en cours d’exécution. Il s’agit également d’un égouttoir de batterie et peut être un peu compliqué dans le processus d’approbation si votre application n’a rien à voir avec l’emplacement, mais pour autant que je sache, c’est la seule solution qui a de bonnes chances d’être approuvée.

Voici comment cela fonctionne:

Dans votre set plist:

  • L’application ne s’exécute pas en arrière-plan: NON
  • Modes d’arrière-plan requirejs: emplacement

Ensuite, référencez le framework CoreLocation (dans les phases de construction) et ajoutez ce code quelque part dans votre application (avant qu’il ne soit en arrière-plan):

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

Remarque: startMonitoringSignificantLocationChanges ne fonctionnera pas .

Il convient également de mentionner que si votre application se bloque, iOS ne la ramènera pas à la vie. Le hack VOIP est le seul qui peut le ramener.

Il existe une autre technique pour restr en permanence en arrière-plan: le démarrage / arrêt du gestionnaire d’emplacement dans votre tâche en arrière-plan réinitialisera le temporisateur d’arrière-plan lorsque didUpdateToLocation: est appelé.

Je ne sais pas pourquoi cela fonctionne, mais je pense que didUpdateToLocation est également appelé comme une tâche et réinitialise ainsi le minuteur.

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

Voir ce post https://stackoverflow.com/a/6465280 d’ où je viens.

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 

J’ai essayé la mise à jour 2 mais ça ne marche pas. Lorsque le gestionnaire d’expiration est appelé, il termine la tâche en arrière-plan. Ensuite, le lancement d’une nouvelle tâche d’arrière-plan force à nouveau un appel immédiat au gestionnaire d’expiration (le temporisateur n’est pas réinitialisé et est toujours expiré). Ainsi, j’ai eu 43 démarrages / arrêts de tâches en arrière-plan avant la suspension de l’application.

Si ce n’est pas le GPS, je pense que la seule autre façon de le faire est la fonction de musique de fond, c’est-à-dire jouer 4 “33” tout le temps qu’elle est activée. Les deux semblent un peu abuser des API de traitement en arrière-plan et sont donc potentiellement soumis aux caprices du processus de révision.

Dans mes tests sur iOS5, j’ai trouvé que la surveillance de CoreLocation pouvait être lancée via startMonitoringForLocationChangeEvents (pas SignificantLocationChange), la précision n’a pas d’importance et même sur iPod si je le fais – backgroundTimeRemaining is ne descend jamais.