Come posso ottenere un aggiornamento della posizione in background ogni n minuti nella mia applicazione iOS?


207

Sto cercando un modo per ottenere un aggiornamento della posizione in background ogni n minuti nella mia applicazione iOS. Sto usando iOS 4.3 e la soluzione dovrebbe funzionare per iPhone senza jailbreak.

Ho provato / considerato le seguenti opzioni:

  • CLLocationManager startUpdatingLocation/startMonitoringSignificantLocationChanges: Funziona in background come previsto, in base alle proprietà configurate, ma non sembra possibile forzarlo per aggiornare la posizione ogni n minuti
  • NSTimer: Funziona quando l'app è in esecuzione in primo piano ma non sembra essere progettata per attività in background
  • Notifiche locali: le notifiche locali possono essere programmate ogni n minuti, ma non è possibile eseguire un codice per ottenere la posizione corrente (senza che l'utente debba avviare l'app tramite la notifica). Anche questo approccio non sembra essere un approccio pulito in quanto non è questo ciò per cui le notifiche dovrebbero essere utilizzate.
  • UIApplication:beginBackgroundTaskWithExpirationHandler: Per quanto ho capito, questo dovrebbe essere usato per finire un po 'di lavoro in background (anche limitato nel tempo) quando un'app viene spostata in background piuttosto che implementare processi in background "di lunga durata".

Come posso implementare questi aggiornamenti regolari della posizione in background?




1
Se stai cercando di farlo funzionare su iOS 7, puoi provare questa soluzione qui: stackoverflow.com/questions/18946881/… Se hai qualche domanda, sei il benvenuto a unirti a noi per una discussione qui: mobileoop.com/background -location-update-Programming-for-iOS-7
Ricky,

1
Tutti i risultati sono corretti (i quattro punti elenco). Informazioni preziose che sono allora, sapendo cosa non corrisponde al tuo caso d'uso? E sì, quando in MODALITÀ SOSPESA o NON IN ESECUZIONE, non esiste un metodo definitivo per l'aggiornamento ogni n-minuti.
LenArt

Risposte:


113

Ho trovato una soluzione per implementarlo con l'aiuto dei forum degli sviluppatori Apple:

  • Specificare location background mode
  • Crea uno NSTimerin background conUIApplication:beginBackgroundTaskWithExpirationHandler:
  • Quando nè più piccolo di UIApplication:backgroundTimeRemainingquanto funzionerà bene. Quando nè più grande , location managerdovrebbe essere nuovamente abilitato (e disabilitato) prima che non rimanga tempo per evitare che l'attività in background venga uccisa.

Questo funziona perché la posizione è uno dei tre tipi consentiti di esecuzione in background .

Nota: ho perso un po 'di tempo testandolo nel simulatore in cui non funziona. Tuttavia, funziona bene sul mio telefono.


24
ti capita di avere il link al forum. Sto cercando di implementare lo stesso tipo di mappatura della posizione e non sono stato in grado di farlo funzionare. O qualche codice di esempio sarebbe molto apprezzato.
utahwithak,

4
Puoi spiegare perché l'attività in background non viene interrotta dopo 10 minuti (tempo massimo consentito) se ti fermi e avvii il gestore della posizione? è una sorta di funzionalità prevista? sembra più un bug in Apple SDK se succede così. Su quale versione di iOS la stavi provando?
Saurabh,

5
@all: Sì, la nostra app è disponibile nell'App Store. Non pubblicherò tutto il codice, tutti i punti menzionati sono funzioni chiaramente documentate. Nel caso in cui tu abbia un problema specifico, pubblica la tua domanda spiegando cosa hai provato e cosa non va.
wjans

3
@ user836026: Sì, questo è ciò che intendo specificando la modalità in background. Dopo aver interrotto l'aggiornamento della posizione, è necessario riavviarlo entro 10 minuti per evitare la chiusura dell'app.
wjans

12
Per tutti coloro che sono interessati a vedere del codice reale che riflette ciò che viene discusso qui, controlla stackoverflow.com/questions/10235203/…
Lolo

