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

207

Estoy buscando una manera de obtener una actualización de ubicación en segundo plano cada n minutos en mi aplicación iOS. Estoy usando iOS 4.3 y la solución debería funcionar para iPhone sin jailbreak.

Intenté / consideré las siguientes opciones:

  • CLLocationManager startUpdatingLocation/startMonitoringSignificantLocationChanges: Esto funciona en segundo plano como se esperaba, en función de las propiedades configuradas, pero parece que no es posible forzarlo a actualizar la ubicación cada n minutos
  • NSTimer: Funciona cuando la aplicación se ejecuta en primer plano, pero no parece estar diseñada para tareas en segundo plano
  • Notificaciones locales: las notificaciones locales se pueden programar cada n minutos, pero no es posible ejecutar algún código para obtener la ubicación actual (sin que el usuario tenga que iniciar la aplicación a través de la notificación). Este enfoque tampoco parece ser un enfoque limpio, ya que esto no es para lo que deberían usarse las notificaciones.
  • UIApplication:beginBackgroundTaskWithExpirationHandler: Según tengo entendido, esto debería usarse para terminar un trabajo en segundo plano (también limitado en el tiempo) cuando una aplicación se mueve a segundo plano en lugar de implementar procesos en segundo plano de "ejecución prolongada".

¿Cómo puedo implementar estas actualizaciones periódicas de ubicación en segundo plano?

wjans
fuente
1
Si está intentando que funcione en iOS 7, puede probar esta solución aquí: stackoverflow.com/questions/18946881/… Si tiene alguna pregunta, puede unirse a nosotros para una discusión aquí: mobileoop.com/background -location-update-programacion-para-ios-7
Ricky
1
Todos sus hallazgos son correctos (los cuatro puntos). Información valiosa que es entonces, sabiendo lo que no coincide con su caso de uso? Y sí, cuando está en MODO SUSPENDIDO o NO EN EJECUCIÓN, no hay un método definitivo para actualizar cada n minutos.
LenArt

Respuestas:

113

Encontré una solución para implementar esto con la ayuda de los foros de desarrolladores de Apple:

  • Especificar location background mode
  • Crea un NSTimerfondo en el fondo conUIApplication:beginBackgroundTaskWithExpirationHandler:
  • Cuando nes más pequeño que UIApplication:backgroundTimeRemainingfuncionará bien. Cuando nes más grande , se location managerdebe habilitar (y deshabilitar) nuevamente antes de que no quede tiempo para evitar que se elimine la tarea en segundo plano.

Esto funciona porque la ubicación es uno de los tres tipos permitidos de ejecución en segundo plano .

Nota: Perdí algo de tiempo probando esto en el simulador donde no funciona. Sin embargo, funciona bien en mi teléfono.

wjans
fuente
24
¿Tienes el enlace al foro? Estoy buscando implementar el mismo tipo de mapeo de ubicación y no he podido hacerlo funcionar. O algún código de muestra sería muy apreciado.
utahwithak
44
¿Puede explicar por qué la tarea en segundo plano no se elimina después de 10 minutos (tiempo máximo permitido) si simplemente se detiene e inicia el administrador de ubicación? ¿Es algún tipo de funcionalidad prevista? suena más como un error en el SDK de Apple si sucede así. ¿En qué versión de iOS lo estabas probando?
saurabh
55
@todos: Sí, nuestra aplicación está disponible en la AppStore. No voy a publicar todo el código, todos los puntos mencionados son características claramente documentadas. En caso de que tenga un problema específico, publique su propia pregunta explicando qué ha intentado y qué sale mal.
wjans
3
@ user836026: Sí, eso es lo que quiero decir al especificar el modo de fondo. Después de detener la actualización de ubicación, debe iniciarse nuevamente dentro de los 10 minutos para evitar que la aplicación se cierre.
wjans
12
Para todos aquellos interesados ​​en ver un código real que refleje lo que se está discutiendo aquí, consulte stackoverflow.com/questions/10235203/…
Lolo
55

En iOS 8/9/10 para hacer una actualización de la ubicación en segundo plano cada 5 minutos, haga lo siguiente:

  1. Vaya a Proyecto -> Capacidades -> Modos de fondo -> seleccione Actualizaciones de ubicación

  2. Vaya a Proyecto -> Información -> agregue una clave NSLocationAlwaysUsageDescription con un valor vacío (u opcionalmente cualquier texto)

  3. Para que la ubicación funcione cuando su aplicación esté en segundo plano y envíe coordenadas al servicio web o haga algo con ellos cada 5 minutos, impleméntela como en el código a continuación.

