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

207

Je cherche un moyen d'obtenir une mise à jour de l'emplacement en arrière-plan toutes les n minutes dans mon application iOS. J'utilise iOS 4.3 et la solution devrait fonctionner pour les iPhones non jailbreakés.

J'ai essayé / envisagé les options suivantes:

  • CLLocationManager startUpdatingLocation/startMonitoringSignificantLocationChanges: Cela fonctionne en arrière-plan comme prévu, en fonction des propriétés configurées, mais il ne semble pas possible de le forcer à mettre à jour l'emplacement toutes les n minutes
  • NSTimer: Fonctionne lorsque l'application s'exécute au premier plan, mais ne semble pas être conçue pour les tâches en arrière-plan
  • Notifications locales: les notifications locales peuvent être planifiées toutes les n minutes, mais il n'est pas possible d'exécuter du code pour obtenir l'emplacement actuel (sans que l'utilisateur n'ait à lancer l'application via la notification). Cette approche ne semble pas non plus être une approche propre, car ce n'est pas pour cela que les notifications doivent être utilisées.
  • UIApplication:beginBackgroundTaskWithExpirationHandler: D'après ce que je comprends, cela devrait être utilisé pour terminer certains travaux en arrière-plan (également limités dans le temps) lorsqu'une application est déplacée en arrière-plan plutôt que de mettre en œuvre des processus d'arrière-plan "de longue durée".

Comment puis-je implémenter ces mises à jour régulières de l'emplacement en arrière-plan?

wjans
la source
doublon possible pour exécuter l'application en continu en arrière
mechanical_meat
1
Si vous essayez de le faire fonctionner sur iOS 7, vous pouvez essayer cette solution ici: stackoverflow.com/questions/18946881/… Si vous avez des questions, vous êtes invités
Ricky
1
Toutes vos conclusions sont correctes (les quatre puces). Des informations précieuses qui sont alors, sachant ce qui ne correspond pas à votre cas d'utilisation? Et oui, en MODE SUSPENDU ou NON EN COURS, il n'y a pas de méthode ultime pour la mise à jour toutes les n minutes.
LenArt

Réponses:

113

J'ai trouvé une solution pour l'implémenter à l'aide des forums des développeurs Apple:

  • Spécifier location background mode
  • Créez un NSTimeren arrière-plan avecUIApplication:beginBackgroundTaskWithExpirationHandler:
  • Quand nest plus petit que UIApplication:backgroundTimeRemainingcela fonctionnera très bien. Lorsque nest plus grand , le location managerdoit être réactivé (et désactivé) avant qu'il ne reste plus de temps pour éviter que la tâche d'arrière-plan ne soit supprimée.

Cela fonctionne car l'emplacement est l'un des trois types d'exécution en arrière-plan autorisés .

Remarque: j'ai perdu du temps en testant cela dans le simulateur où cela ne fonctionne pas. Cependant, cela fonctionne bien sur mon téléphone.

wjans
la source
24
avez-vous justement le lien vers le forum. Je cherche à implémenter le même type de mappage d'emplacement et je n'ai pas pu le faire fonctionner. Ou un exemple de code serait grandement apprécié.
utahwithak
4
Pouvez-vous expliquer pourquoi la tâche d'arrière-plan n'est pas supprimée après 10 minutes (durée maximale autorisée) si vous arrêtez et démarrez le gestionnaire de localisation? est-ce une sorte de fonctionnalité prévue? cela ressemble plus à un bogue dans le SDK Apple si cela se produit comme ça. Sur quelle version iOS vous l'avez essayé?
saurabh
5
@all: Oui, notre application est disponible dans l'AppStore. Je ne publierai pas tout le code, tous les points mentionnés sont des fonctionnalités clairement documentées. Si vous rencontrez un problème spécifique, postez votre propre question expliquant ce que vous avez essayé et ce qui ne va pas.
wjans
3
@ user836026: Oui, c'est ce que je veux dire en spécifiant le mode d'arrière-plan. Après avoir arrêté la mise à jour de l'emplacement, elle doit être redémarrée dans les 10 minutes pour éviter la fermeture de l'application.
wjans
12
Pour tous ceux qui souhaitent voir du code réel reflétant ce qui est discuté ici, consultez stackoverflow.com/questions/10235203/…
Lolo
55

