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

206

Ich suche nach einer Möglichkeit, alle n Minuten ein Hintergrund-Standort-Update in meiner iOS-Anwendung zu erhalten. Ich verwende iOS 4.3 und die Lösung sollte für iPhones ohne Jailbreak funktionieren.

Ich habe folgende Optionen ausprobiert / in Betracht gezogen:

  • CLLocationManager startUpdatingLocation/startMonitoringSignificantLocationChanges: Dies funktioniert erwartungsgemäß im Hintergrund, basierend auf den konfigurierten Eigenschaften, aber es scheint nicht möglich zu sein, die Position alle n Minuten zu aktualisieren
  • NSTimer: Funktioniert, wenn die App im Vordergrund ausgeführt wird, scheint jedoch nicht für Hintergrundaufgaben ausgelegt zu sein
  • Lokale Benachrichtigungen: Lokale Benachrichtigungen können alle n Minuten geplant werden, es ist jedoch nicht möglich, Code auszuführen, um den aktuellen Standort abzurufen (ohne dass der Benutzer die App über die Benachrichtigung starten muss). Dieser Ansatz scheint auch kein sauberer Ansatz zu sein, da hierfür keine Benachrichtigungen verwendet werden sollten.
  • UIApplication:beginBackgroundTaskWithExpirationHandler: Soweit ich weiß, sollte dies verwendet werden, um einige Arbeiten im Hintergrund (auch zeitlich begrenzt) zu beenden, wenn eine App in den Hintergrund verschoben wird, anstatt "lang laufende" Hintergrundprozesse zu implementieren.

Wie kann ich diese regelmäßigen Standortaktualisierungen im Hintergrund implementieren?

Wjans
quelle
nützliches Follow-up: stackoverflow.com/questions/10235203/…
Lolo
1
Wenn Sie versuchen, es unter iOS 7 zum Laufen zu bringen, können Sie diese Lösung hier ausprobieren : stackoverflow.com/questions/18946881/… Wenn Sie Fragen haben, können Sie sich hier an einer Diskussion beteiligen: mobileoop.com/background -Location-Update-Programmierung-für-iOS-7
Ricky
1
Alle Ihre Ergebnisse sind korrekt (die vier Aufzählungspunkte). Wertvolle Informationen, die dann wissen, was nicht zu Ihrem Anwendungsfall passt? Und ja, im SUSPENDED MODE oder NOT RUNNING gibt es keine ultimative Methode zum Aktualisieren alle n Minuten.
LenArt

Antworten:

113

Ich habe mithilfe der Apple Developer Forums eine Lösung gefunden, um dies zu implementieren:

  • Angeben location background mode
  • Erstellen Sie eine NSTimerim Hintergrund mitUIApplication:beginBackgroundTaskWithExpirationHandler:
  • Wann nist kleiner als UIApplication:backgroundTimeRemaininges wird gut funktionieren. Wenn nes größer ist , location managersollte das wieder aktiviert (und deaktiviert) werden, bevor keine Zeit mehr verbleibt, um zu verhindern, dass die Hintergrundaufgabe beendet wird.

Dies funktioniert, da der Speicherort eine der drei zulässigen Arten der Hintergrundausführung ist .

Hinweis: Ich habe einige Zeit verloren, indem ich dies im Simulator getestet habe, wo es nicht funktioniert. Auf meinem Telefon funktioniert es jedoch einwandfrei.