No estoy usando ninguna tarea de fondo o temporizadores. Probé este código con mi dispositivo con iOS 8.1, que estuvo en mi escritorio durante unas horas con mi aplicación ejecutándose en segundo plano. El dispositivo estaba bloqueado y el código se ejecutaba correctamente todo el tiempo.

@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
fuente
3
¿Funciona esto por más de unas pocas horas? Parece que incluso si la aplicación no se termina, el dispositivo deja de enviar actualizaciones de ubicación después de una hora más o menos de que la aplicación pase al fondo.
Myxtic
2
La recuperación de fondo está deshabilitada en mi proyecto (no debería importar si está apagada o encendida). Sin embargo, pausesLocationUpdatesAutomatically debe establecerse en NO para que el ejemplo anterior funcione correctamente. NO se reanudará automáticamente una vez que comience a moverse nuevamente si el sistema lo detuvo previamente. Es por eso que en el ejemplo anterior establecí esta propiedad en NO.
Leszek Szary
1
Creo que tiene toda la razón. Encontré más información sobre el problema aquí: stackoverflow.com/q/17484352/1048331
Myxtic
2
@LeszekS necesita agregar el siguiente código para el soporte de iOS 9 para el fondo - if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) { [self.locationManager setAllowsBackgroundLocationUpdates:YES]; }
Fenil
2
¿Cuál es la conveniencia de hacer esto? Todavía está agotando la batería al tener una alta precisión constante. Lo único que no debes hacer es no utilizar la ubicación ya recibida hasta que alcances un intervalo de 5 minutos ...
Miel
34

Hice esto en una aplicación que estoy desarrollando. Los temporizadores no funcionan cuando la aplicación está en segundo plano, pero la aplicación recibe constantemente las actualizaciones de ubicación. Leí en alguna parte de la documentación (parece que no puedo encontrarla ahora, publicaré una actualización cuando lo haga) que un método solo se puede llamar en un ciclo de ejecución activo cuando la aplicación está en segundo plano. El delegado de la aplicación tiene un ciclo de ejecución activo incluso en el BG, por lo que no necesita crear el suyo propio para que esto funcione. [No estoy seguro si esta es la explicación correcta, pero así es como entendí por lo que leí]

En primer lugar, agregue el locationobjeto para la clave UIBackgroundModesen la lista de información de su aplicación. Ahora, lo que debe hacer es iniciar las actualizaciones de ubicación en cualquier lugar de su aplicación:

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

A continuación, escriba un método para manejar las actualizaciones de ubicación, por ejemplo -(void)didUpdateToLocation:(CLLocation*)location, en el delegado de la aplicación. Luego implemente el método locationManager:didUpdateLocation:fromLocationde CLLocationManagerDelegateen la clase en la que inició el administrador de ubicación (ya que configuramos el delegado del administrador de ubicación en 'self'). Dentro de este método, debe verificar si ha transcurrido el intervalo de tiempo después del cual debe manejar las actualizaciones de ubicación. Puede hacer esto guardando la hora actual cada vez. Si ha transcurrido ese tiempo, llame al método UpdateLocation desde el delegado de su aplicación:

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

Esto llamará a su método cada 5 minutos, incluso cuando su aplicación esté en segundo plano. Imp: esta implementación agota la batería, si la precisión de los datos de su ubicación no es crítica, debe usar[locationManager startMonitoringSignificantLocationChanges]

Antes de agregar esto a su aplicación, lea la Guía de programación de concientización de ubicación

Bushra Shahid
fuente
3
De esa manera, los servicios de ubicación están constantemente habilitados (de hecho, la batería se está agotando), no quiero eso. Quiero habilitar el servicio de ubicación cada n minutos e inmediatamente deshabilitarlo cuando tenga una buena solución (solo noté que no lo expliqué claramente en mi pregunta). Puedo lograr este comportamiento en la solución que describí.
wjans
3
Puede establecer la precisión del administrador de ubicación en 1 km; esto dejará su batería casi intacta. Después de 5 minutos, establece la precisión en 1 m. Cuando obtenga una ubicación satisfactoria (normalmente después de 5 segundos) simplemente vuelva a establecer la precisión en 1 km.
knagode
knagode, probé su solución sugerida para el problema del agotamiento de la batería, pero incluso después de aumentar la precisión después de N minutos, no se vuelve a llamar al método locationManager: didUpdateLocations. Intenté startUpdating y stopUpdating, en lugar de aumentar y disminuir la precisión, se llama delegar con éxito locationManager: método didUpdateLocations, después de N minutos, pero no funciona en modo MODO en segundo plano ...
Hardik Darji
En cuanto a los enlaces de documentación. Consulte aquí : "Los métodos de su objeto delegado se invocan desde el hilo en el que inició los servicios de ubicación correspondientes. Ese hilo debe tener un ciclo de ejecución activo, como el que se encuentra en el hilo principal de su aplicación".
Miel
24

