Actualizaciones periódicas de la ubicación en segundo plano de iOS

91

Estoy escribiendo una aplicación que requiere actualizaciones de ubicación en segundo plano con alta precisión y baja frecuencia . La solución parece ser una tarea NSTimer en segundo plano que inicia las actualizaciones del administrador de ubicación, que luego se apaga inmediatamente. Esta pregunta se ha hecho antes:

¿Cómo obtengo una actualización de la ubicación en segundo plano cada n minutos en mi aplicación iOS?

Obtener la ubicación del usuario cada n minutos después de que la aplicación pasa a segundo plano

iOS No es el problema típico del temporizador de seguimiento de ubicación en segundo plano

Temporizador de fondo de larga duración de iOS con modo de fondo de "ubicación"

Servicio en segundo plano de tiempo completo de iOS basado en el seguimiento de la ubicación

pero todavía tengo que conseguir un ejemplo mínimo que funcione. Después de probar cada permutación de las respuestas aceptadas anteriores, armé un punto de partida. Ingresando antecedentes:

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

y el método delegado:

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

    NSLog(@"%@", newLocation);

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

}

El comportamiento actual es que se backgroundTimeRemainingreduce de 180 segundos a cero (mientras se registra la ubicación), y luego se ejecuta el controlador de vencimiento y no se generan más actualizaciones de ubicación. ¿Cómo modifico el código anterior para recibir actualizaciones periódicas de ubicación en segundo plano de forma indefinida ?

Actualización : estoy apuntando a iOS 7 y parece haber alguna evidencia de que las tareas en segundo plano se comportan de manera diferente:

Inicie el Administrador de ubicaciones en iOS 7 desde la tarea en segundo plano

picar
fuente
No menciona a qué versión de iOS se dirige. iOS 7 (por ejemplo) tiene un sistema multitarea diferente al de su predecesor.
Jason Whitehorn
@JasonWhitehorn Buen punto. Actualicé la pregunta.
Disponible el
2
@pcoving: ¿Funcionará su solución incluso si la aplicación se mata o termina?
Nirav

Respuestas:

62

Parece que eso stopUpdatingLocationes lo que activa el temporizador de vigilancia en segundo plano, así que lo reemplacé didUpdateLocationcon:

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

que parece apagar efectivamente el GPS. El selector para el fondo NSTimerse convierte en:

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

Todo lo que hago es alternar periódicamente la precisión para obtener una coordenada de alta precisión cada pocos minutos y, como locationManagerno se ha detenido, backgroundTimeRemainingpermanece en su valor máximo. Esto redujo el consumo de batería de ~ 10% por hora (con constante kCLLocationAccuracyBesten segundo plano) a ~ 2% por hora en mi dispositivo.

picar
fuente
4
¿Podría actualizar su pregunta con el ejemplo de trabajo completo? Algo así como: "ACTUALIZACIÓN: he descubierto Aquí está mi solución ...."
smholloway
@Virussmca Volví a una respuesta anterior que es mucho más robusta con un rendimiento similar. Detener las actualizaciones de ubicación por completo parece desencadenar una condición de carrera (o algún otro escenario no determinista).
2013
Este enfoque no funcionará como esperaba: 1) Aumentar el valor de la propiedad distanceFilter no da como resultado un ahorro de batería porque gps aún tiene que seguir calculando su ubicación. 2) En iOS 7, el tiempo de fondo no es contiguo. 3) Puede tener en cuenta los importantes servicios de ChangeLocation. 4) Aparte de su soution en iOS 7, no puede iniciar UIBackgroundModes - ubicación desde el fondo ...
aMother
2
@aMother 1) Me doy cuenta de que aumentar el filtro de distancia (y no procesar las devoluciones de llamada) no ahorra mucha batería. Sin embargo, setDesiredAccuracy sí. Se basa en la información de la torre de telefonía móvil, que es básicamente gratuita. 2) ¿No contiguo? 3) He especificado, en negrita, que necesito una alta precisión. Los cambios de ubicación significativos no proporcionan esto. 4) Los servicios de ubicación no se inician / detienen desde el fondo.
partir del
2
Sé que esta es una publicación antigua ahora, pero los usuarios de mi aplicación que usan este truco han notado un aumento significativo en el uso de la batería desde la actualización a iOS9. No he investigado todavía, pero tengo la sensación de que este 'truco' puede que ya no funcione.
Gary Wright
45

