Regelmäßige Aktualisierungen des iOS-Hintergrundstandorts

91

Ich schreibe eine Anwendung, die Aktualisierungen des Hintergrundstandorts mit hoher Genauigkeit und niedriger Frequenz erfordert . Die Lösung scheint eine NSTimer-Hintergrundaufgabe zu sein, die die Aktualisierungen des Standortmanagers startet und diese dann sofort herunterfährt. Diese Frage wurde schon einmal gestellt:

Wie erhalte ich in meiner iOS-Anwendung alle n Minuten ein Hintergrund-Standort-Update?

Abrufen des Benutzerstandorts alle n Minuten, nachdem die App in den Hintergrund geschaltet wurde

iOS Nicht das typische Problem mit dem Timer zur Verfolgung des Hintergrundstandorts

iOS-Hintergrundtimer mit langer Laufzeit und Hintergrundmodus "Standort"

iOS Vollzeit-Hintergrunddienst basierend auf Standortverfolgung

Aber ich habe noch kein Mindestbeispiel zum Laufen gebracht. Nachdem ich jede Permutation der oben akzeptierten Antworten ausprobiert hatte, stellte ich einen Ausgangspunkt zusammen. Hintergrund eingeben:

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

und die Delegatenmethode:

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

    NSLog(@"%@", newLocation);

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

}

Das aktuelle Verhalten besteht darin, dass die backgroundTimeRemainingDekrementierung von 180 Sekunden auf Null (während der Protokollierung des Speicherorts) erfolgt und der Ablaufhandler dann ausgeführt wird und keine weiteren Standortaktualisierungen generiert werden. Wie ändere ich den obigen Code, um regelmäßig Standortaktualisierungen im Hintergrund auf unbestimmte Zeit zu erhalten ?

Update : Ich ziele auf iOS 7 ab und es scheint Hinweise darauf zu geben, dass sich Hintergrundaufgaben anders verhalten:

Starten Sie den Standort-Manager in iOS 7 über die Hintergrundaufgabe

pcoving
quelle
Sie erwähnen nicht, auf welche iOS-Version Sie abzielen. iOS 7 (zum Beispiel) hat ein anderes Multitasking-System als sein Vorgänger.
Jason Whitehorn
@ JasonWhitehorn Guter Punkt. Ich habe die Frage aktualisiert.
pcoving
2
@pcoving: Funktioniert Ihre Lösung auch dann, wenn die App beendet oder beendet wird?
Nirav

Antworten:

62

Es scheint, dass dies stopUpdatingLocationden Hintergrund-Watchdog-Timer auslöst, also habe ich ihn ersetzt didUpdateLocationdurch:

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

Dies scheint das GPS effektiv auszuschalten. Der Selektor für den Hintergrund wird NSTimerdann:

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

Alles, was ich tue, ist, die Genauigkeit regelmäßig umzuschalten, um alle paar Minuten eine hochgenaue Koordinate zu erhalten. Da die Koordinate locationManagernicht gestoppt wurde, backgroundTimeRemainingbleibt sie auf ihrem Maximalwert. Dies reduzierte den Batterieverbrauch von ~ 10% pro Stunde (mit Konstante kCLLocationAccuracyBestim Hintergrund) auf ~ 2% pro Stunde auf meinem Gerät.

pcoving
quelle
4
Könnten Sie Ihre Frage mit dem vollständigen Arbeitsbeispiel aktualisieren? Etwas wie: "UPDATE: Ich habe es herausgefunden. Hier ist meine Lösung ..."
smholloway
@Virussmca Ich bin zu einer früheren Antwort zurückgekehrt, die bei ähnlicher Leistung viel robuster ist. Das Stoppen von Standortaktualisierungen insgesamt scheint eine Rennbedingung (oder ein anderes nicht deterministisches Szenario) auszulösen.
pcoving
Dieser Ansatz funktioniert nicht wie erwartet: 1) Das Erhöhen des Werts der Eigenschaft distanceFilter führt nicht zu einer Batterieeinsparung, da das GPS immer noch Ihren Standort ermitteln muss. 2) In iOS 7 ist die Hintergrundzeit nicht zusammenhängend. 3) Sie können signifikante ChangeLocation-Services berücksichtigen. 4) Abgesehen von Ihrer soution in iOS 7 Sie können nicht UIBackgroundModes starten - Standort von Hintergrund ...
amother
2
@aMother 1) Mir ist klar, dass das Erhöhen des Distanzfilters (und das Nichtverarbeiten von Rückrufen) nicht viel Batterie spart. SetDesiredAccuracy tut dies jedoch! Es greift auf Informationen zum Zellturm zurück, die grundsätzlich kostenlos sind. 2) Nicht zusammenhängend? 3) Ich habe fett angegeben, dass ich eine hohe Genauigkeit benötige. Wesentliche Standortänderungen bieten dies nicht. 4) Ortungsdienste werden nicht im Hintergrund gestartet / gestoppt.
pcoving
2
Ich weiß, dass dies jetzt ein alter Beitrag ist, aber Benutzer meiner Anwendung, die diesen Trick verwenden, haben seit dem Upgrade auf iOS9 einen deutlichen Anstieg des Batterieverbrauchs festgestellt. Ich habe noch nicht nachgeforscht, aber ich habe das Gefühl, dass dieser "Trick" möglicherweise nicht mehr funktioniert?
Gary Wright
45

