Aggiornamenti periodici della posizione in background di iOS


91

Sto scrivendo un'applicazione che richiede aggiornamenti della posizione in background con elevata precisione e bassa frequenza . La soluzione sembra essere un'attività NSTimer in background che avvia gli aggiornamenti del gestore della posizione, che quindi si spegne immediatamente. Questa domanda è stata posta in precedenza:

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

Ottenere la posizione dell'utente ogni n minuti dopo che l'app è passata in background

iOS Non è il tipico problema del timer di rilevamento della posizione in background

Timer in background iOS di lunga durata con modalità in background "location"

Servizio in background iOS a tempo pieno basato sul rilevamento della posizione

ma devo ancora ottenere un minimo di esempio funzionante. Dopo aver provato ogni permutazione delle risposte accettate sopra, ho messo insieme un punto di partenza. Inserimento dello sfondo:

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

e il metodo delegato:

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

    NSLog(@"%@", newLocation);

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

}

Il comportamento corrente è che il valore backgroundTimeRemainingdiminuisce da 180 secondi a zero (durante la registrazione della posizione), quindi viene eseguito il gestore della scadenza e non vengono generati ulteriori aggiornamenti della posizione. Come posso modificare il codice precedente per ricevere aggiornamenti periodici sulla posizione in background a tempo indeterminato ?

Aggiornamento : sto prendendo di mira iOS 7 e sembra che ci siano alcune prove che le attività in background si comportano in modo diverso:

Avvia Gestione posizione in iOS 7 dall'attività in background


Non dici quale versione di iOS stai prendendo di mira. iOS 7 (ad esempio) ha un sistema multitasking diverso dal suo predecessore.
Jason Whitehorn,

@JasonWhitehorn Buon punto. Ho aggiornato la domanda.
pcoving

2
@pcoving: la tua soluzione funzionerà anche se l'app viene interrotta o terminata?
Nirav

Risposte:


62

Sembra che stopUpdatingLocationsia ciò che attiva il timer del watchdog in background, quindi l'ho sostituito didUpdateLocationcon:

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

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

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

Tutto ciò che sto facendo è cambiare periodicamente la precisione per ottenere una coordinata di alta precisione ogni pochi minuti e poiché locationManagernon è stato fermato, backgroundTimeRemainingrimane al suo valore massimo. Questo ha ridotto il consumo della batteria dal ~ 10% all'ora (con costante kCLLocationAccuracyBestin background) al ~ 2% all'ora sul mio dispositivo.


4
Potresti aggiornare la tua domanda con l'esempio funzionante completo? Qualcosa come: "AGGIORNAMENTO: ho capito. Ecco la mia soluzione ..."
smholloway

@Virussmca Sono tornato a una risposta precedente che è molto più robusta con prestazioni simili. L'interruzione totale degli aggiornamenti della posizione sembra attivare una condizione di competizione (o qualche altro scenario non deterministico).
pcoving

Questo approccio non funzionerà come ti aspetti: 1) Aumentare il valore della proprietà distanceFilter non si traduce in un risparmio della batteria perché il GPS deve continuare a calcolare la tua posizione. 2) In iOS 7 il tempo in background non è contiguo. 3) Potresti prendere in considerazione servizi significativi di ChangeLocation. 4) A parte la tua versione in iOS 7 non puoi avviare UIBackgroundModes - posizione dallo sfondo ...
aMother

2
@aMother 1) Mi rendo conto che aumentare il filtro della distanza (e non elaborare i callback) non fa risparmiare molta batteria. Tuttavia, setDesiredAccuracy lo fa! Si basa sulle informazioni sulle torri cellulari che vengono fondamentalmente gratuite. 2) Non contiguo? 3) Ho specificato, in grassetto, che richiedo un'elevata precisione. Cambiamenti significativi di posizione non lo forniscono. 4) I servizi di localizzazione non vengono avviati / arrestati in background.
pcoving

