Jak uzyskać aktualizację lokalizacji w tle co n minut w mojej aplikacji na iOS?


207

Szukam sposobu, aby uzyskać aktualizację lokalizacji w tle co n minut w mojej aplikacji na iOS. Korzystam z iOS 4.3 i rozwiązanie powinno działać na iPhone'ach bez jailbreaku.

Próbowałem / rozważałem następujące opcje:

  • CLLocationManager startUpdatingLocation/startMonitoringSignificantLocationChanges: Działa to w tle zgodnie z oczekiwaniami, w oparciu o skonfigurowane właściwości, ale wydaje się, że nie można zmusić go do aktualizacji lokalizacji co n minut
  • NSTimer: Działa, gdy aplikacja działa na pierwszym planie, ale wydaje się, że nie jest przeznaczona do zadań w tle
  • Powiadomienia lokalne: powiadomienia lokalne można planować co n minut, ale nie można wykonać kodu, aby uzyskać bieżącą lokalizację (bez konieczności uruchamiania aplikacji za pośrednictwem powiadomienia). Podejście to również nie wydaje się podejściem czystym, ponieważ nie do tego należy używać powiadomień.
  • UIApplication:beginBackgroundTaskWithExpirationHandler: O ile rozumiem, należy tego użyć do zakończenia niektórych prac w tle (również ograniczonych czasowo), gdy aplikacja zostanie przeniesiona w tło, zamiast wdrażania „długotrwałych” procesów w tle.

Jak mogę wdrożyć te regularne aktualizacje lokalizacji w tle?



użyteczne działania następcze: stackoverflow.com/questions/10235203/…
Lolo

1
Jeśli próbujesz sprawić, by działało na iOS 7, możesz wypróbować to rozwiązanie tutaj: stackoverflow.com/questions/18946881/... Jeśli masz jakieś pytanie, możesz dołączyć do dyskusji tutaj: mobileoop.com/background -lokalizacja-aktualizacja-programowanie-dla-ios-7
Ricky

1
Wszystkie twoje ustalenia są poprawne (cztery punkty). Cenne informacje, to znaczy, wiedząc, co nie pasuje do twojego przypadku użycia? I tak, gdy w TRYBIE ZAWIESZONYM lub NIEURUCHAMIAJĄCYM, nie ma ostatecznej metody aktualizacji co n-minut.
LenArt,

Odpowiedzi:


113

Znalazłem rozwiązanie do wdrożenia tego za pomocą forów Apple Developer:

  • Sprecyzować location background mode
  • Utwórz NSTimerw tle za pomocąUIApplication:beginBackgroundTaskWithExpirationHandler:
  • Kiedy njest mniejszy niż UIApplication:backgroundTimeRemainingbędzie działał dobrze. Gdy njest większy , location managernależy go ponownie włączyć (i wyłączyć), zanim nie pozostanie czas, aby uniknąć zabicia zadania w tle.

Działa to, ponieważ lokalizacja jest jednym z trzech dozwolonych typów wykonywania w tle .

Uwaga: straciłem trochę czasu, testując to w symulatorze, gdzie to nie działa. Działa to jednak dobrze na moim telefonie.


24
czy zdarza ci się mieć link do forum. Chcę zaimplementować ten sam typ mapowania lokalizacji i nie udało mi się go uruchomić. Lub bardzo przykładowy kod byłby bardzo mile widziany.
utahwithak

4
Czy możesz wyjaśnić, dlaczego zadanie w tle nie jest zabijane po 10 minutach (maksymalny dozwolony czas), jeśli po prostu zatrzymasz się i uruchomisz menedżera lokalizacji? czy jest to jakaś zamierzona funkcjonalność? brzmi bardziej jak błąd w Apple SDK, jeśli tak się dzieje. Na jakiej wersji iOS próbujesz?
saurabh

5
@all: Tak, nasza aplikacja jest dostępna w AppStore. Nie opublikuję całego kodu, wszystkie wspomniane punkty są wyraźnie udokumentowanymi funkcjami. Jeśli masz konkretny problem, opublikuj własne pytanie wyjaśniające, co próbowałeś i co poszło nie tak.
wjans