Sur iOS 8/9/10 pour effectuer la mise à jour de l'emplacement en arrière-plan toutes les 5 minutes, procédez comme suit:

  1. Allez dans Project -> Capabilities -> Background Modes -> select Location updates

  2. Allez dans Projet -> Info -> ajoutez une clé NSLocationAlwaysUsageDescription avec une valeur vide (ou éventuellement n'importe quel texte)

  3. Pour faire fonctionner l'emplacement lorsque votre application est en arrière-plan et envoyer des coordonnées au service Web ou faire quoi que ce soit avec elles toutes les 5 minutes, implémentez-le comme dans le code ci-dessous.

Je n'utilise aucune tâche d'arrière-plan ni aucun minuteur. J'ai testé ce code avec mon appareil avec iOS 8.1 qui était allongé sur mon bureau pendant quelques heures avec mon application en arrière-plan. L'appareil était verrouillé et le code fonctionnait correctement tout le temps.

@interface LocationManager () <CLLocationManagerDelegate>
@property (strong, nonatomic) CLLocationManager *locationManager;
@property (strong, nonatomic) NSDate *lastTimestamp;

@end

@implementation LocationManager

+ (instancetype)sharedInstance
{
    static id sharedInstance = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
        LocationManager *instance = sharedInstance;
        instance.locationManager = [CLLocationManager new];
        instance.locationManager.delegate = instance;
        instance.locationManager.desiredAccuracy = kCLLocationAccuracyBest; // you can use kCLLocationAccuracyHundredMeters to get better battery life
        instance.locationManager.pausesLocationUpdatesAutomatically = NO; // this is important
    });

    return sharedInstance;
}

- (void)startUpdatingLocation
{
    CLAuthorizationStatus status = [CLLocationManager authorizationStatus];

    if (status == kCLAuthorizationStatusDenied)
    {
        NSLog(@"Location services are disabled in settings.");
    }
    else
    {
        // for iOS 8
        if ([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)])
        {
            [self.locationManager requestAlwaysAuthorization];
        }
        // for iOS 9
        if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)])
        {
            [self.locationManager setAllowsBackgroundLocationUpdates:YES];
        }

        [self.locationManager startUpdatingLocation];
    }
}

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    CLLocation *mostRecentLocation = locations.lastObject;
    NSLog(@"Current location: %@ %@", @(mostRecentLocation.coordinate.latitude), @(mostRecentLocation.coordinate.longitude));

    NSDate *now = [NSDate date];
    NSTimeInterval interval = self.lastTimestamp ? [now timeIntervalSinceDate:self.lastTimestamp] : 0;

    if (!self.lastTimestamp || interval >= 5 * 60)
    {
        self.lastTimestamp = now;
        NSLog(@"Sending current location to web service.");
    }
}

@end
Leszek Szary
la source
3
Cela fonctionne-t-il pendant plus de quelques heures? Il semble que même si l'application n'est pas terminée, l'appareil cesse de pousser les mises à jour de localisation après environ une heure de l'application en arrière-plan.
Myxtic
2
La récupération en arrière-plan est désactivée dans mon projet (peu importe si elle est désactivée ou activée). Cependant, pausesLocationUpdatesAutomatically doit être défini sur NO pour que l'exemple ci-dessus fonctionne correctement. Il ne reprendra PAS automatiquement une fois que vous aurez recommencé à bouger s'il a été suspendu par le système précédemment. C'est pourquoi dans l'exemple ci-dessus, j'ai défini cette propriété sur NO.
Leszek Szary
1
Je pense que vous avez absolument raison. Vous trouverez plus d'informations sur le problème ici: stackoverflow.com/q/17484352/1048331
Myxtic
2
@LeszekS vous devez ajouter ci-dessous le code pour la prise en charge d'iOS 9 en arrière-plan - if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) { [self.locationManager setAllowsBackgroundLocationUpdates:YES]; }
Fenil
2
Quelle est la commodité de faire cela? Vous videz toujours la batterie en ayant une précision élevée et constante. La seule chose que vous ne faites pas, c'est que vous n'utilisez pas réellement l' emplacement déjà reçu jusqu'à ce que vous atteigniez un intervalle de 5 minutes ...
Honey
34