55

Su iOS 8/9/10 per effettuare l'aggiornamento della posizione in background ogni 5 minuti, procedi come segue:

  1. Vai a Progetto -> Funzionalità -> Modalità sfondo -> seleziona Aggiornamenti posizione

  2. Vai a Progetto -> Informazioni -> aggiungi una chiave NSLocationAlwaysUsageDescription con valore vuoto (o facoltativamente qualsiasi testo)

  3. Per far funzionare la posizione quando la tua app è in background e inviare coordinate al servizio web o fare qualcosa con loro ogni 5 minuti implementala come nel codice qui sotto.

Non sto usando attività in background o timer. Ho testato questo codice con il mio dispositivo con iOS 8.1 che giaceva sulla mia scrivania per alcune ore con la mia app in esecuzione in background. Il dispositivo era bloccato e il codice era sempre in esecuzione correttamente.

@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

3
Funziona da più di qualche ora? Sembra che anche se l'app non viene terminata, il dispositivo smette di inviare aggiornamenti di posizione dopo circa un'ora quando l'app passa in background.
Myxtic

2
Il recupero in background è disabilitato nel mio progetto (non dovrebbe importare se è spento o acceso). Tuttavia pausesLocationUpdatesAutomaticamente deve essere impostato su NO affinché l'esempio sopra funzioni correttamente. NON riprenderà automaticamente quando si ricomincia a spostarsi se è stato precedentemente sospeso dal sistema. Ecco perché nell'esempio sopra ho impostato questa proprietà su NO.
Leszek Szary

1
Penso che tu abbia assolutamente ragione. Trovato un po 'di informazioni sul problema qui: stackoverflow.com/q/17484352/1048331
Myxtic

2
@LeszekS devi aggiungere il codice seguente per il supporto di iOS 9 per lo sfondo - if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) { [self.locationManager setAllowsBackgroundLocationUpdates:YES]; }
Fenil

2
Qual è la comodità di farlo? Stai ancora scaricando la batteria avendo un'alta precisione costante. L'unica cosa che non fai è che in realtà non usi la posizione già ricevuta fino a quando non raggiungi un intervallo di 5 minuti ...
Miele

34

L'ho fatto in un'applicazione che sto sviluppando. I timer non funzionano quando l'app è in background ma l'app riceve costantemente gli aggiornamenti della posizione. Ho letto da qualche parte nella documentazione (non riesco a trovarlo ora, posterò un aggiornamento quando lo faccio) che un metodo può essere chiamato solo su un ciclo di esecuzione attivo quando l'app è in background. Il delegato dell'app ha un ciclo di esecuzione attivo anche in bg, quindi non è necessario crearne uno proprio per farlo funzionare. [Non sono sicuro che questa sia la spiegazione corretta, ma è così che ho capito da quello che ho letto]

Prima di tutto, aggiungi l' locationoggetto per la chiave UIBackgroundModesnella info.plist della tua app. Ora, ciò che devi fare è avviare gli aggiornamenti della posizione ovunque nell'app:

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

Quindi, scrivi un metodo per gestire gli aggiornamenti della posizione, ad esempio -(void)didUpdateToLocation:(CLLocation*)location, nel delegato dell'app. Quindi implementare il metodo locationManager:didUpdateLocation:fromLocationdi CLLocationManagerDelegatenella classe in cui è stato avviato il location manager (dal momento che abbiamo impostato la location manager delegato al 'sé'). All'interno di questo metodo è necessario verificare se è trascorso l'intervallo di tempo dopo il quale è necessario gestire gli aggiornamenti della posizione. Puoi farlo salvando l'ora corrente ogni volta. Se è trascorso questo tempo, chiama il metodo UpdateLocation dal delegato dell'app:

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

Questo chiamerà il tuo metodo ogni 5 minuti anche quando l'app è in background. Imp: questa implementazione scarica la batteria, se la precisione dei dati sulla tua posizione non è critica, dovresti usarla[locationManager startMonitoringSignificantLocationChanges]