3
@ user836026: Tak, mam na myśli określenie trybu tła. Po zatrzymaniu aktualizacji lokalizacji należy ją ponownie uruchomić w ciągu 10 minut, aby uniknąć zamknięcia aplikacji.
wjans

12
Dla wszystkich zainteresowanych zobaczeniem rzeczywistego kodu odzwierciedlającego to, co jest tutaj omawiane, sprawdź stackoverflow.com/questions/10235203/...
Lolo

55

W systemie iOS 8/9/10, aby aktualizować lokalizację w tle co 5 minut, wykonaj następujące czynności:

  1. Przejdź do projektu -> Możliwości -> Tryby tła -> wybierz Aktualizacje lokalizacji

  2. Przejdź do projektu -> Informacje -> dodaj klucz NSLocationAlwaysUsageDescription z pustą wartością (lub opcjonalnie dowolnym tekstem)

  3. Aby lokalizacja działała, gdy aplikacja jest w tle, i wysyłaj współrzędne do serwisu internetowego lub rób z nimi cokolwiek co 5 minut, implementuj ją jak w poniższym kodzie.

Nie używam żadnych zadań ani timerów w tle. Testowałem ten kod na moim urządzeniu z iOS 8.1, które leżało na moim biurku przez kilka godzin, a moja aplikacja działała w tle. Urządzenie zostało zablokowane, a kod działał poprawnie cały czas.

@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
Czy to działa dłużej niż kilka godzin? Wygląda na to, że nawet jeśli aplikacja nie zostanie zakończona, urządzenie przestaje przesyłać aktualizacje lokalizacji po około godzinie od przejścia aplikacji w tło.
Myxtic

2
Pobieranie w tle jest wyłączone w moim projekcie (nie powinno mieć znaczenia, czy jest wyłączone, czy włączone). Jednak pauseLocationUpdatesAutomatycznie musi być ustawiony na NO, aby powyższy przykład działał poprawnie. NIE wznowi się automatycznie, gdy ponownie zaczniesz się poruszać, jeśli system został wcześniej wstrzymany. Dlatego w powyższym przykładzie ustawiłem tę właściwość na NIE.
Leszek Szary

1
Myślę, że masz absolutną rację. Znalazłem więcej informacji na temat problemu tutaj: stackoverflow.com/q/17484352/1048331
Myxtic

2
@LeszekS musisz dodać poniższy kod dla iOS 9 w tle - if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) { [self.locationManager setAllowsBackgroundLocationUpdates:YES]; }
Fenil

2
Jaka jest wygoda robienia tego? Nadal wyczerpujesz baterię, mając stałą wysoką dokładność. Jedyną rzeczą, jaką można zrobić, to nie jesteś w rzeczywistości nie używać już odebrany lokalizację aż co 5 minut ...
Miód

34

Zrobiłem to w opracowywanej przeze mnie aplikacji. Timery nie działają, gdy aplikacja jest w tle, ale aplikacja stale otrzymuje aktualizacje lokalizacji. Przeczytałem gdzieś w dokumentacji (wydaje się, że nie mogę jej teraz znaleźć, opublikuję aktualizację, gdy to zrobię), że metodę można wywołać tylko w aktywnej pętli uruchamiania, gdy aplikacja jest w tle. Delegat aplikacji ma aktywną pętlę uruchamiania, nawet w tle, więc nie musisz tworzyć własnej, aby to działało. [Nie jestem pewien, czy to prawidłowe wyjaśnienie, ale tak zrozumiałem z tego, co przeczytałem]

Przede wszystkim dodaj locationobiekt klucza UIBackgroundModesdo info.plist swojej aplikacji. Teraz musisz rozpocząć aktualizację lokalizacji w dowolnym miejscu aplikacji:

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