Je l'ai fait dans une application que je développe. Les minuteries ne fonctionnent pas lorsque l'application est en arrière-plan, mais l'application reçoit constamment les mises à jour de l'emplacement. J'ai lu quelque part dans la documentation (je n'arrive pas à le trouver maintenant, je publierai une mise à jour lorsque je le ferai) qu'une méthode ne peut être appelée que sur une boucle d'exécution active lorsque l'application est en arrière-plan. Le délégué de l'application a une boucle d'exécution active même dans le bg, vous n'avez donc pas besoin de créer la vôtre pour que cela fonctionne. [Je ne sais pas si c'est la bonne explication mais c'est comme ça que j'ai compris de ce que j'ai lu]

Tout d'abord, ajoutez l' locationobjet de la clé UIBackgroundModesdans info.plist de votre application. Maintenant, ce que vous devez faire, c'est commencer les mises à jour de position n'importe où dans votre application:

    CLLocationManager locationManager = [[CLLocationManager alloc] init];
    locationManager.delegate = self;//or whatever class you have for managing location
    [locationManager startUpdatingLocation];

Ensuite, écrivez une méthode pour gérer les mises à jour d'emplacement, par exemple -(void)didUpdateToLocation:(CLLocation*)location, dans le délégué d'application. Ensuite, implémentez la méthode locationManager:didUpdateLocation:fromLocationdeCLLocationManagerDelegate dans la classe dans laquelle vous avez démarré le gestionnaire d'emplacement (puisque nous avons défini le délégué du gestionnaire d'emplacement sur 'self'). Dans cette méthode, vous devez vérifier si l'intervalle de temps après lequel vous devez gérer les mises à jour de l'emplacement s'est écoulé. Vous pouvez le faire en enregistrant l'heure actuelle à chaque fois. Si ce temps s'est écoulé, appelez la méthode UpdateLocation depuis votre délégué d'application:

NSDate *newLocationTimestamp = newLocation.timestamp;
NSDate *lastLocationUpdateTiemstamp;

int locationUpdateInterval = 300;//5 mins

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if (userDefaults) {

        lastLocationUpdateTiemstamp = [userDefaults objectForKey:kLastLocationUpdateTimestamp];

        if (!([newLocationTimestamp timeIntervalSinceDate:lastLocationUpdateTiemstamp] < locationUpdateInterval)) {
            //NSLog(@"New Location: %@", newLocation);
            [(AppDelegate*)[UIApplication sharedApplication].delegate didUpdateToLocation:newLocation];
            [userDefaults setObject:newLocationTimestamp forKey:kLastLocationUpdateTimestamp];
        }
    }
}

Cela appellera votre méthode toutes les 5 minutes, même lorsque votre application est en arrière-plan. Imp: Cette implémentation vide la batterie, si la précision de vos données de localisation n'est pas critique, vous devez utiliser[locationManager startMonitoringSignificantLocationChanges]

Avant d'ajouter ceci à votre application, veuillez lire le Guide de programmation de détection d'emplacement

Bushra Shahid
la source
3
De cette façon, les services de localisation sont constamment activés (en effet, la décharge de la batterie), je ne veux pas cela. Je veux activer le service de localisation toutes les n minutes et le désactiver immédiatement lorsque j'ai une bonne solution (je viens de remarquer que je ne l'ai pas expliqué clairement dans ma question). Je peux obtenir ce comportement dans la solution que j'ai décrite.
wjans
3
Vous pouvez définir la précision du gestionnaire de position sur 1 km - cela laissera votre batterie presque intacte. Après 5 min, vous définissez la précision à 1 m. Lorsque vous obtenez un emplacement satisfaisant (normalement après 5 s), réglez simplement la précision à 1 km.
knagode
knagode, j'ai essayé votre solution suggérée pour le problème de décharge de la batterie, mais même après une précision accrue après N minutes, la méthode locationManager: didUpdateLocations n'est pas appelée à nouveau. J'ai essayé startUpdating et stopUpdating, au lieu d'augmenter et de diminuer la précision, on l'appelle avec succès déléguer locationManager: méthode didUpdateLocations, après N minutes, mais ne fonctionne pas en MODE Arrière-plan ...
Hardik Darji
Quant aux liens de documentation. Voir ici : "Les méthodes de votre objet délégué sont appelées à partir du thread dans lequel vous avez démarré les services de localisation correspondants. Ce thread doit lui-même avoir une boucle d'exécution active, comme celle trouvée dans le thread principal de votre application."
Honey
24

Maintenant qu'iOS6 est sorti, la meilleure façon d'avoir des services de localisation toujours actifs est ...

