Atualizações periódicas de localização em segundo plano no iOS

91

Estou escrevendo um aplicativo que requer atualizações de localização em segundo plano com alta precisão e baixa frequência . A solução parece ser uma tarefa NSTimer em segundo plano que inicia as atualizações do gerenciador de localização, que então é encerrada imediatamente. Esta pergunta já foi feita antes:

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

Obter a localização do usuário a cada n minutos após o aplicativo entrar em segundo plano

iOS Não é o problema típico de cronômetro de rastreamento de localização em segundo plano

Temporizador de fundo de longa duração do iOS com modo de fundo "local"

Serviço de segundo plano em tempo integral iOS com base no rastreamento de localização

mas ainda tenho que fazer um exemplo mínimo funcionar. Depois de tentar todas as permutações das respostas aceitas acima, montei um ponto de partida. Inserindo fundo:

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    self.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        NSLog(@"ending background task");
        [[UIApplication sharedApplication] endBackgroundTask:self.bgTask];
        self.bgTask = UIBackgroundTaskInvalid;
    }];


    self.timer = [NSTimer scheduledTimerWithTimeInterval:60
                                                  target:self.locationManager
                                                selector:@selector(startUpdatingLocation)
                                                userInfo:nil
                                                 repeats:YES];
}

e o 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];

}

O comportamento atual é que os backgroundTimeRemainingdecréscimos de 180 segundos para zero (durante o registro do local) e, em seguida, o manipulador de expiração é executado e nenhuma outra atualização de local é gerada. Como modifico o código acima para receber atualizações periódicas de localização em segundo plano indefinidamente ?

Atualização : estou visando o iOS 7 e parece haver alguma evidência de que as tarefas em segundo plano se comportam de maneira diferente:

Inicie o Location Manager no iOS 7 a partir da tarefa em segundo plano

em movimento
fonte
Você não menciona a versão do iOS que deseja. O iOS 7 (por exemplo) tem um sistema multitarefa diferente de seu predecessor.
Jason Whitehorn
@JasonWhitehorn Bom argumento. Eu atualizei a pergunta.
andamento em
2
@pcoving: sua solução funcionará mesmo se o aplicativo for interrompido ou encerrado?
Nirav

Respostas:

62

Parece que stopUpdatingLocationé isso que aciona o cronômetro de vigilância em segundo plano, então eu o substituí didUpdateLocationpor:

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

que parece efetivamente desligar o GPS. O seletor para o fundo NSTimertorna-se então:

- (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 locationManagernão foi interrompido, backgroundTimeRemainingpermanece em seu valor máximo. Isso reduziu o consumo de bateria de ~ 10% por hora (com constante kCLLocationAccuracyBestem segundo plano) para ~ 2% por hora no meu dispositivo.

em movimento
fonte
4
Você poderia atualizar sua pergunta com o exemplo completo de trabalho? Algo como: "ATUALIZAÇÃO: descobri. Aqui está minha solução ..."
smholloway
@Virussmca I reverteu para uma resposta anterior que é muito mais robusta com desempenho semelhante. Parar completamente as atualizações de localização parece desencadear uma condição de corrida (ou algum outro cenário não determinístico).
andamento em
Esta abordagem não funcionará como você esperava: 1) Aumentar o valor da propriedade distanceFilter não resulta em economia de bateria porque o GPS ainda precisa descobrir sua localização. 2) No iOS 7, o tempo de segundo plano não é contíguo. 3) Você pode levar em consideração serviços significativos de ChangeLocation. 4) Além de sua função no iOS 7, você não pode iniciar UIBackgroundModes - localização de fundo ...
aMother
2
@aMother 1) Percebi que aumentar o filtro de distância (e não processar callbacks) não economiza muita bateria. No entanto, setDesiredAccuracy sim! Ele recorre às informações da torre de celular, basicamente de graça. 2) Não contíguo? 3) Especifiquei, em negrito, que exijo alta precisão. Mudanças de localização significativas não fornecem isso. 4) Os serviços de localização não estão sendo iniciados / interrompidos em segundo plano.
andamento em
2
Eu sei que este é um post antigo agora, mas os usuários do meu aplicativo que usam esse truque notaram um aumento significativo no uso da bateria desde a atualização para o iOS9. Ainda não investiguei, mas tenho a sensação de que esse 'truque' pode não funcionar mais?
Gary Wright,
45