Prima di aggiungere questo alla tua app, leggi la Guida alla programmazione della consapevolezza della posizione


3
In questo modo i servizi di localizzazione sono costantemente abilitati (anzi, l'esaurimento della batteria), non lo voglio. Voglio abilitare il servizio di localizzazione ogni n minuti e disabilitarlo immediatamente quando ho una buona soluzione (ho appena notato che non l'ho spiegato chiaramente nella mia domanda). Posso ottenere questo comportamento nella soluzione che ho descritto.
wjans,

3
Puoi impostare la precisione del gestore della posizione su 1 km - questo lascerà la batteria quasi intatta. Dopo 5 minuti si imposta la precisione su 1m. Quando si ottiene una posizione soddisfacente (normalmente dopo 5 secondi), riportare la precisione a 1 km.
knagode

knagode, ho provato la soluzione suggerita per il problema di esaurimento della batteria, ma anche dopo un aumento della precisione dopo N minuti, locationManager: il metodo didUpdateLocations non viene più richiamato. Ho provato startUpdating e stopUpdating, invece di aumentare e diminuire l'accuratezza, si chiama delegare locationManager con successo: metodo didUpdateLocations, dopo N minuti, ma non funziona in MODALITÀ di sfondo ...
Hardik Darji

Per quanto riguarda i collegamenti della documentazione. Vedere qui : "I metodi dell'oggetto delegato vengono chiamati dal thread in cui sono stati avviati i servizi di posizione corrispondenti. Tale thread deve avere un ciclo di esecuzione attivo, come quello trovato nel thread principale dell'applicazione."
Miele,

24

Ora che iOS6 è il modo migliore per avere servizi di localizzazione sempre attivi è ...

- (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]);

}

Ho appena provato in questo modo:

Ho avviato l'app, sono passato in background e mi sono spostato in macchina per alcuni minuti. Poi torno a casa per 1 ora e ricomincio a spostarmi (senza riaprire l'app). Posizioni ricominciate. Quindi si fermò per due ore e ricominciò. Tutto ok di nuovo ...

NON DIMENTICARE DI USARE i nuovi servizi di localizzazione in iOS6

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

    // Lat/Lon
    float latitudeMe = loc.coordinate.latitude;
    float longitudeMe = loc.coordinate.longitude;
}

1
Se l'app si arresta in modo anomalo o viene interrotta, il sistema non si riavvia, giusto?
Ken,

1
Per un codice più leggibile, puoi fare un [locations lastObject]; invece di [locations objectAtIndex: [località contate] - 1]
axello

il tuo metodo è solo su iOS6?
pengwang

Dobbiamo usare questo? Ho pensato di avere solo il codice del gestore della posizione in viewDidLoad di una classe e ho impostato una chiave di sfondo nel file plist che l'app registra per gli aggiornamenti della posizione dovrebbe essere abbastanza buona? Potete per favore aiutarmi con questo!
nithinreddy,

Sì, funzionerà come il fascino del nithinreddy ma smetterà di funzionare dopo 10 minuti poiché iOS uccide i thread lunghi dopo quel periodo. La mia soluzione è perfetta se vuoi che quei servizi siano avviati per sempre. Ho fatto alcuni test due giorni fa e scarica la batteria del 6% ogni ora. L'uso di [locationManager startUpdatingLocation] consumerà invece il 17%
Alejandro Luengo

13

A qualcun altro che ha un incubo capire questo. Ho una soluzione semplice.

  1. guarda questo esempio da raywenderlich.com -> avere un codice di esempio, questo funziona perfettamente, ma sfortunatamente nessun timer durante la posizione in background. questo funzionerà indefinitamente.
  2. Aggiungi timer usando:

    -(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. Non dimenticare di aggiungere "Registri app per gli aggiornamenti di posizione" in info.plist.


Questo ottiene la posizione per più di 3 minuti?
SonnoNon

Deve impostare la posizione in Funzionalità -> Modalità sfondo.
Sergio Andreotti,

La sua non funziona!! ? Ho verificato iOS 8 e iOS 10
Kirti il

Correggimi se sbaglio. Sulla base di ciò che ho letto, si avvia locationManager solo una volta. Dopodiché tutti gli intervalli sono ridondanti. Dal momento che è già stato avviato
Honey

funziona ma si arresta dopo 170 secondi. Voglio eseguire il mio compito in background per un tempo illimitato
anshuman burmman

8

Ecco cosa uso:

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)
    }
}