Wjans
quelle
24
Haben Sie zufällig den Link zum Forum? Ich möchte dieselbe Art der Standortzuordnung implementieren und konnte sie nicht zum Laufen bringen. Oder ein Beispielcode wäre sehr dankbar.
Utahwithak
4
Können Sie bitte erklären, warum die Hintergrundaufgabe nach 10 Minuten (maximal zulässige Zeit) nicht beendet wird, wenn Sie den Standortmanager einfach anhalten und starten? ist es eine beabsichtigte Funktionalität? Es klingt eher nach einem Fehler im Apple SDK, wenn es so passiert. Auf welcher iOS-Version haben Sie es ausprobiert?
Saurabh
5
@all: Ja, unsere App ist im AppStore verfügbar. Ich werde nicht den gesamten Code veröffentlichen, alle genannten Aufzählungspunkte sind klar dokumentierte Funktionen. Wenn Sie ein bestimmtes Problem haben, stellen Sie Ihre eigene Frage, in der erläutert wird, was Sie versucht haben und was schief geht.
Wjans
3
@ user836026: Ja, das meine ich mit der Angabe des Hintergrundmodus. Nach dem Stoppen der Standortaktualisierung sollte diese innerhalb von 10 Minuten erneut gestartet werden, um zu verhindern, dass die App beendet wird.
Wjans
12
Für alle, die daran interessiert sind, einen tatsächlichen Code zu sehen, der die hier diskutierten Themen widerspiegelt
Lolo
55

Gehen Sie unter iOS 8/9/10 wie folgt vor, um die Hintergrundposition alle 5 Minuten zu aktualisieren:

  1. Gehen Sie zu Projekt -> Funktionen -> Hintergrundmodi -> wählen Sie Standortaktualisierungen

  2. Gehen Sie zu Projekt -> Info -> fügen Sie einen Schlüssel NSLocationAlwaysUsageDescription mit leerem Wert (oder optional einem beliebigen Text) hinzu.

  3. Damit der Standort funktioniert, wenn sich Ihre App im Hintergrund befindet, und alle 5 Minuten Koordinaten an den Webdienst senden oder irgendetwas damit tun, implementieren Sie ihn wie im folgenden Code.

Ich verwende keine Hintergrundaufgaben oder Timer. Ich habe diesen Code mit meinem Gerät mit iOS 8.1 getestet, das einige Stunden auf meinem Schreibtisch lag, während meine App im Hintergrund ausgeführt wurde. Das Gerät war gesperrt und der Code lief die ganze Zeit ordnungsgemäß.

@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
quelle
3
Funktioniert das länger als ein paar Stunden? Selbst wenn die App nicht beendet wird, scheint das Gerät nach etwa einer Stunde, in der die App in den Hintergrund tritt, keine Standortaktualisierungen mehr zu senden.
Myxtic
2
Das Abrufen von Hintergrundinformationen ist in meinem Projekt deaktiviert (es sollte keine Rolle spielen, ob es aktiviert oder deaktiviert ist). PausenLocationUpdatesAutomatically muss jedoch auf NO gesetzt werden, damit das obige Beispiel ordnungsgemäß funktioniert. Es wird NICHT automatisch fortgesetzt, sobald Sie erneut mit dem Bewegen beginnen, wenn es zuvor vom System angehalten wurde. Deshalb habe ich im obigen Beispiel diese Eigenschaft auf NO gesetzt.
Leszek Szary
1
Ich denke du hast absolut recht. Weitere Informationen zu diesem Problem finden Sie hier: stackoverflow.com/q/17484352/1048331
Myxtic
2
@LeszekS Sie müssen unten Code für iOS 9 Unterstützung für Hintergrund hinzufügen - if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) { [self.locationManager setAllowsBackgroundLocationUpdates:YES]; }
Fenil
2
Was ist der Vorteil dabei? Sie entladen den Akku immer noch mit einer konstant hohen Genauigkeit. Das einzige, was Sie nicht tun, ist, dass Sie den bereits empfangenen Speicherort erst dann verwenden, wenn Sie ein Intervall von 5 Minuten erreicht haben ...
Honey
34

Ich habe dies in einer Anwendung getan, die ich entwickle. Die Timer funktionieren nicht, wenn sich die App im Hintergrund befindet, aber die App erhält ständig die Standortaktualisierungen. Ich habe irgendwo in der Dokumentation gelesen (ich kann es jetzt nicht finden, ich werde ein Update veröffentlichen, wenn ich es tue), dass eine Methode nur in einer aktiven Ausführungsschleife aufgerufen werden kann, wenn sich die App im Hintergrund befindet. Der App-Delegat verfügt auch im Hintergrund über eine aktive Ausführungsschleife, sodass Sie keine eigene erstellen müssen, damit dies funktioniert. [Ich bin nicht sicher, ob dies die richtige Erklärung ist, aber so habe ich verstanden, was ich gelesen habe]

