Mises à jour périodiques de l'emplacement en arrière-plan iOS


91

J'écris une application qui nécessite des mises à jour de l'emplacement en arrière-plan avec une grande précision et une faible fréquence . La solution semble être une tâche NSTimer en arrière-plan qui démarre les mises à jour du gestionnaire d'emplacement, qui s'arrête immédiatement. Cette question a déjà été posée:

Comment obtenir une mise à jour de l'emplacement en arrière-plan toutes les n minutes dans mon application iOS?

Obtenir l'emplacement de l'utilisateur toutes les n minutes après que l'application passe en arrière-plan

iOS Pas le problème typique du minuteur de suivi de l'emplacement en arrière-plan

Minuterie d'arrière-plan iOS de longue durée avec mode d'arrière-plan "emplacement"

Service d'arrière-plan iOS à plein temps basé sur le suivi de l'emplacement

mais je n'ai pas encore fait fonctionner un exemple minimum . Après avoir essayé toutes les permutations des réponses acceptées ci-dessus, j'ai établi un point de départ. Entrer en arrière-plan:

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    self.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        NSLog(@"ending background task");
        [[UIApplication sharedApplication] endBackgroundTask:self.bgTask];
        self.bgTask = UIBackgroundTaskInvalid;
    }];


    self.timer = [NSTimer scheduledTimerWithTimeInterval:60
                                                  target:self.locationManager
                                                selector:@selector(startUpdatingLocation)
                                                userInfo:nil
                                                 repeats:YES];
}

et la méthode des délégués:

- (void)locationManager:(CLLocationManager *)manager 
    didUpdateToLocation:(CLLocation *)newLocation 
           fromLocation:(CLLocation *)oldLocation {

    NSLog(@"%@", newLocation);

    NSLog(@"background time: %f", [UIApplication sharedApplication].backgroundTimeRemaining);
    [self.locationManager stopUpdatingLocation];

}

Le comportement actuel est que les backgroundTimeRemainingdécrémentations de 180 secondes à zéro (lors de la journalisation de l'emplacement), puis le gestionnaire d'expiration s'exécute et aucune autre mise à jour d'emplacement n'est générée. Comment puis-je modifier le code ci-dessus afin de recevoir des mises à jour périodiques de l'emplacement en arrière-plan indéfiniment ?

Mise à jour : je cible iOS 7 et il semble y avoir des preuves que les tâches d'arrière-plan se comportent différemment:

Démarrez Location Manager dans iOS 7 à partir de la tâche en arrière-plan


Vous ne mentionnez pas la version d'iOS que vous ciblez. iOS 7 (par exemple) a un système multitâche différent de celui de son prédécesseur.
Jason Whitehorn du

@JasonWhitehorn Bon point. J'ai mis à jour la question.
pcoving le

2
@pcoving: votre solution fonctionnera-t-elle même si l'application est supprimée ou arrêtée?
Nirav

Réponses:


62

Il semble que stopUpdatingLocationce soit ce qui déclenche la minuterie du chien de garde en arrière-plan, alors je l'ai remplacé didUpdateLocationpar:

[self.locationManager setDesiredAccuracy:kCLLocationAccuracyThreeKilometers];
[self.locationManager setDistanceFilter:99999];

qui semble éteindre efficacement le GPS. Le sélecteur du fond NSTimerdevient alors:

- (void) changeAccuracy {
    [self.locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
    [self.locationManager setDistanceFilter:kCLDistanceFilterNone];
}

Tout ce que je fais est de basculer périodiquement la précision pour obtenir une coordonnée de haute précision toutes les quelques minutes et, comme le locationManagern'a pas été arrêté, backgroundTimeRemainingreste à sa valeur maximale. Cela a réduit la consommation de la batterie d'environ 10% par heure (avec une constante kCLLocationAccuracyBesten arrière-plan) à ~ 2% par heure sur mon appareil.


4
Pourriez-vous mettre à jour votre question avec l'exemple de travail complet? Quelque chose comme: "MISE À JOUR: J'ai compris. Voici ma solution ..."
smholloway

@Virussmca Je suis revenu à une réponse précédente qui est beaucoup plus robuste avec des performances similaires. L'arrêt complet des mises à jour de localisation semble déclencher une condition de concurrence (ou un autre scénario non déterministe).
pcoving

Cette approche ne fonctionnera pas comme prévu: 1) L'augmentation de la valeur de la propriété distanceFilter n'entraîne pas une économie de batterie car le GPS doit toujours déterminer votre emplacement. 2) Dans iOS 7, le temps de fond n'est pas contigu. 3) Vous pouvez prendre en compte des services importantsChangeLocation. 4) En dehors de votre solution dans iOS 7, vous ne pouvez pas démarrer UIBackgroundModes - emplacement à partir de l'arrière-plan ...
aMother