Ich habe eine App mit Ortungsdiensten geschrieben. Die App muss alle 10 Sekunden einen Ort senden. Und es hat sehr gut funktioniert.

Verwenden Sie einfach die Methode " allowDeferredLocationUpdatesUntilTraveled: timeout " gemäß Apples Dokument.

Die Schritte sind wie folgt:

Erforderlich: Registrieren Sie den Hintergrundmodus für den Update-Speicherort.

1. Erstellen Sie LocationManger und startUpdatingLocation mit Genauigkeit und gefilterter Entfernung, wie Sie möchten:

-(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. Damit die App mit der Methode "allowDeferredLocationUpdatesUntilTraveled: timeout" im Hintergrund für immer ausgeführt wird, müssen Sie updatingLocation mit neuen Parametern neu starten, wenn die App in den Hintergrund wechselt.

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

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

3. App erhält aktualisierte Standorte wie gewohnt mit dem Rückruf "locationManager: didUpdateLocations:":

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

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

4. Sie sollten die Daten jedoch in dem Rückruf "locationManager: didFinishDeferredUpdatesWithError:" für Ihren Zweck verarbeiten

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

     _deferringUpdates = NO;

     //do something 
}

5. HINWEIS: Ich denke, wir sollten die Parameter von LocationManager jedes Mal zurücksetzen, wenn die App zwischen Hintergrund- und Hintergrundmodus wechselt.

samthui7
quelle
Das funktioniert großartig und ich denke, dies ist der richtige Weg, wie im WWDC vom letzten Jahr erwähnt !! Vielen Dank für den Beitrag
prasad1250
@ samthui7 danke das funktioniert, aber ich muss den Ort auch nach der Kündigung bekommen. wie kann es sein.??
Mohit Tomar
@ samthui7 .. bitte sehen Sie diese que ... stackoverflow.com/questions/37338466/…
Suraj Sukale
2
@amar: Ich habe auch iOS 10 Beta überprüft, es funktioniert immer noch. Beachten Sie, dass wir vor startUpdatingLocation: allowsBackgroundLocationUpdates = YESund requestAlwaysAuthorizationoder Code hinzufügen müssen requestWhenInUseAuthorization. Beispiel: `CLLocationManager * locationManager = [[CLLocationManager alloc] init]; locationManager.delegate = self; locationManager.allowsBackgroundLocationUpdates = YES; [locationManager requestAlwaysAuthorization]; [locationManager startUpdatingLocation]; `
samthui7
1
@Nirav Nein. Ich glaube nicht, dass Apple das zulassen könnte, soweit ich weiß.
Samthui7
38
  1. Wenn Sie den Schlüssel UIBackgroundModesin Ihrer Liste locationhaben, müssen Sie keine beginBackgroundTaskWithExpirationHandlerMethode verwenden. Das ist überflüssig. Sie verwenden es auch falsch (siehe hier ), aber das ist umstritten, da Ihre Plist eingestellt ist.

  2. Mit UIBackgroundModes locationin der Liste wird die App nur so lange im Hintergrund ausgeführt, wie sie ausgeführt CLLocationMangerwird. Wenn Sie stopUpdatingLocationim Hintergrund anrufen, wird die App gestoppt und nicht erneut gestartet.

    Vielleicht könnten Sie beginBackgroundTaskWithExpirationHandlerkurz vor dem Anruf anrufen stopUpdatingLocationund dann nach dem startUpdatingLocationAnruf den anrufen endBackgroundTask, um den Hintergrund zu erhalten, während das GPS gestoppt ist, aber das habe ich nie versucht - es ist nur eine Idee.

    Eine andere Option (die ich nicht ausprobiert habe) besteht darin, den Standortmanager im Hintergrund laufen zu lassen. Wenn Sie jedoch einen genauen Standort erhalten, ändern Sie die desiredAccuracyEigenschaft auf 1000 m oder höher, damit der GPS-Chip ausgeschaltet werden kann (um Batterie zu sparen). Wenn Sie dann 10 Minuten später eine weitere Standortaktualisierung benötigen, ändern Sie den desiredAccuracyRücken auf 100 m, um das GPS einzuschalten, bis Sie einen genauen Standort erhalten. Wiederholen Sie diesen Vorgang.

  3. Wenn Sie startUpdatingLocationden Standortmanager anrufen , müssen Sie ihm Zeit geben, um eine Position zu bekommen. Sie sollten nicht sofort anrufen stopUpdatingLocation. Wir lassen es maximal 10 Sekunden lang laufen oder bis wir einen nicht zwischengespeicherten Ort mit hoher Genauigkeit erhalten.

  4. Sie müssen zwischengespeicherte Speicherorte herausfiltern und die Genauigkeit des Speicherorts überprüfen, um sicherzustellen, dass er Ihrer erforderlichen Mindestgenauigkeit entspricht (siehe hier ). Das erste Update, das Sie erhalten, ist möglicherweise 10 Minuten oder 10 Tage alt. Die erste Genauigkeit, die Sie erhalten, kann 3000 m betragen.

  5. Verwenden Sie stattdessen die APIs für signifikante Standortänderungen. Sobald Sie die Benachrichtigung über signifikante Änderungen erhalten haben, können Sie CLLocationManagereinige Sekunden lang beginnen, um eine Position mit hoher Genauigkeit zu erhalten. Ich bin mir nicht sicher, ich habe die signifikanten Standortänderungsdienste nie genutzt.