Escribí una aplicación usando los servicios de ubicación, la aplicación debe enviar la ubicación cada 10 segundos. Y funcionó muy bien.

Simplemente use el método " allowDeferredLocationUpdatesUntilTraveled: timeout ", siguiendo el documento de Apple.

Los pasos son los siguientes:

Obligatorio: registre el modo de fondo para actualizar la ubicación.

1. Crea LocationManger y startUpdatingLocation, con precisión y FilterDistance como lo que quieras:

-(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. Para que la aplicación se ejecute para siempre con el método "allowDeferredLocationUpdatesUntilTraveled: timeout" en segundo plano, debe reiniciar updateLocation con un nuevo parámetro cuando la aplicación pasa a segundo plano, así:

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

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

3. La aplicación obtiene updatedLocations como de costumbre con la devolución de llamada "locationManager: didUpdateLocations:":

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

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

4. Pero debe manejar los datos en la devolución de llamada "locationManager: didFinishDeferredUpdatesWithError:" para su propósito

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

     _deferringUpdates = NO;

     //do something 
}

5. NOTA: Creo que deberíamos restablecer los parámetros de LocationManager cada vez que la aplicación cambia entre el modo de fondo / primer plano.

samthui7
fuente
¡Esto funciona muy bien y creo que es la manera correcta de hacerlo, como se mencionó en la WWDC el año pasado! Gracias por la publicación
prasad1250
@ samthui7 gracias, esto funciona, pero también necesito obtener la ubicación después de la terminación. cómo puede ser.??
Mohit Tomar
@ samthui7 .. por favor vea esta que ... stackoverflow.com/questions/37338466/…
Suraj Sukale
2
@amar: También comprobé la versión beta de iOS 10, todavía funciona. Tenga en cuenta que necesitamos agregar algo de código antes de startUpdatingLocation: allowsBackgroundLocationUpdates = YESy requestAlwaysAuthorizationo requestWhenInUseAuthorization. Por ejemplo: `CLLocationManager * locationManager = [[CLLocationManager alloc] init]; locationManager.delegate = self; locationManager.allowsBackgroundLocationUpdates = YES; [locationManager requestAlwaysAuthorization]; [locationManager startUpdatingLocation]; `
samthui7
1
@Nirav No. No creo que Apple pueda permitirnos hacer eso, hasta donde yo sé.
samthui7
38
  1. Si tiene UIBackgroundModesen su plist con locationclave, entonces no necesita usar el beginBackgroundTaskWithExpirationHandlermétodo. Eso es redundante. También lo está usando incorrectamente (vea aquí ) pero eso es discutible ya que su plist está configurado.

  2. Con UIBackgroundModes locationel plist, la aplicación seguirá ejecutándose en segundo plano indefinidamente solo mientras se CLLocationMangeresté ejecutando. Si llamasstopUpdatingLocation mientras está en segundo plano, la aplicación se detendrá y no volverá a comenzar.

    Tal vez podría llamar beginBackgroundTaskWithExpirationHandlerjusto antes de llamar stopUpdatingLocationy luego, después de llamar startUpdatingLocation, podría llamar alendBackgroundTask para mantenerlo en segundo plano mientras el GPS está detenido, pero nunca lo intenté, es solo una idea.

    Otra opción (que no he probado) es mantener el administrador de ubicación en ejecución mientras está en segundo plano, pero una vez que obtenga una ubicación precisa, cambie la desiredAccuracypropiedad a 1000 mo más para permitir que el chip GPS se apague (para ahorrar batería). Luego, 10 minutos más tarde, cuando necesite otra actualización de ubicación, cambie la parte desiredAccuracyposterior a 100 m para encender el GPS hasta que obtenga una ubicación precisa, repita.

  3. Cuando llame startUpdatingLocational administrador de ubicación, debe darle tiempo para obtener un puesto. No debe llamar de inmediato stopUpdatingLocation. Dejamos que se ejecute durante un máximo de 10 segundos o hasta que obtengamos una ubicación de alta precisión sin caché.

  4. Debe filtrar las ubicaciones almacenadas en caché y verificar la precisión de la ubicación que obtiene para asegurarse de que cumpla con la precisión mínima requerida (consulte aquí ). La primera actualización que reciba puede tener 10 minutos o 10 días de antigüedad. La primera precisión que obtenga puede ser de 3000 m.

  5. En su lugar, considere usar las API de cambios de ubicación significativos. Una vez que reciba la notificación de cambio significativo, puede comenzar CLLocationManagerdurante unos segundos para obtener una posición de alta precisión. No estoy seguro, nunca he utilizado los servicios de cambio de ubicación significativo.