- (void)applicationWillResignActive:(UIApplication *)application
{
/*
 Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
 Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
 */

NSLog(@"to background");

app.isInBackground = TRUE;

UIApplication *app = [UIApplication sharedApplication];

// Request permission to run in the background. Provide an
// expiration handler in case the task runs long.
NSAssert(bgTask == UIBackgroundTaskInvalid, nil);

bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
    // Synchronize the cleanup call on the main thread in case
    // the task actually finishes at around the same time.
    dispatch_async(dispatch_get_main_queue(), ^{

        if (bgTask != UIBackgroundTaskInvalid)
        {
            [app endBackgroundTask:bgTask];
            bgTask = UIBackgroundTaskInvalid;
        }
    });
}];

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

    // Do the work associated with the task.

    locationManager.distanceFilter = 100;
    locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
    [locationManager startMonitoringSignificantLocationChanges];
    [locationManager startUpdatingLocation];

    NSLog(@"App staus: applicationDidEnterBackground");
    // Synchronize the cleanup call on the main thread in case
    // the expiration handler is fired at the same time.
    dispatch_async(dispatch_get_main_queue(), ^{
        if (bgTask != UIBackgroundTaskInvalid)
        {
            [app endBackgroundTask:bgTask];
            bgTask = UIBackgroundTaskInvalid;
        }
    });
});

NSLog(@"backgroundTimeRemaining: %.0f", [[UIApplication sharedApplication] backgroundTimeRemaining]);

}

Je l'ai juste testé comme ça:

J'ai démarré l'application, je vais en arrière-plan et je me déplace dans la voiture de quelques minutes. Ensuite, je rentre chez moi pendant 1 heure et recommence à bouger (sans rouvrir l'application). Les emplacements ont recommencé. Puis s'est arrêté pendant deux heures et a recommencé. Tout va bien encore ...

N'OUBLIEZ PAS D'UTILISER les nouveaux services de localisation dans iOS6

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{   
    CLLocation *loc = [locations lastObject];

    // Lat/Lon
    float latitudeMe = loc.coordinate.latitude;
    float longitudeMe = loc.coordinate.longitude;
}
Alejandro Luengo
la source
1
Si l'application se bloque ou est tuée, le système ne redémarre pas, non?
Ken
1
Pour un code plus lisible, vous pouvez faire un [locations lastObject]; au lieu de [locations objectAtIndex: [locations count] - 1]
axello
votre méthode est uniquement sur ios6?
pengwang
Doit-on utiliser cela? Je pensais simplement avoir le code du gestionnaire d'emplacement dans viewDidLoad d'une classe et avoir défini une clé d'arrière-plan dans le fichier plist que l'application enregistre pour les mises à jour d'emplacement devrait être assez bonne? Pouvez-vous m'aider s'il vous plait!
nithinreddy
Oui, cela fonctionnera comme un charme nithinreddy mais il cessera de fonctionner après 10 minutes car iOS tue les longs threads après cette période. Ma solution est parfaite si vous voulez que ces services démarrent pour toujours. J'ai fait quelques tests il y a deux jours et ça décharge 6% de batterie chaque heure. L'utilisation de [locationManager startUpdatingLocation] à la place drainera 17%
Alejandro Luengo
13

Pour quelqu'un d'autre ayant un cauchemar, comprenez celui-ci. J'ai une solution simple.

  1. regardez cet exemple de raywenderlich.com -> avoir un exemple de code, cela fonctionne parfaitement, mais malheureusement pas de minuterie pendant l'emplacement en arrière-plan. cela fonctionnera indéfiniment.
  2. Ajouter un minuteur en utilisant:

    -(void)applicationDidEnterBackground {
    [self.locationManager stopUpdatingLocation];
    
    UIApplication*    app = [UIApplication sharedApplication];
    
    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
    
     self.timer = [NSTimer scheduledTimerWithTimeInterval:intervalBackgroundUpdate
                                                  target:self.locationManager
                                                selector:@selector(startUpdatingLocation)
                                                userInfo:nil
                                                 repeats:YES];
    
    }
  3. N'oubliez pas d'ajouter "Registres d'application pour les mises à jour de localisation" dans info.plist.

HelmiB
la source
Cela obtient-il l'emplacement pendant plus de 3 minutes?
SleepNot
Doit définir l'emplacement dans Capabilities -> Background Mode.
Sergio Andreotti
Ça ne fonctionne pas!! ? J'ai vérifié sur iOS 8 et iOS 10
Kirti
Corrige moi si je me trompe. D'après ce que j'ai lu, vous ne lancez locationManager qu'une seule fois. Après cela, tous les intervalles sont redondants. Puisqu'il a déjà commencé
Honey
ça marche mais ça s'arrête après 170 secondes. je veux exécuter ma tâche en arrière-plan pour une
durée
8

Voici ce que j'utilise:

import Foundation
import CoreLocation
import UIKit

class BackgroundLocationManager :NSObject, CLLocationManagerDelegate {

    static let instance = BackgroundLocationManager()
    static let BACKGROUND_TIMER = 150.0 // restart location manager every 150 seconds
    static let UPDATE_SERVER_INTERVAL = 60 * 60 // 1 hour - once every 1 hour send location to server

    let locationManager = CLLocationManager()
    var timer:NSTimer?
    var currentBgTaskId : UIBackgroundTaskIdentifier?
    var lastLocationDate : NSDate = NSDate()

    private override init(){
        super.init()
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
        locationManager.activityType = .Other;
        locationManager.distanceFilter = kCLDistanceFilterNone;
        if #available(iOS 9, *){
            locationManager.allowsBackgroundLocationUpdates = true
        }

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.applicationEnterBackground), name: UIApplicationDidEnterBackgroundNotification, object: nil)
    }

    func applicationEnterBackground(){
        FileLogger.log("applicationEnterBackground")
        start()
    }

    func start(){
        if(CLLocationManager.authorizationStatus() == CLAuthorizationStatus.AuthorizedAlways){
            if #available(iOS 9, *){
                locationManager.requestLocation()
            } else {
                locationManager.startUpdatingLocation()
            }
        } else {
                locationManager.requestAlwaysAuthorization()
        }
    }
    func restart (){
        timer?.invalidate()
        timer = nil
        start()
    }

    func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
        switch status {
        case CLAuthorizationStatus.Restricted:
            //log("Restricted Access to location")
        case CLAuthorizationStatus.Denied:
            //log("User denied access to location")
        case CLAuthorizationStatus.NotDetermined:
            //log("Status not determined")
        default:
            //log("startUpdatintLocation")
            if #available(iOS 9, *){
                locationManager.requestLocation()
            } else {
                locationManager.startUpdatingLocation()
            }
        }
    }
    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

        if(timer==nil){
            // The locations array is sorted in chronologically ascending order, so the
            // last element is the most recent
            guard let location = locations.last else {return}

            beginNewBackgroundTask()
            locationManager.stopUpdatingLocation()
            let now = NSDate()
            if(isItTime(now)){
                //TODO: Every n minutes do whatever you want with the new location. Like for example sendLocationToServer(location, now:now)
            }
        }
    }

    func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
        CrashReporter.recordError(error)

        beginNewBackgroundTask()
        locationManager.stopUpdatingLocation()
    }

    func isItTime(now:NSDate) -> Bool {
        let timePast = now.timeIntervalSinceDate(lastLocationDate)
        let intervalExceeded = Int(timePast) > BackgroundLocationManager.UPDATE_SERVER_INTERVAL
        return intervalExceeded;
    }

    func sendLocationToServer(location:CLLocation, now:NSDate){
        //TODO
    }

    func beginNewBackgroundTask(){
        var previousTaskId = currentBgTaskId;
        currentBgTaskId = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
            FileLogger.log("task expired: ")
        })
        if let taskId = previousTaskId{
            UIApplication.sharedApplication().endBackgroundTask(taskId)
            previousTaskId = UIBackgroundTaskInvalid
        }

        timer = NSTimer.scheduledTimerWithTimeInterval(BackgroundLocationManager.BACKGROUND_TIMER, target: self, selector: #selector(self.restart),userInfo: nil, repeats: false)
    }
}

Je démarre le suivi dans AppDelegate comme ça:

BackgroundLocationManager.instance.start()
hmitkov
la source
Merci. Notre projet doit suivre l'emplacement des utilisateurs + envoyer des événements PubNub en arrière-plan et votre solution fonctionne très bien.
user921509
Salut Hmitkov, où je peux appeler la méthode sendLocationToServer pour envoyer l'emplacement de l'utilisateur sur le serveur
AmanGupta007
@ AmanGupta007 Vous pouvez appeler sendLocationToServer dans func locationManager (gestionnaire: didUpdateLocations :). Remarquez le commentaire // TODO dans le code.
hmitkov
@hmitkov Est-il possible de démarrer et d'arrêter les services de localisation avec cela lorsque l'application est en arrière-plan? Par exemple, démarrez le service de localisation à partir d'une notification push, récupérez des lat / long, envoyez au webservice, puis arrêtez de mettre à jour l'emplacement. Faites-le chaque fois que le «contenu disponible» = 1 est inclus dans le corps de poussée.
DookieMan
1
J'ai essayé ce code mais il semble que cela ne fonctionne pas sur iOS11? (Je ne l'ai testé sur aucune autre version.)
Tom
6

Malheureusement, toutes vos hypothèses semblent correctes, et je ne pense pas qu'il existe un moyen de le faire. Afin d'économiser la batterie, les services de localisation de l'iPhone sont basés sur le mouvement. Si le téléphone se trouve au même endroit, il est invisible pour les services de localisation.

Le CLLocationManagerne fera qu'appelerlocationManager:didUpdateToLocation:fromLocation: lorsque le téléphone recevra une mise à jour de localisation, ce qui ne se produit que si l'un des trois services de localisation (tour cellulaire, GPS, Wi-Fi) perçoit un changement.

Quelques autres choses qui pourraient aider à éclairer d'autres solutions:

  • Le démarrage et l'arrêt des services entraînent l' didUpdateToLocationappel de la méthode déléguée, mais ils newLocationpeuvent avoir un ancien horodatage.

  • La surveillance de la région pourrait aider

  • Lors de l'exécution en arrière-plan, sachez qu'il peut être difficile d'obtenir une prise en charge «complète» des LocationServices approuvée par Apple. D'après ce que j'ai vu, ils ont été spécifiquement conçus startMonitoringSignificantLocationChangescomme une alternative à faible consommation pour les applications qui nécessitent une prise en charge de l'emplacement en arrière-plan, et encouragent fortement les développeurs à l'utiliser à moins que l'application n'en ait absolument besoin.

Bonne chance!

MISE À JOUR: Ces pensées peuvent être obsolètes à ce jour. On dirait que les gens ont du succès avec la réponse @wjans, ci-dessus.

Chazbot
la source
1
Il existe des applications disponibles dans l'AppStore (comme par exemple "My Locus") qui permettent d'obtenir une mise à jour de l'emplacement en arrière-plan. Ils ne gardent pas le service de localisation actif, mais il est activé peu de temps après l'intervalle défini. comment font-ils ça?
wjans
2
Dans la situation que vous décrivez, l'application utilise très probablement l'approche startMonitoringSignificantLocationChanges. Ici, le téléphone est temporairement `` réveillé '' lorsqu'il reçoit une mise à jour de position, mais il n'y a aucun intervalle que vous pouvez définir pour `` cingler '' ce service en arrière-plan. Lorsque le téléphone se déplace (ou passe du cellulaire au GPS ou au Wifi), il déclenche une mise à jour. La conférence Stanford iTunes U sur le sujet m'a été très utile - j'espère qu'elle peut vous aider à trouver une solution: itunes.apple.com/us/itunes-u/iphone-application-development/…
Chazbot
2
Thx pour le pointeur. Cependant, je ne comprends toujours pas ce que fait l'application. Même si mon téléphone est à mon bureau, ne bouge pas du tout, je peux voir le service de localisation déclenché toutes les 10 minutes (exactement). Si je comprends bien, startMonitoringSignificantLocationChangesje ne donnerais aucune mise à jour dans ce cas.
wjans
1
@wjans comment est la consommation de la batterie de votre téléphone, avez-vous remarqué qu'elle s'épuisait rapidement peut-être à cause de l'application Mylocus?
user836026
6

J'ai écrit une application à l'aide des services de localisation, l'application doit envoyer une position toutes les 10 secondes. Et cela a très bien fonctionné.

Utilisez simplement le " allowDeferredLocationUpdatesUntilTraveled: timeout ", en suivant le doc d'Apple.

Ce que j'ai fait, c'est:

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

1. Créez LocationMangeret startUpdatingLocation, avec accuracyet filteredDistancecomme vous voulez:

-(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 s'exécute indéfiniment en utilisant la allowDeferredLocationUpdatesUntilTraveled:timeoutméthode en arrière-plan, vous devez redémarrer updatingLocationavec un nouveau paramètre lorsque l'application se déplace 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 à jour comme d'habitude avec un locationManager:didUpdateLocations:rappel:

-(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 puis les locationManager:didFinishDeferredUpdatesWithError:rappeler en fonction de votre objectif

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

     _deferringUpdates = NO;

     //do something 
}

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

samthui7
la source
@Toutes les solutions que cette solution ou les explications de wjans ci-dessus dois-je préférer économiser la batterie de l'appareil ou les deux affecteront la même chose?
Yash
2
J'ai essayé les deux solutions que vous mentionnez et j'ai vu que celle de @ wjans est un peu plus économique. Mais, après l'arrivée d'iOS 8, il semble que cette solution ne fonctionne plus correctement. Pour plus de détails: la plupart du temps, l'application ne peut pas vivre longtemps en arrière-plan.
samthui7
@ samthui7 Pourquoi définissez-vous pausesLocationUpdatesAutomatically = false?
CedricSoubrie
L'exigence de mon application est de continuer à envoyer userLocation toutes les 10 secondes, tout en indiquant pausesLocationUpdatesAutomatically = trueau gestionnaire de localisation de suspendre les mises à jour s'il n'y a apparemment aucun changement de localisation ( doc d'Apple ). Quoi qu'il en soit, je n'ai pas encore explicitement testé le cas de location manager pauses updates: D.
samthui7
@CedricSoubrie: ce paramètre empêche pausesLocationUpdatesAutomatically = truemon application de mettre à jour l'emplacement.
samthui7
5
if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) {
    [self.locationManager setAllowsBackgroundLocationUpdates:YES];
}

Cela est nécessaire pour le suivi de l'emplacement en arrière-plan depuis iOS 9.

Nilesh
la source
1
Cela m'a sauvé la journée! en utilisant la cible de déploiement iOS8, avec un appareil iOS9
Yaro
4

J'ai utilisé la méthode de xs2bush pour obtenir un intervalle (en utilisant timeIntervalSinceDate ) et développé un peu. Je voulais m'assurer que j'obtenais la précision requise dont j'avais besoin et aussi que je n'épuisais pas la batterie en gardant la radio gps allumée plus que nécessaire.

Je maintiens la localisation en continu avec les paramètres suivants:

locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
locationManager.distanceFilter = 5;

il s'agit d'un drain relativement faible sur la batterie. Lorsque je suis prêt à obtenir ma prochaine lecture d'emplacement périodique, je vérifie d'abord si l'emplacement est dans la précision souhaitée, si c'est le cas, j'utilise ensuite l'emplacement. Si ce n'est pas le cas, j'augmente la précision avec ceci:

locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
locationManager.distanceFilter = 0;

obtenir mon emplacement, puis une fois que j'ai l'emplacement, je baisse à nouveau la précision pour minimiser la décharge de la batterie. J'ai écrit un échantillon de travail complet de cela et j'ai également écrit la source du code côté serveur pour collecter les données de localisation, les stocker dans une base de données et permettre aux utilisateurs de visualiser les données GPS en temps réel ou de récupérer et d'afficher les itinéraires précédemment stockés. J'ai des clients pour iOS, android, windows phone et java me. Tous les clients sont écrits en natif et fonctionnent tous correctement en arrière-plan. Le projet est sous licence MIT.

Le projet iOS est ciblé pour iOS 6 à l'aide d'un SDK de base d'iOS 7. Vous pouvez obtenir le code ici .

Veuillez signaler un problème sur github si vous voyez des problèmes avec celui-ci. Merci.

nickfox
la source
J'ai essayé votre solution, mais ne fonctionne pas ... lorsque l'application passe en arrière-plan, même après une précision accrue, l'application ne s'appelle pas méthode didUpdateToLocation dans mon cas
Hardik Darji
@HardikDarji question importante. Déménages-tu? Sinon, les mises à jour de position peuvent s'arrêter. Essayez de sortir votre téléphone pour une promenade ou un trajet en voiture et voyez si cela résout le problème.
nickfox
Merci pour votre réponse rapide. Mais je veux des mises à jour de localisation toutes les 2 minutes, sans que mon téléphone ne bouge ou non. Dans ce scénario, la méthode didUpdateToLocation n'est pas appelée. Je recherche ici: comment obtenir une mise à jour de l'emplacement toutes les n minutes !!!
Hardik Darji
essayez de définir timeIntervalInSeconds sur 120 et décommentez cette ligne: locationManager.pausesLocationUpdatesAutomatically = NO;
nickfox
1
la solution que vous avez publiée dans le commentaire ci-dessus tue-t-elle la vie de la batterie, ou faites-vous toujours une optimisation pour cela?
samfr
2

