Como obtenho uma atualização de localização em segundo plano a cada n minutos no meu aplicativo iOS?


207

Estou procurando uma maneira de obter uma atualização de localização em segundo plano a cada n minutos no meu aplicativo iOS. Estou usando o iOS 4.3 e a solução deve funcionar em iPhones sem jailbreak.

Tentei / considerei as seguintes opções:

  • CLLocationManager startUpdatingLocation/startMonitoringSignificantLocationChanges: Isso funciona em segundo plano conforme o esperado, com base nas propriedades configuradas, mas não parece possível forçá-lo a atualizar o local a cada n minutos
  • NSTimer: Funciona quando o aplicativo está sendo executado em primeiro plano, mas não parece ter sido projetado para tarefas em segundo plano
  • Notificações locais: as notificações locais podem ser agendadas a cada n minutos, mas não é possível executar algum código para obter a localização atual (sem que o usuário precise iniciar o aplicativo por meio da notificação). Essa abordagem também não parece ser uma abordagem limpa, pois não é para isso que as notificações devem ser usadas.
  • UIApplication:beginBackgroundTaskWithExpirationHandler: Pelo que entendi, isso deve ser usado para concluir algum trabalho em segundo plano (também com tempo limitado) quando um aplicativo é movido para o segundo plano, em vez de implementar processos em segundo plano "de longa execução".

Como posso implementar essas atualizações regulares de localização em segundo plano?




1
Se você está tentando fazê-lo funcionar no iOS 7, você pode tentar esta solução aqui: stackoverflow.com/questions/18946881/… Se você tiver alguma dúvida, poderá se juntar a nós para uma discussão aqui: mobileoop.com/background -location-update-programming-for-ios-7
Ricky

1
Todas as suas descobertas estão corretas (os quatro pontos). Informações valiosas que são, sabendo o que não corresponde ao seu caso de uso? E sim, quando no MODO SUSPENSO ou NÃO EXECUTANDO, não há um método definitivo para atualizar a cada n minutos.
precisa saber é o seguinte

Respostas:


113

Encontrei uma solução para implementar isso com a ajuda dos Fóruns de desenvolvedores da Apple:

  • Especificamos location background mode
  • Crie um NSTimerplano de fundo comUIApplication:beginBackgroundTaskWithExpirationHandler:
  • Quando né menor do UIApplication:backgroundTimeRemainingque funcionará bem. Quando nfor maior , location managerdeve ser ativado (e desativado) novamente antes que não haja tempo restante para evitar que a tarefa em segundo plano seja eliminada.

Isso funciona porque o local é um dos três tipos permitidos de execução em segundo plano .

Nota: perdi algum tempo testando isso no simulador, onde não funciona. No entanto, funciona bem no meu telefone.


24
você tem o link para o fórum? Estou procurando implementar o mesmo tipo de mapeamento de localização e não consegui fazê-lo funcionar. Ou algum código de exemplo seria muito apreciado.
utahwithak

4
Você pode explicar por que a tarefa em segundo plano não é interrompida após 10 minutos (tempo máximo permitido) se você parar e iniciar o gerenciador de localização? é algum tipo de funcionalidade pretendida? soa mais como um bug no Apple SDK, se acontecer assim. Em qual versão do iOS você estava experimentando?
precisa saber é

5
@all: Sim, nosso aplicativo está disponível na AppStore. Não vou publicar todo o código, todos os marcadores mencionados são recursos claramente documentados. Caso esteja com um problema específico, poste sua própria pergunta explicando o que você tentou e o que deu errado.
wjans

3
@ user836026: Sim, é isso que quero dizer com a especificação do modo de segundo plano. Depois de interromper a atualização do local, ela deve ser iniciada novamente em 10 minutos para evitar que o aplicativo seja encerrado.
wjans

12
Para todos os interessados ​​em ver algum código real refletindo o que está sendo discutido aqui, verifique stackoverflow.com/questions/10235203/…
Lolo

55

No iOS 8/9/10 para atualizar o local em segundo plano a cada 5 minutos, faça o seguinte:

  1. Vá para Projeto -> Recursos -> Modos de segundo plano -> selecione Atualizações de local

  2. Vá para Projeto -> Informações -> adicione uma chave NSLocationAlwaysUsageDescription com valor vazio (ou opcionalmente qualquer texto)

  3. Para que o local funcione quando o aplicativo estiver em segundo plano e envie coordenadas para o serviço da Web ou faça qualquer coisa com eles a cada 5 minutos, implemente-o como no código abaixo.