Progrmr
quelle
Die Apple- CoreLocationReferenz scheint die Verwendung beginBackgroundTaskWithExpirationHandlerbei der Verarbeitung von Standortdaten im Hintergrund zu empfehlen : "Wenn eine iOS-App mehr Zeit zum Verarbeiten der Standortdaten benötigt, kann sie mithilfe der Methode beginBackgroundTaskWithName: expirationHandler: der UIApplication-Klasse mehr Zeit für die Ausführung im Hintergrund anfordern." - developer.apple.com/library/ios/documentation/UserExperience/…
eliocs
4
Vergessen Sie nicht, _locationManager.allowsBackgroundLocationUpdates = YES hinzuzufügen. für iOS9 +, sonst geht die App nach 180 Sekunden in den Ruhezustand
kolyancz
@kolyancz Ich habe es gerade auf iOS 10.1.1 für iPhone 6+ versucht. Es dauerte ungefähr 17 Minuten, bis es einschlief und auslöste locationManagerDidPauseLocationUpdates. Ich habe dieses Verhalten 10 Mal überprüft!
Honig
Ich verstehe wirklich nicht, warum du sagst, dass es strittig ist, und dann sagst du, du solltest es in eine Hintergrundaufgabe wickeln ...
Honey
9

Wenn es Zeit ist, den Ortungsdienst zu starten und die Hintergrundaufgabe zu stoppen, sollte die Hintergrundaufgabe mit einer Verzögerung gestoppt werden (1 Sekunde sollte ausreichen). Andernfalls wird der Ortungsdienst nicht gestartet. Außerdem sollte der Ortungsdienst einige Sekunden (z. B. 3 Sekunden) eingeschaltet bleiben.

Es gibt einen Cocoapod APScheduledLocationManager , mit dem alle n Sekunden Hintergrund-Standortaktualisierungen mit der gewünschten Standortgenauigkeit abgerufen werden können .

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

Das Repository enthält auch eine in Swift 3 geschriebene Beispiel-App.

Schärpe
quelle
1

Wie wäre es mit einem Versuch mit startMonitoringSignificantLocationChanges:API? Es feuert definitiv mit weniger Frequenz und die Genauigkeit ist ziemlich gut. Darüber hinaus bietet es viel mehr Vorteile als die Verwendung anderer locationManagerAPIs.

Viel mehr zu dieser API wurde bereits unter diesem Link besprochen

thatzprem
quelle
1
Ich habe diesen Ansatz ausprobiert. Es verstößt gegen die Genauigkeitsanforderungen - aus den Dokumenten : This interface delivers new events only when it detects changes to the device’s associated cell towers
Uhr
1

Sie müssen den Standortaktualisierungsmodus in Ihrer Anwendung hinzufügen, indem Sie den folgenden Schlüssel in Ihre info.plist einfügen.

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

didUpdateToLocationMethode wird aufgerufen (auch wenn sich Ihre App im Hintergrund befindet). Anhand dieses Methodenaufrufs können Sie alles ausführen

aahsanali
quelle
Der Standortaktualisierungsmodus befindet sich bereits in der info.plist. Wie bereits erwähnt, erhalte ich Updates bis zum Hintergrund-Timeout von 180 Sekunden.
pcoving
0

Nach iOS 8 gibt es einige Änderungen im CoreLocation Framework-bezogenen Hintergrundabruf und Aktualisierungen von Apple.

1) Apple führt die AlwaysAuthorization-Anforderung zum Abrufen des Standortupdates in der Anwendung ein.

2) Apple führt backgroundLocationupdates zum Abrufen des Standorts aus dem Hintergrund in iOS ein.

Um den Standort im Hintergrund abzurufen, müssen Sie die Standortaktualisierung in den Funktionen von Xcode aktivieren.

//
//  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
    }

}
Technik
quelle
0

Um Standard-Standortdienste zu verwenden, während sich die Anwendung im Hintergrund befindet, müssen Sie zuerst die Hintergrundmodi auf der Registerkarte Funktionen der Zieleinstellungen aktivieren und Standortaktualisierungen auswählen.

Oder fügen Sie es direkt zur Info.plist hinzu .

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

Dann müssen Sie den CLLocationManager einrichten

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

Schnell

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

Mr.Javed Multani
quelle
-2
locationManager.allowsBackgroundLocationUpdates = true
Papon Smc
quelle
4
Könnten Sie Ihre Antwort bearbeiten und eine Erklärung hinzufügen, warum / wie dies das Problem löst?
Barbansan