progrmr
fuente
La CoreLocationreferencia de Apple parece recomendar el uso de beginBackgroundTaskWithExpirationHandleral procesar datos de ubicación en segundo plano: "Si una aplicación de iOS necesita más tiempo para procesar los datos de ubicación, puede solicitar más tiempo de ejecución en segundo plano utilizando el método beginBackgroundTaskWithName: expirationHandler: de la clase UIApplication". - developer.apple.com/library/ios/documentation/UserExperience/…
eliocs
4
no olvide agregar _locationManager.allowsBackgroundLocationUpdates = YES; para iOS9 +, de lo contrario, la aplicación se
suspenderá
@kolyancz Lo acabo de probar en iOS 10.1.1 para iPhone 6+. Tomó aproximadamente ~ 17 minutos para que se durmiera y se activara locationManagerDidPauseLocationUpdates. ¡Verifiqué ese comportamiento 10 veces!
Miel
Realmente no entiendo por qué usted está diciendo que es discutible y luego dices que debe envolver dentro de un backgroundTask ...
Miel
10

Cuando llega el momento de iniciar el servicio de ubicación y detener la tarea en segundo plano, la tarea en segundo plano debe detenerse con un retraso (1 segundo debería ser suficiente). De lo contrario, el servicio de ubicación no se iniciará. Además, el servicio de ubicación debe dejarse encendido durante un par de segundos (por ejemplo, 3 segundos).

Hay un Cocoapod APScheduledLocationManager que permite obtener actualizaciones de ubicación en segundo plano cada n segundos con la precisión de ubicación deseada.

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

El repositorio también contiene una aplicación de ejemplo escrita en Swift 3.

faja
fuente
1

¿Qué tal intentarlo con startMonitoringSignificantLocationChanges:API? Definitivamente dispara con menos frecuencia y la precisión es razonablemente buena. Además, tiene muchas más ventajas que usar otras locationManagerAPI.

Ya se ha discutido mucho más sobre esta API en este enlace

thatzprem
fuente
1
Probé este enfoque. Se viola el requisito de precisión - a partir de los documentos : This interface delivers new events only when it detects changes to the device’s associated cell towers
pcoving
1

Debe agregar el modo de actualización de ubicación en su aplicación agregando la siguiente clave en su info.plist.

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

didUpdateToLocationse llamará al método (incluso cuando su aplicación esté en segundo plano). Puede realizar cualquier cosa sobre la base de esta llamada al método

aahsanali
fuente
El modo de actualización de ubicación ya está en info.plist. Como mencioné, recibo actualizaciones hasta el tiempo de espera en segundo plano de 180 segundos.
partir del
0

Después de iOS 8, hay varios cambios en la recuperación de antecedentes y las actualizaciones relacionadas con el marco CoreLocation realizadas por Apple.

1) Apple introduce la solicitud AlwaysAuthorization para obtener la actualización de ubicación en la aplicación.

2) Apple introduce backgroundLocationupdates para obtener la ubicación desde el fondo en iOS.

Para obtener la ubicación en segundo plano, debe habilitar la Actualización de ubicación en Capacidades de 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
    }

}
Tecnología
fuente
0

Para usar los servicios de ubicación estándar mientras la aplicación está en segundo plano, primero debe activar Modos de fondo en la pestaña Capacidades de la configuración de Destino y seleccionar Actualizaciones de ubicación.

O agréguelo directamente 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>

Entonces necesitas configurar CLLocationManager

C objetivo

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

Rápido

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

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

Señor Javed Multani
fuente
-2
locationManager.allowsBackgroundLocationUpdates = true
Papon Smc
fuente
4
¿Podría editar su respuesta y agregar alguna explicación de por qué / cómo esto resuelve el problema?
barbsan