Comincio il monitoraggio in AppDelegate in questo modo:

BackgroundLocationManager.instance.start()

Grazie. Il nostro progetto deve tenere traccia della posizione dell'utente + inviare eventi PubNub in background e la tua soluzione funziona davvero bene.
user921509

Ciao Hmitkov, dove posso chiamare il metodo sendLocationToServer per inviare la posizione dell'utente sul server
AmanGupta007

@ AmanGupta007 È possibile chiamare sendLocationToServer in func locationManager (manager: didUpdateLocations :). Nota il commento // TODO nel codice.
hmitkov,

@hmitkov È possibile avviare e interrompere i servizi di localizzazione con questo mentre l'app è in background? Esempio, avvia il servizio di localizzazione da una notifica push, prendi un po 'di lat / long, invia al servizio web, quindi smetti di aggiornare la posizione. Fallo ogni volta che il "contenuto disponibile" = 1 è incluso nel corpo del push.
DookieMan,

1
Ho provato questo codice ma sembra che non funzioni su iOS11? (Non l'ho provato su altre versioni.)
Tom,

6

Sfortunatamente, tutte le tue assunzioni sembrano corrette e non penso che ci sia un modo per farlo. Al fine di risparmiare la durata della batteria, i servizi di localizzazione dell'iPhone si basano sul movimento. Se il telefono si trova in un punto, è invisibile ai servizi di localizzazione.

L' CLLocationManagerchiamerà solamente locationManager:didUpdateToLocation:fromLocation:quando il telefono riceve un aggiornamento di posizione, che avviene solo se uno dei tre servizi di localizzazione (torre di cella, gps, wifi) percepisce un cambiamento.

Alcune altre cose che potrebbero aiutare a informare ulteriori soluzioni:

  • L'avvio e l'arresto dei servizi provoca la didUpdateToLocationchiamata del metodo delegato, ma newLocationpotrebbe avere un vecchio timestamp.

  • Il monitoraggio della regione potrebbe essere d'aiuto

  • Quando si esegue in background, tenere presente che potrebbe essere difficile ottenere il supporto "completo" di LocationServices approvato da Apple. Da quello che ho visto, hanno progettato specificamente startMonitoringSignificantLocationChangescome alternativa a basso consumo per le app che richiedono il supporto della posizione in background e incoraggiano fortemente gli sviluppatori a usarlo a meno che l'app non ne abbia assolutamente bisogno.

In bocca al lupo!

AGGIORNAMENTO: Questi pensieri potrebbero essere obsoleti ormai. Sembra che le persone stiano avendo successo con la risposta @wjans, sopra.


1
Nell'AppStore sono disponibili app (come ad esempio "My Locus") che consentono di ottenere un aggiornamento della posizione in background. Non mantengono attivo il servizio di localizzazione ma viene abilitato a breve secondo l'intervallo definito. Come lo fanno?
wjans,

2
Nella situazione che descrivi, l'app sta molto probabilmente usando l'approccio startMonitoringSignificantLocationChanges. Qui, il telefono viene temporaneamente "svegliato" quando riceve un aggiornamento della posizione, ma non c'è intervallo che è possibile impostare per "eseguire il ping" di questo servizio in background. Quando il telefono si sposta (o passa da cellulare a GPS o Wi-Fi), avvia un aggiornamento. La lezione di Stanford iTunes U sull'argomento è stata davvero utile per me - si spera che possa aiutarti a trovare una soluzione alternativa: itunes.apple.com/us/itunes-u/iphone-application-development/…
Chazbot

2
Grazie per il puntatore. Tuttavia, non capisco ancora cosa stia facendo l'app. Anche se il mio telefono è alla mia scrivania, non si muove affatto, posso vedere il servizio di localizzazione essere attivato ogni 10 minuti (esattamente). Se ho capito bene, startMonitoringSignificantLocationChangesnon darei alcun aggiornamento in quel caso.
wjans,

1
@wjans com'è il consumo della batteria del tuo telefono, hai notato che si scarica molto probabilmente a causa dell'app Mylocus?
user836026,

6

Ho scritto un'app utilizzando i servizi di localizzazione, l'app deve inviare la posizione ogni 10 secondi. E ha funzionato molto bene.

Usa semplicemente il metodo " allowDeferredLocationUpdatesUntilTraveled: timeout ", seguendo il documento di Apple.

Quello che ho fatto sono:

Obbligatorio: registra la modalità in background per la posizione di aggiornamento.

1. Crea LocationMangere startUpdatingLocation, con accuracye filteredDistancecome vuoi:

-(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. Per mantenere l'app in esecuzione per sempre utilizzando il allowDeferredLocationUpdatesUntilTraveled:timeoutmetodo in background, è necessario riavviare updatingLocationcon un nuovo parametro quando l'app passa allo sfondo, in questo modo:

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

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

3. L' app viene aggiornata Posizione normalmente con locationManager:didUpdateLocations:callback:

-(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. Ma dovresti gestire i dati e poi locationManager:didFinishDeferredUpdatesWithError:richiamare per il tuo scopo

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

     _deferringUpdates = NO;

     //do something 
}

5. NOTA: penso che dovremmo ripristinare i parametri di LocationManagerogni volta che l'app passa dalla modalità background / forground.


@Tutte le soluzioni spiegate da questa o sopra la spiegazione dei wjan dovrei preferire risparmiare la batteria del dispositivo o entrambe influiranno sulla stessa?
Yash

2
Ho provato entrambe le soluzioni che hai citato e ho visto che quella di @ wjans è un po 'più di risparmio della batteria. Ma, dopo l'arrivo di iOS 8, sembra che quella soluzione non funzioni più correttamente. Per maggiori dettagli: la maggior parte delle volte, l'app non può durare a lungo in modalità background.
samthui7,

@ samthui7 Perché imposti pausesLocationUpdatesAutomatically = false?
CedricSoubrie,

Il requisito della mia app è di continuare a inviare userLocation ogni 10 secondi, mentre pausesLocationUpdatesAutomatically = truedice al gestore della posizione di sospendere gli aggiornamenti se non sembra esserci alcun cambiamento di posizione ( documento di Apple ). Comunque, non ho ancora testato esplicitamente il caso di location manager pauses updates: D.
samthui7,

@CedricSoubrie: l'impostazione pausesLocationUpdatesAutomatically = truecausa la mia app per interrompere l'aggiornamento della posizione.
samthui7,

5
if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) {
    [self.locationManager setAllowsBackgroundLocationUpdates:YES];
}

Ciò è necessario per il tracciamento della posizione in background da iOS 9.


1
Questo mi ha salvato la giornata! utilizzando la destinazione di distribuzione iOS8, con dispositivo iOS9
Yaro

4

Ho usato il metodo di xs2bush per ottenere un intervallo (usando timeIntervalSinceDate) e vi ho ampliato un po '. Volevo assicurarmi di ottenere la precisione richiesta di cui avevo bisogno e anche di non scaricare la batteria mantenendo la radio GPS più del necessario.

Mantengo la posizione in esecuzione continuamente con le seguenti impostazioni:

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

questo è un consumo relativamente basso della batteria. Quando sono pronto per ottenere la mia prossima lettura periodica della posizione, per prima cosa controllo se la posizione è nella precisione desiderata, se lo è, quindi uso la posizione. In caso contrario, aumento la precisione con questo:

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

ottenere la mia posizione e quindi, una volta trovata la posizione, abbassare nuovamente la precisione per ridurre al minimo il consumo della batteria. Ho scritto un esempio di lavoro completo di questo e ho anche scritto l'origine per il codice lato server per raccogliere i dati sulla posizione, archiviarli in un database e consentire agli utenti di visualizzare i dati GPS in tempo reale o recuperare e visualizzare percorsi precedentemente memorizzati. Ho client per iOS, Android, Windows Phone e Java Me. Tutti i client sono scritti in modo nativo e funzionano tutti correttamente in background. Il progetto è concesso in licenza dal MIT.

Il progetto iOS è mirato per iOS 6 utilizzando un SDK di base di iOS 7. Puoi ottenere il codice qui .

Si prega di presentare un problema su Github se si riscontrano problemi con esso. Grazie.


Ho provato la tua soluzione, ma non funziona ... quando l'app passa in background, quindi, anche dopo un aumento della precisione, l'app non viene chiamata metodo didUpdateToLocation nel mio caso
Hardik Darji

@HardikDarji domanda importante. Ti stai muovendo? In caso contrario, gli aggiornamenti della posizione potrebbero interrompersi. Prova a portare il telefono a fare una passeggiata o in auto e vedere se questo risolve il problema.
Nickfox

Grazie per la sua pronta risposta. Ma voglio aggiornamenti sulla posizione ogni 2 minuti, senza che il mio telefono si stia muovendo o meno. In questo scenario il metodo didUpdateToLocation non viene chiamato. Sto cercando qui: come posso ottenere l'aggiornamento della posizione ogni n minuti !!!
Hardik Darji,

prova a impostare timeIntervalInSeconds su 120 e decommenta questa riga: locationManager.pausesLocationUpdatesAutomatically = NO;
Nickfox

1
la soluzione che hai pubblicato nel commento sopra fa perdere la durata della batteria o stai ancora facendo qualche ottimizzazione per questo?
Samfr

2

Sembra che stopUpdatingLocation sia ciò che attiva il timer watchdog in background, quindi l'ho sostituito in didUpdateLocation con:

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

che sembra spegnere efficacemente il GPS. Il selettore per lo sfondo NSTimer diventa quindi:

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

Tutto quello che sto facendo è alternare periodicamente l'accuratezza per ottenere una coordinata ad alta precisione ogni pochi minuti e poiché il locationManager non è stato arrestato, backgroundTimeRemaining rimane al suo valore massimo. Ciò ha ridotto il consumo della batteria da ~ 10% all'ora (con kCLLocationAccuracyBest costante in background) a ~ 2% all'ora sul mio dispositivo


2

Esiste un cocoapod APScheduledLocationManager che consente di ottenere aggiornamenti sulla posizione in background ogni n secondi con l'accuratezza della posizione desiderata.

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

Il repository contiene anche un'app di esempio scritta in Swift 3.


hai qualcosa del genere nell'obiettivo c?
Moxarth,

1

In iOS 9 e watchOS 2.0 c'è un nuovo metodo su CLLocationManager che ti consente di richiedere la posizione corrente: CLLocationManager: requestLocation (). Questo si completa immediatamente e quindi restituisce la posizione al delegato CLLocationManager.

È possibile utilizzare un NSTimer per richiedere una posizione ogni minuto con questo metodo ora e non è necessario lavorare con i metodi startUpdatingLocation e stopUpdatingLocation.

Tuttavia, se si desidera acquisire posizioni basate su una modifica di X metri dall'ultima posizione, è sufficiente impostare la proprietà distanceFilter di CLLocationManger e su X call startUpdatingLocation ().


-1

In allegato è una soluzione Swift basata su:

Definire App registers for location updatesnella info.plist

Mantieni il locationManager sempre in esecuzione

Passare kCLLocationAccuracyda BestForNavigation(per 5 secondi per ottenere la posizione) e ThreeKilometersper il resto del periodo di attesa per evitare il consumo della batteria

Questo esempio aggiorna la posizione ogni 1 minuto in primo piano e ogni 15 minuti in background.

L'esempio funziona perfettamente con Xcode 6 Beta 6, in esecuzione su un dispositivo iOS 7.

Nel delegato dell'app (mapView è un facoltativo che punta al controller 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
}

In mapView (locationManager punta all'oggetto in 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")
        }
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.