Il semble que stopUpdatingLocation soit ce qui déclenche le temporisateur du chien de garde en arrière-plan, donc je l'ai remplacé dans didUpdateLocation par:

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

qui semble éteindre efficacement le GPS. Le sélecteur de l'arrière-plan NSTimer devient alors:

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

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

Amit Shelgaonkar
la source
2

Il existe un cocoapod APScheduledLocationManager qui permet d'obtenir des mises à jour de l'emplacement 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 écrit en Swift 3.

ceinture
la source
avez-vous quelque chose comme ça dans l'objectif c?
Moxarth
1

Dans iOS 9 et watchOS 2.0, il existe une nouvelle méthode sur CLLocationManager qui vous permet de demander l'emplacement actuel: CLLocationManager: requestLocation (). Cela se termine immédiatement, puis renvoie l'emplacement au délégué CLLocationManager.

Vous pouvez utiliser un NSTimer pour demander un emplacement toutes les minutes avec cette méthode maintenant et ne pas avoir à travailler avec les méthodes startUpdatingLocation et stopUpdatingLocation.

Toutefois, si vous souhaitez capturer des emplacements en fonction d'un changement de X mètres par rapport au dernier emplacement, définissez simplement la propriété distanceFilter de CLLocationManger et appelez X startUpdatingLocation ().

Malcolm
la source
-1

Ci-joint une solution Swift basée sur:

Définir App registers for location updates dans info.plist

Gardez le gestionnaire de localisation en cours d'exécution tout le temps

Basculez kCLLocationAccuracyentre BestForNavigation(pendant 5 secondes pour obtenir l'emplacement) etThreeKilometers pour le reste de la période d'attente pour éviter l'épuisement de la batterie

Cet exemple met à jour l'emplacement toutes les 1 min en avant-plan et toutes les 15 minutes en arrière-plan.

L'exemple fonctionne correctement avec Xcode 6 Beta 6, exécuté sur un appareil iOS 7.

Dans le délégué d'application (mapView est une option pointant vers le contrôleur mapView)

func applicationDidBecomeActive(application: UIApplication!) {
    if appLaunched! == false { // Reference to mapView used to limit one location update per timer cycle
        appLaunched = true
        var appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
        var window = appDelegate.window
        var tabBar = window?.rootViewController as UITabBarController
        var navCon = tabBar.viewControllers[0] as UINavigationController
        mapView = navCon.topViewController as? MapViewController
    }
    self.startInitialPeriodWithTimeInterval(60.0)
}

func applicationDidEnterBackground(application: UIApplication!) {
    self.startInitialPeriodWithTimeInterval(15 * 60.0)
}

func startInitialPeriodWithTimeInterval(timeInterval: NSTimeInterval) {
    timer?.invalidate() // reset timer
    locationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation
    timer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: Selector("getFirstLocationUpdate:"), userInfo: timeInterval, repeats: false)
}

func getFirstLocationUpdate(sender: NSTimer) {
    let timeInterval = sender.userInfo as Double
    timer?.invalidate()
    mapView?.canReportLocation = true
    timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval, target: self, selector: Selector("waitForTimer:"), userInfo: timeInterval, repeats: true)
}

func waitForTimer(sender: NSTimer) {
    let time = sender.userInfo as Double
    locationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation
    finalTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: Selector("getLocationUpdate"), userInfo: nil, repeats: false)
}

func getLocationUpdate() {
    finalTimer?.invalidate()
    mapView?.canReportLocation = true
}

Dans mapView (locationManager pointe vers l'objet dans AppDelegate)

override func viewDidLoad() {
    super.viewDidLoad()
    var appDelegate = UIApplication.sharedApplication().delegate! as AppDelegate
    locationManager = appDelegate.locationManager!
    locationManager.delegate = self
    canReportLocation = true
}

  func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
        if canReportLocation! {
            canReportLocation = false
            locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
        } else {
            //println("Ignore location update")
        }
    }
eharo2
la source