2
So che questo è un vecchio post ora, ma gli utenti della mia applicazione che utilizzano questo trucco hanno notato un aumento significativo dell'utilizzo della batteria dall'aggiornamento a iOS9. Non ho ancora indagato, ma ho la sensazione che questo "trucco" potrebbe non funzionare più?
Gary Wright

45

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.

I passaggi sono i seguenti:

Obbligatorio: registra la modalità in background per aggiornare la posizione.

1. Crea LocationManger e avviaUpdatingLocation, con precisione e filteredDistance come 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 metodo "allowDeferredLocationUpdatesUntilTraveled: timeout" in background, devi riavviare updateLocation con un nuovo parametro quando l'app passa in background, 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 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 in "locationManager: didFinishDeferredUpdatesWithError:" callback per il tuo scopo

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

     _deferringUpdates = NO;

     //do something 
}

5. NOTA: Penso che dovremmo reimpostare i parametri di LocationManager ogni volta che l'app passa dalla modalità sfondo / primo piano.


Funziona alla grande e sento che questo è il modo giusto di fare come menzionato nel WWDC l'anno scorso !! Grazie per il post
prasad1250

@ samthui7 grazie funziona, ma devo anche ottenere la posizione dopo la risoluzione. come può essere.??
Mohit Tomar

@ samthui7 .. si prega di vedere questo que ... stackoverflow.com/questions/37338466/...
Suraj Sukale

2
@amar: ho controllato anche su iOS 10 beta, funziona ancora. Nota che dobbiamo aggiungere del codice prima di startUpdatingLocation: allowsBackgroundLocationUpdates = YESe requestAlwaysAuthorizationo requestWhenInUseAuthorization. Ad esempio: `CLLocationManager * locationManager = [[CLLocationManager alloc] init]; locationManager.delegate = self; locationManager.allowsBackgroundLocationUpdates = SÌ; [locationManager requestAlwaysAuthorization]; [locationManager startUpdatingLocation]; `
samthui7

1
@Nirav No. Non credo che Apple possa permettercelo, per quanto ne so.
samthui7

38
  1. Se hai la chiave UIBackgroundModesnel tuo plist location, non è necessario utilizzare il beginBackgroundTaskWithExpirationHandlermetodo. È ridondante. Inoltre lo stai usando in modo errato (vedi qui ) ma è discutibile poiché il tuo plist è impostato.

  2. Con UIBackgroundModes locationil plist l'app continuerà a essere eseguita in background per un tempo indefinito solo finché CLLocationMangerè in esecuzione. Se chiami stopUpdatingLocationmentre sei in background, l'app si fermerà e non si riavvierà.

    Forse potresti chiamare beginBackgroundTaskWithExpirationHandlerappena prima di chiamare stopUpdatingLocatione poi dopo aver chiamato startUpdatingLocationpotresti chiamare endBackgroundTaskper mantenerlo in background mentre il GPS è fermo, ma non l'ho mai provato - è solo un'idea.

    Un'altra opzione (che non ho provato) è mantenere il gestore della posizione in esecuzione in background, ma una volta ottenuta una posizione precisa, modificare la desiredAccuracyproprietà a 1000 mo superiore per consentire lo spegnimento del chip GPS (per risparmiare batteria). Quindi 10 minuti dopo, quando è necessario un altro aggiornamento della posizione, cambiare la parte desiredAccuracyposteriore a 100 m per accendere il GPS fino a ottenere una posizione precisa, ripetere.

  3. Quando chiami startUpdatingLocationil gestore della posizione, devi dargli il tempo di ottenere una posizione. Non dovresti chiamare immediatamente stopUpdatingLocation. Lo lasciamo funzionare per un massimo di 10 secondi o fino a quando non otteniamo una posizione ad alta precisione non memorizzata nella cache.

  4. È necessario filtrare le posizioni memorizzate nella cache e verificare la precisione della posizione che si ottiene per assicurarsi che soddisfi la precisione minima richiesta (vedere qui ). Il primo aggiornamento che ricevi potrebbe essere di 10 minuti o 10 giorni. La prima precisione che ottieni potrebbe essere 3000 m.

  5. Considera invece l'utilizzo delle API per il cambio di posizione significativo. Una volta ricevuta la notifica di modifica significativa, puoi iniziare CLLocationManagerper alcuni secondi per ottenere una posizione di alta precisione. Non ne sono sicuro, non ho mai utilizzato i servizi di cambio posizione significativo.