2
@aMother 1) Je me rends compte qu'augmenter le filtre de distance (et ne pas traiter les rappels) n'économise pas beaucoup de batterie. Cependant, setDesiredAccuracy le fait! Il s'appuie sur les informations de la tour de téléphonie cellulaire, qui sont essentiellement gratuites. 2) Non contigu? 3) J'ai précisé, en gras, que j'exige une grande précision. Les changements de localisation importants ne fournissent pas cela. 4) Les services de localisation ne sont pas démarrés / arrêtés en arrière-plan.
pcoving

2
Je sais que c'est un ancien article maintenant, mais les utilisateurs de mon application qui utilisent cette astuce ont remarqué une augmentation significative de l'utilisation de la batterie depuis la mise à niveau vers iOS9. Je n'ai pas encore enquêté, mais j'ai le sentiment que cette «astuce» pourrait ne plus fonctionner?
Gary Wright

45

J'ai écrit une application en utilisant les services de localisation, l'application doit envoyer l'emplacement toutes les 10 secondes. Et cela a très bien fonctionné.

Utilisez simplement la méthode " allowDeferredLocationUpdatesUntilTraveled: timeout ", en suivant la doc d'Apple.

Les étapes sont les suivantes:

Requis: enregistrez le mode d'arrière-plan pour l'emplacement de mise à jour.

1. Créez LocationManger et startUpdatingLocation, avec précision et filteredDistance comme vous le souhaitez:

-(void) initLocationManager    
{
    // Create the manager object
    self.locationManager = [[[CLLocationManager alloc] init] autorelease];
    _locationManager.delegate = self;
    // This is the most important property to set for the manager. It ultimately determines how the manager will
    // attempt to acquire location and thus, the amount of power that will be consumed.
    _locationManager.desiredAccuracy = 45;
    _locationManager.distanceFilter = 100;
    // Once configured, the location manager must be "started".
    [_locationManager startUpdatingLocation];
}

2. Pour que l'application continue de fonctionner pour toujours à l'aide de la méthode "allowDeferredLocationUpdatesUntilTraveled: timeout" en arrière-plan, vous devez redémarrer updateLocation avec un nouveau paramètre lorsque l'application passe en arrière-plan, comme ceci:

- (void)applicationWillResignActive:(UIApplication *)application {
     _isBackgroundMode = YES;

    [_locationManager stopUpdatingLocation];
    [_locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
    [_locationManager setDistanceFilter:kCLDistanceFilterNone];
    _locationManager.pausesLocationUpdatesAutomatically = NO;
    _locationManager.activityType = CLActivityTypeAutomotiveNavigation;
    [_locationManager startUpdatingLocation];
 }

3. L' application est mise à jourLocations normalement avec le rappel "locationManager: didUpdateLocations:":

-(void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
//  store data
    CLLocation *newLocation = [locations lastObject];
    self.userLocation = newLocation;

   //tell the centralManager that you want to deferred this updatedLocation
    if (_isBackgroundMode && !_deferringUpdates)
    {
        _deferringUpdates = YES;
        [self.locationManager allowDeferredLocationUpdatesUntilTraveled:CLLocationDistanceMax timeout:10];
    }
}

4. Mais vous devez gérer les données dans le callback "locationManager: didFinishDeferredUpdatesWithError:" pour vos besoins

- (void) locationManager:(CLLocationManager *)manager didFinishDeferredUpdatesWithError:(NSError *)error {

     _deferringUpdates = NO;

     //do something 
}

5. REMARQUE: je pense que nous devrions réinitialiser les paramètres de LocationManager chaque fois que l'application bascule entre le mode arrière-plan / avant-plan.


Cela fonctionne très bien et je pense que c'est la bonne façon de faire comme mentionné dans la WWDC l'année dernière !! Merci pour le message
prasad1250

@ samthui7 merci, cela fonctionne, mais je dois également obtenir l'emplacement après la résiliation. comment est-ce possible. ??
Mohit Tomar le

@ samthui7 .. s'il vous plaît voir cette que ... stackoverflow.com/questions/37338466/…
Suraj Sukale

2
@amar: J'ai également vérifié la version bêta d'iOS 10, cela fonctionne toujours. Notez que nous devons ajouter du code avant startUpdatingLocation: allowsBackgroundLocationUpdates = YESet requestAlwaysAuthorizationou requestWhenInUseAuthorization. Par exemple: `CLLocationManager * locationManager = [[CLLocationManager alloc] init]; locationManager.delegate = soi; locationManager.allowsBackgroundLocationUpdates = OUI; [locationManager requestAlwaysAuthorization]; [locationManager startUpdatingLocation]; `
samthui7

1
@Nirav Non. Je ne pense pas qu'Apple pourrait nous laisser faire ça, pour autant que je sache.
samthui7

38
  1. Si vous avez le UIBackgroundModesdans votre plist avec locationclé, vous n'avez pas besoin d'utiliser la beginBackgroundTaskWithExpirationHandlerméthode. C'est redondant. De plus, vous ne l'utilisez pas correctement (voir ici ), mais c'est sans intérêt puisque votre plist est défini.

  2. Avec UIBackgroundModes locationdans le plist, l'application continuera à s'exécuter en arrière-plan indéfiniment tant qu'elle CLLocationMangerest en cours d'exécution. Si vous appelez stopUpdatingLocationen arrière-plan, l'application s'arrêtera et ne redémarrera pas.

    Vous pourriez peut-être appeler beginBackgroundTaskWithExpirationHandlerjuste avant d'appeler stopUpdatingLocation, puis après avoir appelé, startUpdatingLocationvous pouvez appeler le endBackgroundTaskpour le garder en arrière-plan pendant que le GPS est arrêté, mais je n'ai jamais essayé cela - c'est juste une idée.

    Une autre option (que je n'ai pas essayée) est de faire fonctionner le gestionnaire de localisation en arrière-plan, mais une fois que vous obtenez un emplacement précis, changez la desiredAccuracypropriété à 1000 m ou plus pour permettre à la puce GPS de s'éteindre (pour économiser la batterie). Puis 10 minutes plus tard, lorsque vous avez besoin d'une autre mise à jour de l'emplacement, changez le desiredAccuracydos à 100m pour allumer le GPS jusqu'à ce que vous obteniez un emplacement précis, répétez.

  3. Lorsque vous appelez startUpdatingLocationle gestionnaire de l'emplacement, vous devez lui donner le temps d'obtenir un poste. Vous ne devez pas appeler immédiatement stopUpdatingLocation. Nous le laissons fonctionner pendant un maximum de 10 secondes ou jusqu'à ce que nous obtenions un emplacement de haute précision non mis en cache.

  4. Vous devez filtrer les emplacements mis en cache et vérifier l'exactitude de l'emplacement que vous obtenez pour vous assurer qu'il répond à votre précision minimale requise (voir ici ). La première mise à jour que vous recevez peut dater de 10 minutes ou 10 jours. La première précision que vous obtenez peut être de 3000 m.

  5. Pensez plutôt à utiliser les API de changement d'emplacement importantes. Une fois que vous avez reçu la notification de changement significatif, vous pouvez commencer CLLocationManagerpendant quelques secondes pour obtenir une position de haute précision. Je ne suis pas certain, je n'ai jamais utilisé les services de changement de lieu importants.


La CoreLocationréférence Apple semble recommander l'utilisation de beginBackgroundTaskWithExpirationHandlerlors du traitement des données de localisation en arrière-plan: "Si une application iOS a besoin de plus de temps pour traiter les données de localisation, elle peut demander plus de temps d'exécution en arrière-plan en utilisant la méthode beginBackgroundTaskWithName: expirationHandler: de la classe UIApplication." - developer.apple.com/library/ios/documentation/UserExperience/…
eliocs

4
n'oubliez pas d'ajouter _locationManager.allowsBackgroundLocationUpdates = YES; pour iOS9 +, sinon l'application se mettra en veille après 180 sec
kolyancz

@kolyancz Je viens de l'essayer sur iOS 10.1.1 pour iPhone 6+. Il a fallu environ 17 minutes pour qu'il s'endorme et se déclenche locationManagerDidPauseLocationUpdates. J'ai vérifié ce comportement 10 fois!
Honey

Je ne comprends vraiment pas pourquoi vous dites que c'est sans objet et ensuite vous dites que vous devriez l'envelopper dans un arrière-plan Tâche ...
Chérie

10

Lorsqu'il est temps de démarrer le service de localisation et d'arrêter la tâche d'arrière-plan, la tâche d'arrière-plan doit être arrêtée avec un délai (1 seconde devrait suffire). Sinon, le service de localisation ne démarrera pas. Le service de localisation doit également rester activé pendant quelques secondes (par exemple, 3 secondes).

Il existe un APScheduledLocationManager cocoapod qui permet d'obtenir des mises à jour de localisation en arrière-plan toutes les n secondes avec la précision de localisation souhaitée.

let manager = APScheduledLocationManager(delegate: self)
manager.startUpdatingLocation(interval: 170, acceptableLocationAccuracy: 100)

Le référentiel contient également un exemple d'application écrite en Swift 3.


1

Pourquoi ne pas essayer avec l' startMonitoringSignificantLocationChanges:API? Il se déclenche définitivement avec moins de fréquence et la précision est raisonnablement bonne. De plus, il présente beaucoup plus d'avantages que l'utilisation d'autres locationManagerAPI.

Beaucoup plus concernant cette API ont déjà été discutés sur ce lien


1
J'ai essayé cette approche. Il viole l'exigence de précision - de la documentation : This interface delivers new events only when it detects changes to the device’s associated cell towers
poving

1

Vous devez ajouter le mode de mise à jour de l'emplacement dans votre application en ajoutant la clé suivante dans votre info.plist.

<key>UIBackgroundModes</key>
<array>
    <string>location</string>
</array>

didUpdateToLocationsera appelée (même lorsque votre application est en arrière-plan). Vous pouvez effectuer n'importe quoi sur les bases de cet appel de méthode


Le mode de mise à jour de l'emplacement est déjà dans le fichier info.plist. Comme je l'ai mentionné, je reçois des mises à jour jusqu'au délai d'attente en arrière-plan de 180 secondes.
pcoving le

0

Après iOS 8, il y a plusieurs changements dans la récupération en arrière-plan du framework CoreLocation et les mises à jour effectuées par Apple.

1) Apple introduit la demande AlwaysAuthorization pour récupérer la mise à jour de l'emplacement dans l'application.

2) Apple introduit backgroundLocationupdates pour récupérer l'emplacement à partir de l'arrière-plan dans iOS.