Następnie napisz metodę obsługi aktualizacji lokalizacji, powiedzmy -(void)didUpdateToLocation:(CLLocation*)location, w delegacie aplikacji. Następnie wdrożyć metodę locationManager:didUpdateLocation:fromLocationz CLLocationManagerDelegatew klasie, w której rozpoczął kierownik lokalizacji (od ustawiamy Location Manager delegatem „ja”). Wewnątrz tej metody musisz sprawdzić, czy upłynął przedział czasu, po którym musisz obsłużyć aktualizacje lokalizacji. Możesz to zrobić, zapisując za każdym razem aktualny czas. Jeśli ten czas minął, wywołaj metodę UpdateLocation od delegata aplikacji:

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

Spowoduje to wywołanie metody co 5 minut, nawet gdy aplikacja jest w tle. Imp: Ta implementacja wyczerpuje baterię, jeśli dokładność danych lokalizacji nie jest krytyczna, powinieneś użyć[locationManager startMonitoringSignificantLocationChanges]

Przed dodaniem tego do aplikacji przeczytaj Przewodnik po programowaniu rozpoznawania lokalizacji


3
W ten sposób usługi lokalizacyjne są stale włączone (w rzeczywistości wyczerpuje się akumulator), nie chcę tego. Chcę włączyć usługę lokalizacji co n minut i natychmiast ją wyłączyć, gdy mam dobrą poprawkę (zauważyłem, że nie wyjaśniłem tego jasno w moim pytaniu). Mogę osiągnąć to zachowanie w opisanym przeze mnie rozwiązaniu.
wjans

3
Możesz ustawić dokładność menedżera lokalizacji na 1 km - dzięki temu bateria pozostanie prawie nienaruszona. Po 5 minutach ustawiasz dokładność na 1m. Gdy uzyskasz satysfakcjonującą lokalizację (zwykle po 5 s), po prostu ustaw dokładność z powrotem na 1 km.
knagode

knagode, wypróbowałem sugerowane rozwiązanie problemu rozładowania baterii, ale nawet po zwiększeniu dokładności po N minutach metoda locationManager: didUpdateLocations nie jest wywoływana ponownie. Próbowałem startUpdating i stopUpdating, zamiast zwiększać i zmniejszać dokładność, nazywa się to pomyślnie delegować lokalizacjęManager: metoda didUpdateLocations, po N minutach, ale nie działa w TRYBIE w tle ...
Hardik Darji

Co do linków dokumentacji. Zobacz tutaj : „Metody obiektu delegowanego są wywoływane z wątku, w którym uruchomiono odpowiednie usługi lokalizacyjne. Ten wątek sam musi mieć aktywną pętlę uruchamiania, taką jak ta znaleziona w głównym wątku aplikacji”.
Kochanie,

24

Teraz, gdy iOS6 jest obecnie najlepszym sposobem, aby mieć zawsze działające usługi lokalizacyjne, ...

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

}

Właśnie przetestowałem to w ten sposób:

Uruchomiłem aplikację, idę w tle i poruszam się w samochodzie o kilka minut. Potem wracam do domu na 1 godzinę i znów zaczynam się przeprowadzać (bez ponownego otwierania aplikacji). Lokalizacje zaczęły się od nowa. Następnie zatrzymał się na dwie godziny i zaczął od nowa. Wszystko znowu w porządku ...

NIE ZAPOMNIJ UŻYWAĆ nowych usług lokalizacyjnych w 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
Jeśli aplikacja ulegnie awarii lub zostanie zabita, system nie uruchomi się ponownie, prawda?
Ken

1
Aby uzyskać bardziej czytelny kod, możesz zrobić [lokalizacje lastObject]; zamiast [lokacje objectAtIndex: [liczba lokalizacji] - 1]
axello

twoja metoda jest tylko na ios6?
pengwang

Czy musimy to wykorzystać? Myślałem, że po prostu mam kod menedżera lokalizacji w widoku klasyDidLoad klasy i ustawiłem klucz tła w pliku plist, który aplikacja rejestruje w celu aktualizacji lokalizacji powinna być wystarczająco dobra? Czy możesz mi w tym pomóc!
nithinreddy