Il CoreLocationriferimento Apple sembra consigliare l'uso di beginBackgroundTaskWithExpirationHandlerquando si elaborano i dati sulla posizione in background: "Se un'app iOS richiede più tempo per elaborare i dati sulla posizione, può richiedere più tempo di esecuzione in background utilizzando il metodo beginBackgroundTaskWithName: expirationHandler: della classe UIApplication." - developer.apple.com/library/ios/documentation/UserExperience/…
eliocs

4
non dimenticare di aggiungere _locationManager.allowsBackgroundLocationUpdates = YES; per iOS9 +, altrimenti l'app andrà in
stop

@kolyancz L'ho appena provato su iOS 10.1.1 per iPhone 6+. Ci sono voluti circa 17 minuti prima che andasse a dormire e si attivasse locationManagerDidPauseLocationUpdates. Ho verificato quel comportamento 10 volte!
Miele

Non capisco davvero perché dici che è controverso e poi stai dicendo che dovresti avvolgerlo in uno sfondo Attività ...
Tesoro

10

Quando è il momento di avviare il servizio di localizzazione e interrompere l'attività in background, l'attività in background dovrebbe essere interrotta con un ritardo (1 secondo dovrebbe essere sufficiente). In caso contrario, il servizio di localizzazione non verrà avviato. Anche il servizio di localizzazione dovrebbe essere lasciato attivo per un paio di secondi (ad esempio 3 secondi).

C'è un cocoapod APScheduledLocationManager che consente di ottenere aggiornamenti della posizione in background ogni n secondi con la precisione 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.


1

Che ne dici di provarlo con l' startMonitoringSignificantLocationChanges:API? Sicuramente spara con meno frequenza e la precisione è ragionevolmente buona. Inoltre ha molti più vantaggi rispetto all'utilizzo di altre locationManagerAPI.

Molto di più riguardo a questa API è già stato discusso su questo link


1
Ho provato questo approccio. Viola il requisito di accuratezza - dai documenti : This interface delivers new events only when it detects changes to the device’s associated cell towers
pcoving

1

Devi aggiungere la modalità di aggiornamento della posizione nella tua applicazione aggiungendo la seguente chiave nel tuo info.plist.

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

didUpdateToLocationverrà chiamato il metodo (anche quando la tua app è in background). Puoi eseguire qualsiasi cosa sulla base di questa chiamata al metodo


La modalità di aggiornamento della posizione è già in info.plist. Come ho già detto, ricevo aggiornamenti fino al timeout in background di 180 secondi.
pcoving

0

Dopo iOS 8 ci sono diverse modifiche nel recupero in background e negli aggiornamenti relativi al framework CoreLocation effettuati da Apple.

1) Apple introduce la richiesta AlwaysAuthorization per il recupero dell'aggiornamento della posizione nell'applicazione.

2) Apple introduce backgroundLocationupdates per il recupero della posizione dallo sfondo in iOS.

Per recuperare la posizione in background è necessario abilitare l'aggiornamento della posizione in Funzionalità di 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

Per utilizzare i servizi di localizzazione standard mentre l'applicazione è in background, devi prima attivare le modalità in background nella scheda Funzionalità delle impostazioni di destinazione e selezionare Aggiornamenti della posizione.

Oppure aggiungilo direttamente a Info.plist .

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

Quindi è necessario configurare CLLocationManager

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

Swift

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

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


-2
locationManager.allowsBackgroundLocationUpdates = true

4
Potresti modificare la tua risposta e aggiungere qualche spiegazione perché / come questo risolve il problema?
barbsan
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.