Eu escrevi um aplicativo usando os serviços de localização, o aplicativo deve enviar localização a cada 10s. E funcionou muito bem.

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

As etapas são as seguintes:

Obrigatório: registre o modo de plano de fundo para atualizar o local.

1. Crie LocationManger e startUpdatingLocation, com precisão e distância filtrada como desejar:

-(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 método "allowDeferredLocationUpdatesUntilTraveled: tempo limite" em segundo plano, você deve reiniciar a atualizaçãoLocation com o novo parâmetro quando o aplicativo muda para segundo plano, como este:

- (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 é atualizadoLocations normalmente com "locationManager: didUpdateLocations:" callback:

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

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

4. Mas você deve manipular os dados em "locationManager: didFinishDeferredUpdatesWithError:" callback para seu propósito

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

     _deferringUpdates = NO;

     //do something 
}

5. NOTA: Acho que devemos redefinir os parâmetros do LocationManager cada vez que o aplicativo alternar entre o modo de fundo / fundo.

samthui7
fonte
Isso funciona muito bem e eu sinto que essa é a maneira certa de fazer, conforme mencionado na WWDC no ano passado! Obrigado pela postagem
prasad1250
@ samthui7 obrigado, isso funciona, mas também preciso obter a localização após a rescisão. como pode ser.??
Mohit Tomar de
@ samthui7 .. por favor, veja este que ... stackoverflow.com/questions/37338466/…
Suraj Sukale
2
@amar: Eu verifiquei no iOS 10 beta também, ele ainda funciona. Observe que precisamos adicionar algum código antes de startUpdatingLocation: allowsBackgroundLocationUpdates = YESe requestAlwaysAuthorizationou requestWhenInUseAuthorization. Por exemplo: `CLLocationManager * locationManager = [[CLLocationManager alloc] init]; locationManager.delegate = self; locationManager.allowsBackgroundLocationUpdates = YES; [locationManager requestAlwaysAuthorization]; [locationManager startUpdatingLocation]; `
samthui7
1
@Nirav Não. Não acho que a Apple poderia permitir que fizéssemos isso, pelo que eu sei.
samthui7
38
  1. Se você tiver o UIBackgroundModesem seu plist com locationchave, não precisa usar o beginBackgroundTaskWithExpirationHandlermétodo. Isso é redundante. Além disso, você está usando-o incorretamente (veja aqui ), mas isso é discutível, já que seu plist está definido.

  2. Com UIBackgroundModes locationo plist, o aplicativo continuará a ser executado em segundo plano indefinidamente apenas enquanto CLLocationMangerestiver em execução. Se você ligar stopUpdatingLocationenquanto estiver em segundo plano, o aplicativo irá parar e não iniciará novamente.

    Talvez você possa ligar um beginBackgroundTaskWithExpirationHandlerpouco antes de ligar stopUpdatingLocatione, depois de ligar, startUpdatingLocationpode ligar endBackgroundTaskpara o para mantê-lo em segundo plano enquanto o GPS está parado, mas nunca tentei isso - é apenas uma ideia.

    Outra opção (que eu não tentei) é manter o gerenciador de localização rodando enquanto está em segundo plano, mas assim que você conseguir uma localização precisa, altere a desiredAccuracypropriedade para 1000m ou mais para permitir que o chip GPS seja desligado (para economizar bateria). Então, 10 minutos depois, quando precisar de outra atualização de localização, mude desiredAccuracypara 100m para ligar o GPS até obter uma localização precisa, repita.

  3. Ao ligar startUpdatingLocationpara o gerente de localização, você deve dar-lhe tempo para obter uma posição. Você não deve ligar imediatamente stopUpdatingLocation. Nós o deixamos rodar por no máximo 10 segundos ou até obtermos um local de alta precisão não armazenado em cache.

  4. Você precisa filtrar os locais em cache e verificar a precisão do local obtido para garantir que atenda à precisão mínima exigida (veja aqui ). A primeira atualização que você obtém pode ter 10 minutos ou 10 dias. A primeira precisão que você obtém pode ser de 3000 m.

  5. Considere o uso de APIs de mudança de local significativa. Depois de receber a notificação de mudança significativa, você pode começar CLLocationManagerpor alguns segundos para obter uma posição de alta precisão. Não tenho certeza, nunca usei os serviços significativos de mudança de local.

progrmr
fonte
A CoreLocationreferência da Apple parece recomendar o uso de beginBackgroundTaskWithExpirationHandlerao processar dados de localização em segundo plano: "Se um aplicativo iOS precisa de mais tempo para processar os dados de localização, ele pode solicitar mais tempo de execução em segundo plano usando o método beginBackgroundTaskWithName: expirationHandler: da classe UIApplication." - developer.apple.com/library/ios/documentation/UserExperience/…
eliocs
4
não se esqueça de adicionar _locationManager.allowsBackgroundLocationUpdates = YES; para iOS9 +, caso contrário, o aplicativo irá hibernar após 180 seg
kolyancz
@kolyancz Acabei de experimentar no iOS 10.1.1 para iPhone 6+. Demorou cerca de 17 minutos para ele adormecer e disparar locationManagerDidPauseLocationUpdates. Eu verifiquei esse comportamento 10 vezes!
Honey de
Eu realmente não entendo por que você está dizendo que é discutível e então você está dizendo que deveria embrulhá-lo dentro de um backgroundTask ...
Querida
9

Quando é hora de iniciar o serviço de localização e parar a tarefa em segundo plano, a tarefa em segundo plano deve ser interrompida com um atraso (1 segundo deve ser suficiente). Caso contrário, o serviço de localização não será iniciado. Além disso, o serviço de localização deve ser deixado ligado por alguns segundos (por exemplo, 3 segundos).

Existe um cocoapod APScheduledLocationManager que permite obter atualizações de localização de fundo a cada n segundos com a precisão de 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.

faixa
fonte
1

Que tal experimentar com a startMonitoringSignificantLocationChanges:API? Ele definitivamente dispara com menos frequência e a precisão é razoavelmente boa. Além disso, tem muito mais vantagens do que usar outras locationManagerAPIs.

Muito mais sobre esta API já foi discutido neste link

Thatzprem
fonte
1
Eu tentei essa abordagem. Ele viola o requisito de precisão - dos documentos : This interface delivers new events only when it detects changes to the device’s associated cell towers
pcoving
1

Você precisa adicionar o modo de atualização de localização em seu aplicativo, adicionando a seguinte chave em seu info.plist.

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

didUpdateToLocationmétodo será chamado (mesmo quando seu aplicativo está em segundo plano). Você pode realizar qualquer coisa com base nesta chamada de método

aahsanali
fonte
O modo de atualização de localização já está no info.plist. Como mencionei, estou recebendo atualizações até o tempo limite de fundo de 180 segundos.
andamento em
0

Depois do iOS 8, várias mudanças na estrutura CoreLocation relacionadas à busca de plano de fundo e atualizações feitas pela apple.

1) A Apple apresenta a solicitação AlwaysAuthorization para buscar a atualização do local no aplicativo.

2) A Apple introduz backgroundLocationupdates para buscar a localização do fundo no iOS.

Para obter a localização em segundo plano, você precisa habilitar a atualização de localização nos recursos do 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
    }

}
Tecnologia
fonte
0

Para usar os serviços de localização padrão enquanto o aplicativo está em segundo plano, você precisa primeiro ativar os Modos de segundo plano na guia Recursos das configurações de Destino e selecionar Atualizações de localização.

Ou adicione-o diretamente ao Info.plist .

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

Então você precisa configurar o CLLocationManager

Objetivo C

//The Location Manager must have a strong reference to it.
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
//Request Always authorization (iOS8+)
if ([_locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) { [_locationManager requestAlwaysAuthorization];
}
//Allow location updates in the background (iOS9+)
if ([_locationManager respondsToSelector:@selector(allowsBackgroundLocationUpdates)]) { _locationManager.allowsBackgroundLocationUpdates = YES;
}
[_locationManager startUpdatingLocation];

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

Sr.Javed Multani
fonte
-2
locationManager.allowsBackgroundLocationUpdates = true
Papon Smc
fonte
4
Você poderia editar sua resposta e adicionar alguma explicação por que / como isso resolve o problema?
Barbsan