Ahora que iOS6 está fuera, la mejor manera de tener servicios de ubicación que se ejecutan para siempre es ...

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

}

Solo lo probé así:

Inicié la aplicación, pasé a segundo plano y me moví en el auto unos minutos. Luego me voy a casa por 1 hora y empiezo a moverme nuevamente (sin abrir nuevamente la aplicación). Las ubicaciones comenzaron de nuevo. Luego se detuvo durante dos horas y comenzó de nuevo. Todo bien otra vez ...

NO OLVIDES USAR los nuevos servicios de ubicación en 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
fuente
1
Si la aplicación se bloquea o se cierra, el sistema no se reinicia, ¿verdad?
Ken
1
Para obtener un código más legible, puede hacer un [ubicaciones lastObject]; en lugar de [ubicaciones objectAtIndex: [ubicaciones cuentan] - 1]
axello
su método es solo en ios6?
pengwang
¿Tenemos que usar esto? Pensé que solo tenía el código del administrador de ubicación en viewDidLoad de una clase, y he establecido una clave de fondo en el archivo plist que la aplicación registra para las actualizaciones de ubicación ¿debería ser lo suficientemente bueno? ¿Me pueden ayudar con esto?
nithinreddy
Sí, funcionará como un encanto nithinreddy, pero dejará de funcionar después de 10 minutos ya que iOS mata hilos largos después de ese período. Mi solución es perfecta si desea que esos servicios se inicien para siempre. Hice algunas pruebas hace dos días y agota el 6% de la batería cada hora. El uso de [locationManager startUpdatingLocation] en su lugar reducirá el 17%
Alejandro Luengo
13

Para alguien más que tenga pesadilla, descubra este. Tengo una solución simple

  1. mira este ejemplo de raywenderlich.com -> tiene código de muestra, esto funciona perfectamente, pero desafortunadamente no hay temporizador durante la ubicación en segundo plano. esto se ejecutará indefinidamente.
  2. Agregar temporizador 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. Simplemente no olvide agregar "Registros de aplicaciones para actualizaciones de ubicación" en info.plist.

HelmiB
fuente
¿Esto obtiene la ubicación durante más de 3 minutos?
SleepNot
Debe establecer la ubicación en Capacidades -> Modo de fondo.
Sergio Andreotti
¡¡No funciona!! He comprobado en iOS 8 y iOS 10
Kirti
Corrígeme si me equivoco. Según lo que leí, inicias locationManager solo una vez. Después de eso, todos los intervalos son redundantes. Como ya se ha comenzado
Miel
está funcionando pero se detiene después de 170 segundos. Quiero correr mi tarea en segundo plano por tiempo ilimitado
burmman Anshuman
8

Esto es lo que 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)
    }
}

Comienzo el seguimiento en AppDelegate así:

BackgroundLocationManager.instance.start()
hmitkov
fuente
Gracias. Nuestro proyecto necesita rastrear la ubicación del usuario + enviar eventos de PubNub en segundo plano y su solución funciona realmente bien.
user921509
Hola Hmitkov, ¿Dónde puedo llamar? Método sendLocationToServer para enviar la ubicación del usuario en el servidor
AmanGupta007
@ AmanGupta007 Puede llamar a sendLocationToServer en func locationManager (administrador: didUpdateLocations :). Observe el // comentario TODO en el código.
hmitkov
@hmitkov ¿Es posible iniciar y detener los servicios de ubicación con esto mientras la aplicación está en segundo plano? Ejemplo, inicie el servicio de ubicación desde una notificación push, tome algunos lat / long, envíelos al servicio web y luego deje de actualizar la ubicación. Haga esto cada vez que el 'contenido disponible' = 1 se incluya en el cuerpo de inserción.
DookieMan
1
¿He probado este código pero parece que no funciona en iOS11? (No lo he probado en ninguna otra versión.)
Tom
6

Desafortunadamente, todas sus suposiciones parecen correctas, y no creo que haya una manera de hacerlo. Para ahorrar batería, los servicios de ubicación del iPhone se basan en el movimiento. Si el teléfono se encuentra en un lugar, es invisible para los servicios de ubicación.