Não estou usando nenhuma tarefa ou temporizador em segundo plano. Testei esse código com meu dispositivo com iOS 8.1, que estava em minha mesa por algumas horas com meu aplicativo em execução em segundo plano. O dispositivo estava bloqueado e o código estava funcionando corretamente o tempo todo.

@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
Isso funciona por mais de algumas horas? Parece que, mesmo que o aplicativo não seja encerrado, o dispositivo pára de enviar atualizações de local após uma hora ou mais do aplicativo ir para segundo plano.
Myxtic

2
A busca em segundo plano está desativada no meu projeto (não importa se está desativado ou ativado). No entanto, pausesLocationUpdatesAutomatically deve ser definido como NO no exemplo acima para funcionar corretamente. Ele não será retomado automaticamente quando você começar a se mover novamente, se tiver sido pausado pelo sistema anteriormente. Por isso, no exemplo acima, defino essa propriedade como NO.
Leszek Szary 03/03

1
Eu acho que você está absolutamente certo. Encontrei mais informações sobre o problema aqui: stackoverflow.com/q/17484352/1048331
Myxtic

2
@LeszekS você precisa adicionar o código abaixo para iOS 9 suporte para o fundo - if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) { [self.locationManager setAllowsBackgroundLocationUpdates:YES]; }
Fenil

2
Qual é a conveniência de fazer isso? Você ainda está drenando a bateria tendo uma precisão alta e constante. A única coisa que você não fazer é que você realmente não usar o já recebeu local até chegar a um intervalo de 5 minutos ...
Mel

34

Eu fiz isso em um aplicativo que estou desenvolvendo. Os cronômetros não funcionam quando o aplicativo está em segundo plano, mas o aplicativo recebe constantemente as atualizações de local. Li em algum lugar da documentação (não consigo encontrá-lo agora, publicarei uma atualização quando o fizer) que um método pode ser chamado apenas em um loop de execução ativo quando o aplicativo está em segundo plano. O delegado do aplicativo tem um loop de execução ativo, mesmo na BG, assim você não precisa criar o seu próprio para fazer isso funcionar. [Não tenho certeza se esta é a explicação correta, mas é assim que eu entendi pelo que li]

Primeiro, adicione o locationobjeto para a chave UIBackgroundModesno info.plist do seu aplicativo. Agora, o que você precisa fazer é iniciar as atualizações de local em qualquer lugar do seu aplicativo:

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

Em seguida, escreva um método para lidar com as atualizações de local, digamos -(void)didUpdateToLocation:(CLLocation*)location, no delegado do aplicativo. Em seguida, implementar o método locationManager:didUpdateLocation:fromLocationde CLLocationManagerDelegatena classe em que você começou a locação (uma vez que definir o gerente delegado local para 'self'). Dentro desse método, é necessário verificar se o intervalo de tempo após o qual você precisa lidar com as atualizações de local passou. Você pode fazer isso salvando o horário atual todas as vezes. Se esse tempo tiver passado, chame o método UpdateLocation do seu representante do aplicativo:

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

Isso chamará seu método a cada 5 minutos, mesmo quando o aplicativo estiver em segundo plano. Imp: Esta implementação gasta a bateria. Se a precisão dos dados de localização não for crítica, você deve usar[locationManager startMonitoringSignificantLocationChanges]

Antes de adicionar isso ao seu aplicativo, leia o Guia de programação de reconhecimento de local


3
Dessa forma, os serviços de localização são ativados constantemente (na verdade, o esgotamento da bateria), não quero isso. Desejo ativar o serviço de localização a cada n minutos e desabilitá-lo imediatamente quando tiver uma boa correção (observei que não expliquei isso claramente na minha pergunta). Eu posso conseguir esse comportamento na solução que descrevi.
wjans

3
Você pode definir a precisão do gerenciador de localização como 1 km - isso deixará sua bateria quase intacta. Após 5 minutos, você define a precisão para 1m. Quando você obtiver um local satisfatório (normalmente depois dos 5s), basta definir a precisão de volta para 1 km.
knagode

knagode, tentei a solução sugerida para o problema de esgotamento da bateria, mas mesmo após aumentar a precisão após N minutos, o método locationManager: didUpdateLocations não é chamado novamente. Tentei startUpdating e stopUpdating, em vez de aumento e precisão diminuição, é chamado com sucesso delegar locationManager: método didUpdateLocations, após N minutos, mas não está a funcionar no modo de fundo ...
Hardik Darji