Fügen Sie zunächst das locationObjekt für den Schlüssel UIBackgroundModesin die info.plist Ihrer App ein. Jetzt müssen Sie die Standortaktualisierungen an einer beliebigen Stelle in Ihrer App starten:

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

Schreiben Sie als Nächstes eine Methode, um die Standortaktualisierungen beispielsweise -(void)didUpdateToLocation:(CLLocation*)locationim App-Delegaten zu verarbeiten. Implementieren Sie dann die Methode locationManager:didUpdateLocation:fromLocationvon CLLocationManagerDelegatein der Klasse, in der Sie den Standortmanager gestartet haben (da wir den Standortmanager-Delegaten auf 'self' gesetzt haben). Bei dieser Methode müssen Sie überprüfen, ob das Zeitintervall abgelaufen ist, nach dem Sie die Standortaktualisierungen durchführen müssen. Sie können dies tun, indem Sie jedes Mal die aktuelle Zeit speichern. Wenn diese Zeit abgelaufen ist, rufen Sie die Methode UpdateLocation von Ihrem App-Delegaten auf:

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

Dadurch wird Ihre Methode alle 5 Minuten aufgerufen, auch wenn sich Ihre App im Hintergrund befindet. Wichtel: Diese Implementierung entlädt den Akku. Wenn die Genauigkeit Ihrer Standortdaten nicht kritisch ist, sollten Sie sie verwenden[locationManager startMonitoringSignificantLocationChanges]

Bevor Sie dies zu Ihrer App hinzufügen, lesen Sie bitte das Location Awareness Programming Guide

Bushra Shahid
quelle
3
Auf diese Weise werden die Ortungsdienste ständig aktiviert (tatsächlich wird die Batterie entladen), das möchte ich nicht. Ich möchte den Ortungsdienst alle n Minuten aktivieren und sofort deaktivieren, wenn ich eine gute Lösung gefunden habe (habe gerade bemerkt, dass ich das in meiner Frage nicht klar erklärt habe). Ich kann dieses Verhalten in der von mir beschriebenen Lösung erreichen.
Wjans
3
Sie können die Genauigkeit des Standortmanagers auf 1 km einstellen. Dadurch bleibt Ihre Batterie nahezu intakt. Nach 5 Minuten stellen Sie die Genauigkeit auf 1 m ein. Wenn Sie einen zufriedenstellenden Standort erhalten (normalerweise nach 5 Sekunden), stellen Sie die Genauigkeit einfach auf 1 km zurück.
Knagode
knagode, ich habe Ihre vorgeschlagene Lösung für das Problem der Batterieentladung ausprobiert, aber selbst nach einer Erhöhung der Genauigkeit nach N Minuten wird die Methode locationManager: didUpdateLocations nicht erneut aufgerufen. Ich habe versucht, startUpdating und stopUpdating zu starten. Statt die Genauigkeit zu erhöhen und zu verringern, wird es nach N Minuten als erfolgreich delegierte locationManager: didUpdateLocations-Methode bezeichnet, funktioniert aber nicht im Hintergrundmodus ...
Hardik Darji
Wie für die Links der Dokumentation. Siehe hier : "Die Methoden Ihres Delegatenobjekts werden von dem Thread aufgerufen, in dem Sie die entsprechenden Ortungsdienste gestartet haben. Dieser Thread muss selbst eine aktive Ausführungsschleife haben, wie die im Hauptthread Ihrer Anwendung."
Honig
24

Jetzt, da iOS6 der beste Weg ist, um für immer laufende Ortungsdienste zu haben, ist ...

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

}

Habe es einfach so getestet:

Ich startete die App, ging in den Hintergrund und bewegte mich einige Minuten im Auto. Dann gehe ich für 1 Stunde nach Hause und bewege mich wieder (ohne die App erneut zu öffnen). Die Standorte wurden erneut gestartet. Dann blieb er zwei Stunden stehen und fing wieder an. Alles wieder ok ...

VERGESSEN SIE NICHT, die neuen Ortungsdienste in iOS6 zu verwenden

- (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
quelle
1
Wenn die App abstürzt oder beendet wird, startet das System nicht neu, oder?
Ken
1
Für besser lesbaren Code können Sie ein [location lastObject] ausführen. anstelle der [Standorte objectAtIndex: [Standorte zählen] - 1]
Axello
Ihre Methode ist nur bei ios6?
Pengwang
Müssen wir das benutzen? Ich dachte, ich habe nur den Standortmanager-Code in viewDidLoad einer Klasse und einen Hintergrundschlüssel in der plist-Datei festgelegt, damit die App, die für Standortaktualisierungen registriert wird, gut genug ist. Können Sie mir bitte dabei helfen?
Nithinreddy
Ja, es wird wie Charm Nithinreddy funktionieren, aber es wird nach 10 Minuten aufhören zu funktionieren, da iOS nach dieser Zeit lange Threads beendet. Meine Lösung ist perfekt, wenn Sie möchten, dass diese Dienste für immer gestartet werden. Ich habe vor zwei Tagen einige Tests durchgeführt und es wird jede Stunde 6% Batterie entladen. Wenn Sie stattdessen [locationManager startUpdatingLocation] verwenden, werden 17% verbraucht
Alejandro Luengo
13

Für jemanden, der einen Albtraum hat, ist das ein Problem. Ich habe eine einfache Lösung.

  1. Schauen Sie sich dieses Beispiel von raywenderlich.com an -> haben Sie Beispielcode, dies funktioniert perfekt, aber leider kein Timer während der Hintergrundortung. Dies wird auf unbestimmte Zeit ausgeführt.
  2. Fügen Sie den Timer hinzu, indem Sie Folgendes verwenden:

    -(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. Vergessen Sie nicht, in info.plist "App-Register für Standortaktualisierungen" hinzuzufügen.

HelmiB
quelle
Erhält dies den Standort für mehr als 3 Minuten?
SleepNot
Position muss in Funktionen -> Hintergrundmodus festgelegt werden.
Sergio Andreotti
Es funktioniert nicht!! Ich habe auf iOS 8 und iOS 10
Kirti
Korrigiere mich, wenn ich falsch liege. Basierend auf dem, was ich gelesen habe, starten Sie locationManager nur einmal. Danach sind alle Intervalle redundant. Da es bereits gestartet wurde
Honey
es funktioniert, wird aber nach 170 Sekunden gestoppt. Ich möchte meine Aufgabe unbegrenzt im Hintergrund
ausführen
8

Folgendes benutze ich:

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

Ich starte das Tracking in AppDelegate folgendermaßen:

BackgroundLocationManager.instance.start()
hmitkov
quelle
Vielen Dank. Unser Projekt muss den Standort des Benutzers verfolgen und PubNub-Ereignisse im Hintergrund senden, und Ihre Lösung funktioniert wirklich gut.
user921509
Hallo Hmitkov, wo ich die sendLocationToServer-Methode aufrufen kann, um den Benutzerstandort auf dem Server zu senden
AmanGupta007
@ AmanGupta007 Sie können sendLocationToServer in func locationManager (Manager: didUpdateLocations :) aufrufen. Beachten Sie den // TODO-Kommentar im Code.
Hmitkov
@hmitkov Ist es möglich, Location Services damit zu starten und zu stoppen, während sich die App im Hintergrund befindet? Starten Sie beispielsweise den Standortdienst über eine Push-Benachrichtigung, greifen Sie nach Lat / Long, senden Sie ihn an den Webservice und beenden Sie die Standortaktualisierung. Tun Sie dies jedes Mal, wenn 'content-available' = 1 im Push-Body enthalten ist.
DookieMan
1
Ich habe diesen Code ausprobiert, aber es scheint, dass er unter iOS11 nicht funktioniert. (Ich habe es nicht auf anderen Versionen getestet.)
Tom
6

Leider scheinen alle Ihre Annahmen richtig zu sein, und ich glaube nicht, dass es einen Weg gibt, dies zu tun. Um die Akkulaufzeit zu verlängern, basieren die Ortungsdienste des iPhones auf Bewegung. Wenn sich das Telefon an einer Stelle befindet, ist es für Ortungsdienste unsichtbar.

Der CLLocationManagerwird nur anrufenlocationManager:didUpdateToLocation:fromLocation: wenn das Telefon eine Standortaktualisierung erhält. Dies geschieht nur, wenn einer der drei Standortdienste (Mobilfunkmast, GPS, WLAN) eine Änderung feststellt.

Ein paar andere Dinge, die helfen könnten, weitere Lösungen zu finden:

  • Durch das Starten und Stoppen der Dienste wird die didUpdateToLocationDelegatenmethode aufgerufen, die jedoch newLocationmöglicherweise einen alten Zeitstempel hat.

  • Regionsüberwachung könnte helfen

  • Beachten Sie beim Ausführen im Hintergrund, dass es möglicherweise schwierig ist, die von Apple genehmigte "vollständige" LocationServices-Unterstützung zu erhalten. Nach allem, was ich gesehen habe, haben sie speziell entworfenstartMonitoringSignificantLocationChanges als stromsparende Alternative für Apps entwickelt, die Unterstützung für den Standort im Hintergrund benötigen, und ermutigen Entwickler nachdrücklich, diese zu verwenden, es sei denn, die App benötigt sie unbedingt.

Viel Glück!

UPDATE: Diese Gedanken sind möglicherweise inzwischen veraltet. Es sieht so aus, als ob die Leute mit der Antwort von @wjans oben Erfolg haben.

Chazbot
quelle
1
Im AppStore sind Apps verfügbar (wie zum Beispiel "My Locus"), die es ermöglichen, ein Standortupdate im Hintergrund zu erhalten. Sie halten den Ortungsdienst nicht aktiv, sondern werden nur in Kürze gemäß dem definierten Intervall aktiviert. Wie machen sie das?
Wjans
2
In der von Ihnen beschriebenen Situation verwendet die App höchstwahrscheinlich den Ansatz startMonitoringSignificantLocationChanges. Hier wird das Telefon vorübergehend "aufgeweckt", wenn es eine Standortaktualisierung erhält, aber es gibt kein Intervall, in dem Sie diesen Dienst im Hintergrund "pingen" können. Wenn sich das Telefon bewegt (oder von Mobilfunk zu GPS oder zu WLAN wechselt), wird ein Update ausgelöst. Der Stanford iTunes U-Vortrag zu diesem Thema war wirklich hilfreich für mich - hoffentlich kann er Ihnen dabei helfen, eine
Problemumgehung zu
2
Danke für den Zeiger. Ich verstehe jedoch immer noch nicht, was die App tut. Selbst wenn sich mein Telefon an meinem Schreibtisch befindet und sich überhaupt nicht bewegt, kann ich sehen, dass der Ortungsdienst alle 10 Minuten (genau) ausgelöst wird. Wenn ich das richtig verstehe, startMonitoringSignificantLocationChangeswürde ich in diesem Fall kein Update geben.
Wjans
1
@wjans Wie ist der Akkuverbrauch Ihres Telefons? Haben Sie bemerkt, dass er möglicherweise aufgrund der Mylocus-App schnell leer wird?
user836026
6

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 das Zeitlimit " allowDeferredLocationUpdatesUntilTraveled:" Methode Apples Dokument.

Was ich getan habe sind:

Erforderlich: Registrieren Sie den Hintergrundmodus für den Aktualisierungsort.

1. Erstellen Sie LocationMangerund startUpdatingLocationmit accuracyund filteredDistancenach Belieben:

-(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 allowDeferredLocationUpdatesUntilTraveled:timeoutMethode im Hintergrund für immer ausgeführt wird, müssen Sie updatingLocationmit einem neuen Parameter 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 locationManager:didUpdateLocations:Rückruf:

-(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 jedoch die Daten in locationManager:didFinishDeferredUpdatesWithError:Ihrem Rückruf für Ihren Zweck verarbeiten

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

     _deferringUpdates = NO;

     //do something 
}

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

samthui7
quelle
@Alle welche Lösung dieser oder der oben beschriebenen Lösung von wjans sollte ich lieber den Akku des Geräts schonen oder beide wirken sich gleich aus?
Yash
2
Ich habe beide von Ihnen erwähnten Lösungen ausprobiert und festgestellt, dass die von @ wjans etwas sparsamer ist. Aber nach dem Erscheinen von iOS 8 scheint diese Lösung nicht mehr richtig zu funktionieren. Für mehr Details: Meistens kann die App im Hintergrundmodus nicht lange leben.
Samthui7
@ samthui7 Warum setzen Sie pausenLocationUpdatesAutomatically = false?
CedricSoubrie
Die Anforderung meiner App besteht darin, userLocation weiterhin alle 10 Sekunden zu senden, während pausesLocationUpdatesAutomatically = trueder Standortmanager angewiesen wird, Aktualisierungen anzuhalten, wenn anscheinend keine Standortänderung vorliegt ( Apples Dokument ). Jedenfalls habe ich den Fall location manager pauses updatesnoch nicht explizit getestet : D.
Samthui7
@CedricSoubrie: Die Einstellung pausesLocationUpdatesAutomatically = truebewirkt, dass meine App den Standort nicht mehr aktualisiert.
Samthui7
5
if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) {
    [self.locationManager setAllowsBackgroundLocationUpdates:YES];
}

Dies wird für die Standortverfolgung im Hintergrund seit iOS 9 benötigt.

Nilesh
quelle
1
Das hat mir den Tag gerettet! Verwenden des iOS8-Bereitstellungsziels mit iOS9-Gerät
Yaro
4

Ich habe die Methode von xs2bush verwendet, um ein Intervall zu erhalten (using timeIntervalSinceDate) und es ein wenig erweitert. Ich wollte sicherstellen, dass ich die erforderliche Genauigkeit erhalte, die ich benötige, und dass ich den Akku nicht entleere, indem ich das GPS-Radio mehr als nötig eingeschaltet lasse.

Ich halte den Standort mit den folgenden Einstellungen kontinuierlich am Laufen:

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

Dies ist eine relativ geringe Belastung der Batterie. Wenn ich bereit bin, meine nächste regelmäßige Standortmessung durchzuführen, überprüfe ich zuerst, ob der Standort innerhalb meiner gewünschten Genauigkeit liegt. Wenn dies der Fall ist, verwende ich den Standort. Wenn nicht, dann erhöhe ich die Genauigkeit damit:

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

Ermitteln Sie meinen Standort, und sobald ich den Standort gefunden habe, drehe ich die Genauigkeit wieder zurück, um den Verbrauch der Batterie zu minimieren. Ich habe ein vollständiges Arbeitsbeispiel dafür geschrieben und auch die Quelle für den serverseitigen Code geschrieben, um die Standortdaten zu sammeln, in einer Datenbank zu speichern und Benutzern zu ermöglichen, GPS-Daten in Echtzeit anzuzeigen oder zuvor gespeicherte Routen abzurufen und anzuzeigen. Ich habe Clients für iOS, Android, Windows Phone und Java. Alle Clients sind nativ geschrieben und arbeiten im Hintergrund ordnungsgemäß. Das Projekt ist MIT-lizenziert.

Das iOS-Projekt ist für iOS 6 mit einem Basis-SDK von iOS 7 vorgesehen. Den Code erhalten Sie hier .

Bitte melden Sie ein Problem bei github an, wenn Sie Probleme damit sehen. Vielen Dank.

Nickfox
quelle
Ich habe Ihre Lösung ausprobiert, funktioniert aber nicht ... Wenn die App in den Hintergrund wechselt, wird die App in meinem Fall auch nach Erhöhung der Genauigkeit nicht als didUpdateToLocation-Methode bezeichnet
Hardik Darji
@ HardikDarji wichtige Frage. Ziehst du um? Wenn nicht, werden die Standortaktualisierungen möglicherweise gestoppt. Nehmen Sie Ihr Telefon für einen Spaziergang oder eine Fahrt mit und prüfen Sie, ob dies das Problem behebt.
Nickfox
Danke für die schnelle Antwort. Ich möchte jedoch alle 2 Minuten Standortaktualisierungen, ohne dass sich mein Telefon bewegt oder nicht. In diesem Szenario wird die didUpdateToLocation-Methode nicht aufgerufen. Ich suche hier nach: Wie bekomme ich alle n Minuten eine Standortaktualisierung !!!
Hardik Darji
Versuchen Sie, timeIntervalInSeconds auf 120 zu setzen, und kommentieren Sie diese Zeile aus: locationManager.pausesLocationUpdatesAutomatically = NO;
Nickfox
1
Tötet die Lösung, die Sie im obigen Kommentar gepostet haben, die Akkulaufzeit oder führen Sie noch Optimierungen dafür durch?
Samfr
2

Es scheint, dass stopUpdatingLocation den Hintergrund-Watchdog-Timer auslöst. Deshalb habe ich ihn in didUpdateLocation durch Folgendes ersetzt:

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

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

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

Ich schalte nur regelmäßig die Genauigkeit um, um alle paar Minuten eine hochgenaue Koordinate zu erhalten. Da der locationManager nicht gestoppt wurde, bleibt backgroundTimeRemaining auf seinem Maximalwert. Dies reduzierte den Batterieverbrauch von ~ 10% pro Stunde (mit konstanter kCLLocationAccuracyBest im Hintergrund) auf ~ 2% pro Stunde auf meinem Gerät

Amit Shelgaonkar
quelle
2

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
Haben Sie so etwas in Ziel c?
Moxarth
1

In iOS 9 und watchOS 2.0 gibt es eine neue Methode in CLLocationManager, mit der Sie den aktuellen Speicherort anfordern können: CLLocationManager: requestLocation (). Dies wird sofort abgeschlossen und gibt den Speicherort an den CLLocationManager-Delegaten zurück.

Mit dieser Methode können Sie jetzt jede Minute einen NSTimer verwenden, um einen Speicherort anzufordern, und müssen nicht mit den Methoden startUpdatingLocation und stopUpdatingLocation arbeiten.

Wenn Sie jedoch Standorte erfassen möchten, die auf einer Änderung von X Metern vom letzten Standort basieren, legen Sie einfach die Eigenschaft distanceFilter von CLLocationManger fest und rufen Sie X auf, um startUpdatingLocation () aufzurufen.

Malcolm
quelle
-1

Im Anhang befindet sich eine Swift-Lösung, die auf Folgendem basiert:

Definieren Sie App registers for location updatesin der info.plist

Lassen Sie den locationManager die ganze Zeit laufen

Wechseln Sie kCLLocationAccuracyzwischen BestForNavigation(für 5 Sekunden, um den Standort zu ermitteln) undThreeKilometers dem Rest der Wartezeit, um ein Entladen der Batterie zu vermeiden

In diesem Beispiel wird der Standort alle 1 Minute im Vordergrund und alle 15 Minuten im Hintergrund aktualisiert.

Das Beispiel funktioniert gut mit Xcode 6 Beta 6, das auf einem iOS 7-Gerät ausgeführt wird.

Im App-Delegaten (mapView ist ein optionaler Hinweis auf den 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
}

In der mapView (locationManager zeigt auf das Objekt im 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
quelle