Pour récupérer l'emplacement en arrière-plan, vous devez activer la mise à jour de l'emplacement dans Capabilities of Xcode.

//
//  ViewController.swift
//  DemoStackLocation
//
//  Created by iOS Test User on 02/01/18.
//  Copyright © 2018 iOS Test User. All rights reserved.
//

import UIKit
import CoreLocation

class ViewController: UIViewController {

    var locationManager: CLLocationManager = CLLocationManager()
    override func viewDidLoad() {
        super.viewDidLoad()
        startLocationManager()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    internal func startLocationManager(){
        locationManager.requestAlwaysAuthorization()
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.delegate = self
        locationManager.allowsBackgroundLocationUpdates = true
        locationManager.startUpdatingLocation()
    }

}

extension ViewController : CLLocationManagerDelegate {

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
        let location = locations[0] as CLLocation
        print(location.coordinate.latitude) // prints user's latitude
        print(location.coordinate.longitude) //will print user's longitude
        print(location.speed) //will print user's speed
    }

}

0

Pour utiliser les services de localisation standard lorsque l'application est en arrière-plan, vous devez d'abord activer les modes d'arrière-plan dans l'onglet Capacités des paramètres Cible, puis sélectionner Mises à jour de l'emplacement.

Ou ajoutez-le directement à Info.plist .

<key>NSLocationAlwaysUsageDescription</key>
 <string>I want to get your location Information in background</string>
<key>UIBackgroundModes</key>
 <array><string>location</string> </array>

Ensuite, vous devez configurer le CLLocationManager

Objectif c

//The Location Manager must have a strong reference to it.
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
//Request Always authorization (iOS8+)
if ([_locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) { [_locationManager requestAlwaysAuthorization];
}
//Allow location updates in the background (iOS9+)
if ([_locationManager respondsToSelector:@selector(allowsBackgroundLocationUpdates)]) { _locationManager.allowsBackgroundLocationUpdates = YES;
}
[_locationManager startUpdatingLocation];

Rapide

self.locationManager.delegate = self
if #available (iOS 8.0,*) {
    self.locationManager.requestAlwaysAuthorization()
}
if #available (iOS 9.0,*) {
    self.locationManager.allowsBackgroundLocationUpdates = true
}
self.locationManager.startUpdatingLocation()

Réf: https://medium.com/@javedmultani16/location-service-in-the-background-ios-942c89cd99ba


-2
locationManager.allowsBackgroundLocationUpdates = true

4
Pourriez-vous modifier votre réponse et ajouter une explication pourquoi / comment cela résout le problème?
barbsan
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.