Quanto aos links da documentação. Consulte aqui : "Os métodos do seu objeto delegado são chamados a partir do encadeamento no qual você iniciou os serviços de localização correspondentes. Esse encadeamento deve ter um loop de execução ativo, como o encontrado no encadeamento principal do aplicativo."
Honey

24

Agora que o iOS6 é a melhor maneira de ter serviços de localização em execução permanente ...

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

}

Apenas testei assim:

Iniciei o aplicativo, entre em segundo plano e movo-me no carro por alguns minutos. Depois vou para casa por 1 hora e começo a me mover novamente (sem abrir novamente o aplicativo). Os locais começaram novamente. Então parou por duas horas e começou de novo. Tudo ok de novo ...

NÃO ESQUEÇA DE USAR os novos serviços de localização no 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 o aplicativo travar ou for morto, o sistema não será reiniciado, certo?
Ken

1
Para um código mais legível, você pode fazer um [locais lastObject]; em vez de [locais objectAtIndex: [locais contam] - 1]
axello 1/12

seu método é apenas no ios6?
Pengwang

Nós temos que usar isso? Eu pensei que apenas tinha o código do gerenciador de localização em viewDidLoad de uma classe e defini uma chave de plano de fundo no arquivo plist que o aplicativo registra para atualizações de local deve ser bom o suficiente? Você pode por favor me ajudar com isso!
Nithinreddy

Sim, ele funcionará como um charme, mas não funcionará depois de 10 minutos, pois o iOS mata longos threads após esse período. Minha solução é perfeita se você deseja que esses serviços sejam iniciados para sempre. Fiz alguns testes há dois dias e consome 6% da bateria a cada hora. Usar [locationManager startUpdatingLocation] irá drenar 17%
Alejandro Luengo

13

Para alguém que está tendo pesadelo, descubra este. Eu tenho uma solução simples.

  1. veja este exemplo em raywenderlich.com -> tem código de amostra, isso funciona perfeitamente, mas infelizmente não há cronômetro durante a localização em segundo plano. isso será executado indefinidamente.
  2. Adicionar 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. Só não se esqueça de adicionar "Registros de aplicativos para atualizações de local" em info.plist.


Isso obtém o local por mais de 3 minutos?
SleepNot

É necessário definir o local em Recursos -> Modo de segundo plano.
Sergio Andreotti

Não está funcionando!! Eu verifiquei no iOS 8 e iOS 10
Kirti

Corrija-me se eu estiver errado. Com base no que li, você inicia o locationManager apenas uma vez. Depois disso, todos os intervalos são redundantes. Uma vez que já foi iniciado
Mel

está funcionando, mas está sendo interrompido após 170 segundos. Eu quero correr minha tarefa em segundo plano por tempo ilimitado
burmman Anshuman

8

Aqui está o que eu 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)
    }
}

Começo o rastreamento no AppDelegate assim:

BackgroundLocationManager.instance.start()

Obrigado. Nosso projeto precisa rastrear a localização do usuário + enviar eventos do PubNub em segundo plano e sua solução está funcionando muito bem.
user921509

Oi Hmitkov, Onde método que eu posso chamar sendLocationToServer para enviar a localização do usuário no servidor
AmanGupta007

@ AmanGupta007 Você pode chamar sendLocationToServer em func locationManager (manager: didUpdateLocations :). Observe o comentário // TODO no código.
hmitkov

@hmitkov É possível iniciar e parar os Serviços de Localização com isso enquanto o aplicativo estiver em segundo plano? Por exemplo, inicie o serviço de localização a partir de uma notificação por push, pegue um lat / long, envie para o serviço da web e pare de atualizar o local. Faça isso sempre que o 'conteúdo disponível' = 1 for incluído no corpo do push.
DookieMan

1
Eu tentei esse código, mas parece que ele não está funcionando no iOS11? (Eu não testá-lo em qualquer outra versão.)
Tom

6

Infelizmente, todas as suas suposições parecem corretas e não acho que haja uma maneira de fazer isso. Para economizar a bateria, os serviços de localização do iPhone são baseados no movimento. Se o telefone estiver em um local, é invisível para os serviços de localização.

A CLLocationManagerchamada será feita apenas locationManager:didUpdateToLocation:fromLocation:quando o telefone receber uma atualização de localização, o que só acontece se um dos três serviços de localização (torre de celular, gps, wifi) perceber uma alteração.