La CLLocationManagerúnica llamará locationManager:didUpdateToLocation:fromLocation:cuando el teléfono recibe una actualización de posición, que sólo ocurre si uno de los tres servicios de localización (antena de telefonía móvil, GPS, WiFi) percibe un cambio.

Algunas otras cosas que podrían ayudar a informar soluciones adicionales:

  • Al iniciar y detener los servicios, didUpdateToLocationse llama al método delegado, pero es newLocationposible que tenga una marca de tiempo antigua.

  • El monitoreo regional podría ayudar

  • Cuando se ejecute en segundo plano, tenga en cuenta que puede ser difícil obtener el soporte "completo" de LocationServices aprobado por Apple. Por lo que he visto, se han diseñado específicamente startMonitoringSignificantLocationChangescomo una alternativa de bajo consumo de energía para aplicaciones que necesitan soporte de ubicación en segundo plano, y recomiendan encarecidamente a los desarrolladores que lo usen a menos que la aplicación lo necesite absolutamente.

¡Buena suerte!

ACTUALIZACIÓN: Estos pensamientos pueden estar desactualizados por ahora. Parece que las personas están teniendo éxito con la respuesta de @wjans, arriba.

Chazbot
fuente
1
Hay aplicaciones disponibles en la AppStore (como por ejemplo "My Locus") que permiten obtener una actualización de ubicación en segundo plano. No mantienen activo el servicio de ubicación, pero solo se habilita en breve según el intervalo definido. ¿Cómo lo hacen?
wjans
2
En la situación que describe, lo más probable es que la aplicación utilice el enfoque startMonitoringSignificantLocationChanges. Aquí, el teléfono se 'despierta' temporalmente cuando recibe una actualización de ubicación, pero no hay intervalo que pueda configurar para 'hacer ping' a este servicio en segundo plano. Cuando el teléfono se mueve (o cambia de Celular a GPS o Wifi), desencadena una actualización. La conferencia Stanford iTunes U sobre el tema fue realmente útil para mí, espero que pueda ayudarlo a encontrar una solución alternativa: itunes.apple.com/us/itunes-u/iphone-application-development/…
Chazbot
2
Gracias por el puntero. Sin embargo, todavía no entiendo lo que está haciendo la aplicación. Incluso si mi teléfono está en mi escritorio, sin moverse, puedo ver que el servicio de ubicación se activa cada 10 minutos (exactamente). Si entiendo correctamente, startMonitoringSignificantLocationChangesno daría ninguna actualización en ese caso.
wjans
1
@wjans, ¿cómo es el consumo de batería de su teléfono? ¿Notó que se agotó rápidamente tal vez debido a la aplicación Mylocus?
user836026
6

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.

Lo que hice son:

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

1. Crea LocationMangery startUpdatingLocation, con accuracyy filteredDistancecomo 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 utilizando el allowDeferredLocationUpdatesUntilTraveled:timeoutmétodo en segundo plano, debe reiniciar updatingLocationcon un nuevo parámetro cuando la aplicación se mueva a segundo plano, de esta manera:

- (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 se actualizaLocalizaciones de forma normal con locationManager:didUpdateLocations:devolución de llamada:

-(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 locationManager:didFinishDeferredUpdatesWithError:devolución de llamada 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 LocationManagercada vez que la aplicación cambia entre modo de fondo / fondo.

samthui7
fuente
@ ¿En qué solución, esta o la solución explicada de wjans, debería preferir ahorrar la batería del dispositivo o ambas afectarán lo mismo?
Yash
2
Probé ambas soluciones que mencionas, y vi que la de @ wjans es un poco más ahorro de batería. Pero, después de la llegada de iOS 8, parece que esa solución ya no funciona correctamente. Para más detalles: la mayoría de las veces, la aplicación no puede tener una larga vida en modo de fondo.
samthui7
@ samthui7 ¿Por qué establece pausesLocationUpdatesAutomatically = false?
CedricSoubrie
El requisito de mi aplicación es seguir enviando userLocation cada 10 segundos, mientras que pausesLocationUpdatesAutomatically = truele dice al gerente de ubicación que pause las actualizaciones si aparentemente no hay cambio de ubicación ( documento de Apple ). De todos modos, location manager pauses updatestodavía no probé explícitamente el caso de : D.
samthui7
@CedricSoubrie: la configuración pausesLocationUpdatesAutomatically = truehace que mi aplicación deje de actualizar la ubicación.
samthui7
5
if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) {
    [self.locationManager setAllowsBackgroundLocationUpdates:YES];
}

Esto es necesario para el seguimiento de la ubicación en segundo plano desde iOS 9.

Nilesh
fuente
1
Esto me salvó el día! usando el objetivo de implementación iOS8, con el dispositivo iOS9
Yaro
4

Usé el método de xs2bush para obtener un intervalo (usando timeIntervalSinceDate) y lo expandí un poco. Quería asegurarme de que estaba obteniendo la precisión requerida que necesitaba y también que no estaba agotando la batería manteniendo la radio gps encendida más de lo necesario.

Mantengo la ubicación ejecutándose continuamente con la siguiente configuración:

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

Esta es una descarga relativamente baja en la batería. Cuando estoy listo para obtener mi próxima lectura periódica de ubicación, primero verifico si la ubicación está dentro de mi precisión deseada, si es así, entonces uso la ubicación. Si no es así, entonces aumento la precisión con esto:

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

obtener mi ubicación y luego, una vez que tengo la ubicación, vuelvo a bajar la precisión para minimizar el agotamiento de la batería. He escrito una muestra de trabajo completa de esto y también he escrito la fuente del código del lado del servidor para recopilar los datos de ubicación, almacenarlos en una base de datos y permitir a los usuarios ver datos gps en tiempo real o recuperar y ver rutas almacenadas anteriormente. Tengo clientes para iOS, Android, Windows Phone y Java. Todos los clientes están escritos de forma nativa y todos funcionan correctamente en segundo plano. El proyecto tiene licencia del MIT.

El proyecto iOS está dirigido a iOS 6 utilizando un SDK base de iOS 7. Puede obtener el código aquí .

Presente un problema en github si ve algún problema con él. Gracias.

nickfox
fuente
Probé su solución, pero no funciona ... cuando la aplicación pasa a segundo plano, incluso después de aumentar la precisión, la aplicación no se llama método didUpdateToLocation en mi caso
Hardik Darji
@HardikDarji pregunta importante. ¿Te estas moviendo? De lo contrario, las actualizaciones de ubicación pueden detenerse. Intente sacar su teléfono a caminar o conducir y vea si eso lo soluciona.
nickfox
Gracias por su rápida respuesta. Pero quiero actualizaciones de ubicación cada 2 minutos, sin que mi teléfono se mueva o no. En este escenario, no se llama al método didUpdateToLocation. Estoy buscando aquí: ¿Cómo obtengo la actualización de ubicación cada n minutos!
Hardik Darji
intente establecer timeIntervalInSeconds en 120 y descomente esta línea: locationManager.pausesLocationUpdatesAutomatically = NO;
nickfox
1
¿La solución que publicaste en el comentario anterior mata la vida de la batería, o todavía estás haciendo alguna optimización para eso?
samfr
2

Parece que stopUpdatingLocation es lo que activa el temporizador de vigilancia de fondo, por lo que lo reemplacé en didUpdateLocation con:

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

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

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

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

Amit Shelgaonkar
fuente
2

Hay un APScheduledLocationManager de cocoapod 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
¿Tienes algo como esto en el objetivo c?
Moxarth
1

En iOS 9 y watchOS 2.0 hay un nuevo método en CLLocationManager que le permite solicitar la ubicación actual: CLLocationManager: requestLocation (). Esto se completa inmediatamente y luego devuelve la ubicación al delegado CLLocationManager.

Puede usar un NSTimer para solicitar una ubicación cada minuto con este método ahora y no tiene que trabajar con los métodos startUpdatingLocation y stopUpdatingLocation.

Sin embargo, si desea capturar ubicaciones en función de un cambio de X metros desde la última ubicación, simplemente configure la propiedad distanceFilter de CLLocationManger y para que X llame a startUpdatingLocation ().

Malcolm
fuente
-1

Attached es una solución Swift basada en:

Definir App registers for location updatesen la info.plist

Mantenga el administrador de ubicaciones funcionando todo el tiempo

Cambie kCLLocationAccuracyentre BestForNavigation(durante 5 segundos para obtener la ubicación) y ThreeKilometersdurante el resto del período de espera para evitar el drenaje de la batería

Este ejemplo actualiza la ubicación cada 1 minuto en primer plano y cada 15 minutos en segundo plano.

El ejemplo funciona bien con Xcode 6 Beta 6, ejecutándose en un dispositivo iOS 7.

En el delegado de la aplicación (mapView es una opción que apunta al controlador 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
}

En mapView (locationManager apunta al objeto en 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
fuente