Tak, będzie działać jak urok nithinreddy, ale przestanie działać po 10 minutach, ponieważ iOS zabija długie wątki po tym okresie. Moje rozwiązanie jest idealne, jeśli chcesz, aby te usługi były uruchamiane na zawsze. Zrobiłem kilka testów dwa dni temu i rozładowuje 6% baterii co godzinę. Zamiast tego użycie [locationManager startUpdatingLocation] spowoduje wyczerpanie 17%
Alejandro Luengo

13

Dla kogoś innego, kto ma koszmar, wymyśl to. Mam proste rozwiązanie.

  1. spójrz na ten przykład z raywenderlich.com -> masz przykładowy kod, działa to doskonale, ale niestety nie ma timera podczas lokalizacji w tle. będzie to działać w nieskończoność.
  2. Dodaj licznik czasu, używając:

    -(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. Po prostu nie zapomnij dodać „Rejestrów aplikacji do aktualizacji lokalizacji” w info.plist.


Czy uzyskuje to lokalizację przez ponad 3 minuty?
SleepNot

Musisz ustawić lokalizację w obszarze Możliwości -> Tryb tła.
Sergio Andreotti,

To nie działa!! ? Sprawdziłem na iOS 8 i iOS 10
Kirti

Popraw mnie, jeśli się mylę. Na podstawie tego, co przeczytałem, lokalizację menedżera lokalizacji uruchamiasz tylko raz. Następnie wszystkie przedziały są zbędne. Ponieważ już się rozpoczęło
Honey,

działa, ale zatrzymuje się po 170 sekundach. chcę uruchomić moje zadanie w tle przez nieograniczony czas
anshuman burmman

8

Oto czego używam:

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

Zaczynam śledzenie w AppDelegate w ten sposób:

BackgroundLocationManager.instance.start()

Dzięki. Nasz projekt musi śledzić lokalizację użytkownika + wysyłać zdarzenia PubNub w tle, a Twoje rozwiązanie działa naprawdę dobrze.
user921509,

Cześć Hmitkov, Gdzie mogę zadzwonić Metoda sendLocationToServer, aby wysłać lokalizację użytkownika na serwerze
AmanGupta007

@ AmanGupta007 Możesz wywołać sendLocationToServer w func locationManager (manager: didUpdateLocations :). Zwróć uwagę na komentarz // TODO w kodzie.
hmitkov

@hmitkov Czy można w tej aplikacji uruchamiać i zatrzymywać Usługi lokalizacyjne, gdy aplikacja działa w tle? Przykład: uruchom usługę lokalizacyjną od powiadomienia wypychanego, weź trochę lat / long, wyślij do usługi internetowej, a następnie przestań aktualizować lokalizację. Zrób to za każdym razem, gdy „treść dostępna” = 1 znajduje się w korpusie push.
DookieMan

1
Próbowałem tego kodu, ale wygląda na to, że nie działa na iOS11? (Nie testowałem tego na żadnych innych wersjach.)
Tom

6

Niestety wszystkie twoje założenia wydają się słuszne i nie sądzę, aby można to zrobić. Aby oszczędzać baterię, usługi lokalizacyjne iPhone'a są oparte na ruchu. Jeśli telefon znajduje się w jednym miejscu, jest niewidoczny dla usług lokalizacyjnych.

CLLocationManagerZadzwoni tylko locationManager:didUpdateToLocation:fromLocation:wtedy, gdy telefon odbiera aktualizacji lokalizacji, co się dzieje, jeśli tylko jeden z trzech usług lokalizacyjnych (wieża komórka, GPS, WiFi) postrzega zmianę.

Kilka innych rzeczy, które mogą pomóc w uzyskaniu informacji o dalszych rozwiązaniach:

  • Uruchomienie i zatrzymanie usług powoduje didUpdateToLocationwywołanie metody delegowanej, ale newLocationmoże mieć stary znacznik czasu.

  • Monitorowanie regionu może pomóc

  • Podczas pracy w tle należy pamiętać, że uzyskanie pełnej „obsługi” usług lokalizacyjnych zatwierdzonych przez Apple może być trudne. Z tego, co widziałem, zostały one zaprojektowane specjalnie startMonitoringSignificantLocationChangesjako energooszczędna alternatywa dla aplikacji, które wymagają obsługi lokalizacji w tle, i zdecydowanie zachęcają programistów do korzystania z niej, chyba że aplikacja absolutnie tego potrzebuje.

Powodzenia!

AKTUALIZACJA: Te myśli mogą być już nieaktualne. Wygląda na to, że ludzie odnoszą sukcesy z odpowiedzią @wjans powyżej.


1
W AppStore dostępne są aplikacje (np. „Mój locus”), które umożliwiają aktualizację lokalizacji w tle. Nie utrzymują one aktywnej usługi lokalizacji, ale zostaje ona wkrótce włączona zgodnie z określonym interwałem. Jak oni to robią?
wjans

2
W opisywanej sytuacji aplikacja najprawdopodobniej używa metody startMonitoringSinentantLocationChanges. Tutaj telefon tymczasowo „budzi się” po otrzymaniu aktualizacji lokalizacji, ale nie ma interwału, który można ustawić, aby „pingować” tę usługę w tle. Gdy telefon się porusza (lub przełącza się z sieci komórkowej na GPS lub Wi-Fi), uruchamia aktualizację. Wykład Stanforda iTunes U na ten temat był dla mnie bardzo pomocny - mam nadzieję, że pomoże ci znaleźć obejście: itunes.apple.com/us/itunes-u/iphone-application-development/...
Chazbot

2
Dzięki za wskaźnik. Jednak nadal nie rozumiem, co robi aplikacja. Nawet jeśli mój telefon stoi przy biurku i nie porusza się wcale, widzę, że usługa lokalizacji jest uruchamiana co 10 minut (dokładnie). Jeśli dobrze rozumiem, startMonitoringSignificantLocationChangesnie dałbym żadnej aktualizacji w tym przypadku.
wjans

1
@ wjans, jakie jest zużycie baterii telefonu, czy zauważyłeś, że szybko się wyczerpuje, może z powodu aplikacji Mylocus?
user836026

6

Napisałem aplikację za pomocą usług lokalizacyjnych, aplikacja musi wysyłać lokalizację co 10s. I działało bardzo dobrze.

Wystarczy użyć metody „ allowDeferredLocationUpdatesUntilTraveled: timeout ”, postępując zgodnie z dokumentem Apple.

To co zrobiłem to:

Wymagane: Zarejestruj tryb tła dla aktualizacji Lokalizacja.

1. Twórz LocationMangeri za startUpdatingLocationpomocą accuracyi filteredDistancejak chcesz:

-(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. Aby aplikacja działała nieprzerwanie przy użyciu allowDeferredLocationUpdatesUntilTraveled:timeoutmetody w tle, musisz ponownie uruchomić updatingLocationnowy parametr, gdy aplikacja przejdzie do tła, w następujący sposób:

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

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

3. Aplikacja jest aktualizowana jak zwykle z locationManager:didUpdateLocations:oddzwanianiem:

-(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. Ale powinieneś obsłużyć dane, a następnie locationManager:didFinishDeferredUpdatesWithError:oddzwonić w swoim celu

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

     _deferringUpdates = NO;

     //do something 
}

5. UWAGA: Myślę, że powinniśmy zresetować parametryLocationManager każdym razem, gdy aplikacja przełącza się między trybem tła / forgroundu.


@Wszystkie rozwiązanie, które to lub wyżej wyjaśnione rozwiązanie wjans powinno preferować oszczędzanie baterii urządzenia, czy oba wpłyną na to samo?
Yash

2
Wypróbowałem oba rozwiązania, o których wspomniałeś, i zobaczyłem, że jeden z @ wjans jest trochę bardziej oszczędny. Ale po pojawieniu się iOS 8 wydaje się, że to rozwiązanie nie działa już poprawnie. Aby uzyskać więcej informacji: przez większość czasu aplikacja nie może długo żyć w trybie tła.
samthui7

@ samthui7 Dlaczego ustawiasz pauseLocationUpdatesAutomatically = false?
CedricSoubrie,

Wymaganiem mojej aplikacji jest ciągłe wysyłanie userLocation co 10s, a jednocześnie pausesLocationUpdatesAutomatically = trueinformuje menedżera lokalizacji, aby wstrzymywał aktualizacje, jeśli pozornie nie ma zmiany lokalizacji ( dokument Apple ). W każdym razie nie testowałem location manager pauses updatesjeszcze wyraźnie przypadku : D.
samthui7

@CedricSoubrie: Setting pausesLocationUpdatesAutomatically = true powoduje, że moja aplikacja przestaje aktualizować lokalizację.
samthui7,

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

Jest to potrzebne do śledzenia lokalizacji w tle od iOS 9.


1
To uratowało mi dzień! za pomocą docelowego miejsca wdrożenia systemu iOS8 na urządzeniu z systemem iOS9
Yaro

4

Użyłem metody xs2bush, aby uzyskać interwał (użycie timeIntervalSinceDate) i trochę go rozwinąłem. Chciałem się upewnić, że uzyskuję wymaganą dokładność, której potrzebowałem, a także że nie wyczerpuję baterii, utrzymując radio GPS więcej niż to konieczne.

Utrzymuję lokalizację działającą nieprzerwanie z następującymi ustawieniami:

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

jest to stosunkowo niski poziom zużycia baterii. Kiedy będę gotowy do następnego okresowego odczytu lokalizacji, najpierw sprawdzam, czy lokalizacja mieści się w mojej pożądanej dokładności, a jeśli tak, to używam lokalizacji. Jeśli nie, zwiększam dokładność o:

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

uzyskaj moją lokalizację, a następnie, gdy już ją znajdę, ponownie zmniejszam dokładność, aby zminimalizować zużycie baterii. Napisałem pełną działającą próbkę tego, a także napisałem źródło kodu po stronie serwera w celu zebrania danych o lokalizacji, przechowywania ich w bazie danych i umożliwienia użytkownikom przeglądania danych GPS w czasie rzeczywistym lub pobierania i przeglądania wcześniej zapisanych tras. Mam klientów na iOS, Androida, Windows Phone i java ja. Wszyscy klienci są napisani w sposób natywny i wszyscy działają poprawnie w tle. Projekt jest na licencji MIT.

Projekt systemu iOS jest przeznaczony dla systemu iOS 6 przy użyciu podstawowego zestawu SDK systemu iOS 7. Kod można pobrać tutaj .

Jeśli zauważysz jakiekolwiek problemy, zgłoś problem na github. Dzięki.


Wypróbowałem Twoje rozwiązanie, ale nie działa ... kiedy aplikacja przechodzi w tło, nawet po zwiększeniu dokładności aplikacja nie otrzymuje w moim przypadku metody didUpdateToLocation
Hardik Darji

@HardikDarji ważne pytanie. Przeprowadzasz się Jeśli nie, aktualizacje lokalizacji mogą się zatrzymać. Spróbuj zabrać telefon na spacer lub na przejażdżkę i sprawdź, czy to rozwiązuje problem.
nickfox

Dziękuję za szybką odpowiedź. Ale chcę aktualizować lokalizację co 2 minuty, bez względu na to, czy mój telefon się porusza, czy nie. W tym scenariuszu metoda didUpdateToLocation nie jest wywoływana. Szukam tutaj: Jak uzyskać aktualizację lokalizacji co n minut !!!
Hardik Darji,

spróbuj ustawić timeIntervalInSeconds na 120 i odkomentuj ten wiersz: locationManager.pauzujeLokalizacjaUaktualnieniaAutomatycznie = NIE;
nickfox

1
czy rozwiązanie, które opublikowałeś w powyższym komentarzu, zabija żywotność baterii, czy nadal dokonujesz optymalizacji?
samfr

2

Wygląda na to, że stopUpdatingLocation wyzwala licznik czasu nadzorowania w tle, więc zastąpiłem go w didUpdateLocation:

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

który wydaje się skutecznie wyłączać GPS. Selektor tła NSTimer staje się wtedy:

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

Wszystko, co robię, to okresowe przełączanie dokładności w celu uzyskania współrzędnych o wysokiej dokładności co kilka minut, a ponieważ nie udało się zatrzymać locationManager, backgroundTimeRemaining pozostaje na maksymalnej wartości. To zmniejszone zużycie baterii z ~ 10% na godzinę (ze stałym kCLLocationAccuracyBest w tle) do ~ 2% na godzinę na moim urządzeniu


2

Istnieje cocoapod APScheduledLocationManager, który pozwala otrzymywać aktualizacje lokalizacji w tle co n sekund z pożądaną dokładnością lokalizacji.

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

Repozytorium zawiera także przykładową aplikację napisaną w Swift 3.


czy masz coś takiego w celu c?
Moxarth,

1

W iOS 9 i watchOS 2.0 w CLLocationManager dostępna jest nowa metoda, która pozwala zażądać bieżącej lokalizacji: CLLocationManager: requestLocation (). To kończy się natychmiast, a następnie zwraca lokalizację do delegata CLLocationManager.

Możesz użyć NSTimer do żądania lokalizacji co minutę za pomocą tej metody i nie musisz pracować z metodami startUpdatingLocation i stopUpdatingLocation.

Jeśli jednak chcesz przechwytywać lokalizacje na podstawie zmiany X metrów od ostatniej lokalizacji, po prostu ustaw właściwość distanceFilter CLLocationManger i wywołaj funkcję X startUpdatingLocation ().


-1

W załączeniu znajduje się szybkie rozwiązanie oparte na:

Definiować App registers for location updates w info.plist

Utrzymuj działanie menedżera położenia przez cały czas

Przełączaj kCLLocationAccuracypomiędzy BestForNavigation(przez 5 sekund, aby uzyskać lokalizację) aThreeKilometers przez resztę okresu oczekiwania, aby uniknąć rozładowania baterii

Ten przykład aktualizuje lokalizację co 1 minutę na pierwszym planie i co 15 minut w tle.

Przykład działa dobrze z Xcode 6 Beta 6, działającym na urządzeniu z systemem iOS 7.

W delegacie aplikacji (mapView jest opcjonalnym wskazaniem kontrolera mapView)

func applicationDidBecomeActive(application: UIApplication!) {
    if appLaunched! == false { // Reference to mapView used to limit one location update per timer cycle
        appLaunched = true
        var appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
        var window = appDelegate.window
        var tabBar = window?.rootViewController as UITabBarController
        var navCon = tabBar.viewControllers[0] as UINavigationController
        mapView = navCon.topViewController as? MapViewController
    }
    self.startInitialPeriodWithTimeInterval(60.0)
}

func applicationDidEnterBackground(application: UIApplication!) {
    self.startInitialPeriodWithTimeInterval(15 * 60.0)
}

func startInitialPeriodWithTimeInterval(timeInterval: NSTimeInterval) {
    timer?.invalidate() // reset timer
    locationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation
    timer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: Selector("getFirstLocationUpdate:"), userInfo: timeInterval, repeats: false)
}

func getFirstLocationUpdate(sender: NSTimer) {
    let timeInterval = sender.userInfo as Double
    timer?.invalidate()
    mapView?.canReportLocation = true
    timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval, target: self, selector: Selector("waitForTimer:"), userInfo: timeInterval, repeats: true)
}

func waitForTimer(sender: NSTimer) {
    let time = sender.userInfo as Double
    locationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation
    finalTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: Selector("getLocationUpdate"), userInfo: nil, repeats: false)
}

func getLocationUpdate() {
    finalTimer?.invalidate()
    mapView?.canReportLocation = true
}

W mapView (locationManager wskazuje obiekt w 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")
        }
    }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.