Algumas outras coisas que podem ajudar a informar outras soluções:

  • Iniciar e interromper os serviços faz com que o didUpdateToLocationmétodo delegado seja chamado, mas newLocationpode ter um carimbo de data / hora antigo.

  • O monitoramento de região pode ajudar

  • Ao executar em segundo plano, lembre-se de que pode ser difícil obter o suporte completo ao LocationServices aprovado pela Apple. Pelo que vi, eles foram projetados especificamente startMonitoringSignificantLocationChangescomo uma alternativa de baixo consumo de energia para aplicativos que precisam de suporte de localização em segundo plano e incentivam fortemente os desenvolvedores a usá-lo, a menos que o aplicativo precise absolutamente.

Boa sorte!

ATUALIZAÇÃO: Esses pensamentos podem estar desatualizados até agora. Parece que as pessoas estão tendo sucesso com a resposta @wjans, acima.


1
Existem aplicativos disponíveis na AppStore (como, por exemplo, "Meu Locus"), que permitem obter uma atualização de localização em segundo plano. Eles não mantêm o serviço de localização ativo, mas ele é ativado em breve, de acordo com o intervalo definido. Como eles fazem isso?
Wjans

2
Na situação que você descreve, é provável que o aplicativo use a abordagem startMonitoringSignificantLocationChanges. Aqui, o telefone é temporariamente 'acordado' quando recebe uma atualização de local, mas não há intervalo que você possa definir para 'executar ping' neste serviço em segundo plano. Quando o telefone se move (ou muda de Celular para GPS ou Wifi), ele dispara uma atualização. A palestra de Stanford iTunes U sobre o tema foi muito útil para mim - espero que ele pode ajudar a encontrar uma solução alternativa: itunes.apple.com/us/itunes-u/iphone-application-development/...
Chazbot

2
Thx para o ponteiro. No entanto, ainda não entendo o que o aplicativo está fazendo. Mesmo que meu telefone esteja na minha mesa, sem se mover, posso ver o serviço de localização sendo acionado a cada 10 minutos (exatamente). Se bem entendi, startMonitoringSignificantLocationChangesnão daria nenhuma atualização nesse caso.
Wjans

1
@wjans, como está o consumo de bateria do seu telefone, você notou que ele se esgotou rapidamente, talvez devido ao aplicativo Mylocus?
User836026

6

Escrevi um aplicativo usando os serviços de localização. O aplicativo deve enviar o local a cada 10s. E funcionou muito bem.

Basta usar o método " allowDeferredLocationUpdatesUntilTraveled: timeout ", seguindo o documento da Apple.

O que eu fiz são:

Necessário: registre o modo de segundo plano para atualizar o local.

1. Crie LocationMangere startUpdatingLocation, com accuracye filteredDistancecomo quiser:

-(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 manter o aplicativo em execução para sempre usando o allowDeferredLocationUpdatesUntilTraveled:timeoutmétodo em segundo plano, você deve reiniciar updatingLocationcom um novo parâmetro quando o aplicativo for para o segundo plano, desta forma:

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

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

3. O aplicativo é atualizado como local normalmente com locationManager:didUpdateLocations:retorno de chamada:

-(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. Mas você deve manipular os dados e locationManager:didFinishDeferredUpdatesWithError:retornar a chamada para sua finalidade

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

     _deferringUpdates = NO;

     //do something 
}

5. OBSERVAÇÃO: acho que devemos redefinir os parâmetros de LocationManagercada vez que o aplicativo alterna entre o modo background / forground.


@Todas as soluções desta ou acima da solução explicada pela wjans devo preferir economizar a bateria do dispositivo ou ambas afetam o mesmo?
Yash

2
Tentei as duas soluções mencionadas e vi que a do @ wjans é um pouco mais econômica. Mas, após a chegada do iOS 8, parece que essa solução não funciona mais corretamente. Para mais detalhes: na maioria das vezes, o aplicativo não pode ter vida longa no modo de plano de fundo.
samthui7

@ samthui7 Por que você define pausesLocationUpdatesAutomatically = false?
CedricSoubrie 13/09/16

O requisito do meu aplicativo é continuar enviando userLocation a cada 10s, enquanto pausesLocationUpdatesAutomatically = trueinstrui o gerente de local a interromper as atualizações se aparentemente não houver alteração de local ( documento da Apple ). De qualquer forma, ainda não testei explicitamente o caso location manager pauses updates: D.
samthui7

@CedricSoubrie: a configuração pausesLocationUpdatesAutomatically = truefaz com que meu aplicativo pare de atualizar o local.
samthui7

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

Isso é necessário para o rastreamento de localização em segundo plano desde o iOS 9.


1
Isso salvou meu dia! usando alvo implantação iOS8, com dispositivo iOS9
Yaro

4

Eu usei o método do xs2bush de obter um intervalo (usando timeIntervalSinceDate) e expandi-o um pouco. Eu queria ter certeza de que estava obtendo a precisão necessária e também que não estava ficando sem bateria, mantendo o rádio GPS ligado mais do que o necessário.

Eu mantenho o local em execução continuamente com as seguintes configurações:

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

este é um consumo relativamente baixo da bateria. Quando estiver pronto para ler minha próxima localização periódica, primeiro verifico se o local está dentro da precisão desejada; se estiver, então uso o local. Caso contrário, aumentei a precisão com isso:

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

obtenha minha localização e, depois que tiver a localização, diminuo a precisão novamente para minimizar o consumo da bateria. Escrevi um exemplo completo disso e também escrevi a fonte do código do servidor para coletar os dados de localização, armazená-los em um banco de dados e permitir que os usuários visualizem dados de GPS em tempo real ou recuperem e visualizem rotas armazenadas anteriormente. Tenho clientes para iOS, android, windows phone e java me. Todos os clientes são escritos de forma nativa e todos funcionam corretamente em segundo plano. O projeto é licenciado pelo MIT.

O projeto do iOS está direcionado para o iOS 6 usando um SDK base do iOS 7. Você pode obter o código aqui .

Por favor, registre um problema no github se houver algum problema. Obrigado.


Eu tentei sua solução, mas não está funcionando ... quando o aplicativo vai para o fundo, em seguida, mesmo após o aumento de precisão, app não gettign chamado método didUpdateToLocation no meu caso
Hardik Darji

@HardikDarji pergunta importante. Você está se movendo? Caso contrário, as atualizações de local podem parar. Tente levar o telefone para passear ou dirigir e veja se isso o corrige.
Nickfox 13/03/2014

Obrigado pela sua resposta rápida. Mas quero atualizações de local a cada 2 minutos, sem que meu telefone esteja em movimento ou não. Nesse cenário, o método didUpdateToLocation não é chamado. Estou procurando aqui: Como faço para atualizar a localização a cada n minutos !!!
Hardik Darji

tente definir o timeIntervalInSeconds para 120 e remova o comentário desta linha: locationManager.pausesLocationUpdatesAutomatically = NO;
nickfox

1
a solução que você postou no comentário acima mata a vida útil da bateria ou você ainda está fazendo alguma otimização para isso?
Samfr de

2

Parece que stopUpdatingLocation é o que aciona o timer do watchdog em segundo plano, então substituí-o em didUpdateLocation por:

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

que parece efetivamente desligar o GPS. O seletor para o NSTimer em segundo plano se torna:

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

Tudo o que estou fazendo é alternar periodicamente a precisão para obter uma coordenada de alta precisão a cada poucos minutos e, como o locationManager não foi parado, backgroundTimeRemaining permanece em seu valor máximo. Isso reduziu o consumo da bateria de ~ 10% por hora (com constante kCLLocationAccuracyBest em segundo plano) para ~ 2% por hora no meu dispositivo


2

Existe um cocoapod APScheduledLocationManager que permite obter atualizações de localização em segundo plano a cada n segundos com a precisão da localização desejada.

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

O repositório também contém um aplicativo de exemplo escrito em Swift 3.


você tem algo assim no objetivo c?
Moxarth 6/07

1

No iOS 9 e no watchOS 2.0, há um novo método no CLLocationManager que permite solicitar o local atual: CLLocationManager: requestLocation (). Isso é concluído imediatamente e, em seguida, retorna o local ao delegado CLLocationManager.

Você pode usar um NSTimer para solicitar um local a cada minuto com esse método agora e não precisa trabalhar com os métodos startUpdatingLocation e stopUpdatingLocation.

No entanto, se você deseja capturar locais com base em uma alteração de X metros a partir do último local, basta definir a propriedade distanceFilter de CLLocationManger e chamar X startUpdatingLocation ().


-1

Em anexo é uma solução Swift baseada em:

Definir App registers for location updatesno info.plist

Mantenha o locationManager em execução o tempo todo

Alterne kCLLocationAccuracyentre BestForNavigation(por 5 segundos para obter o local) e ThreeKilometerspelo resto do período de espera para evitar a drenagem da bateria

Este exemplo atualiza o local a cada 1 minuto em primeiro plano e a cada 15 minutos em segundo plano.

O exemplo funciona bem com o Xcode 6 Beta 6, executando em um dispositivo iOS 7.

No App Delegate (mapView é um apontador opcional para o mapView Controller)

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
}

No mapView (locationManager aponta para o objeto no 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")
        }
    }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.