Okresowe aktualizacje lokalizacji w tle iOS


92

Piszę aplikację, która wymaga aktualizacji lokalizacji w tle z dużą dokładnością i niską częstotliwością . Rozwiązaniem wydaje się być działające w tle zadanie NSTimer, które uruchamia aktualizacje menedżera lokalizacji, które następnie natychmiast się wyłączają. To pytanie zostało już zadane:

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

Uzyskiwanie lokalizacji użytkownika co n minut po przejściu aplikacji w tło

iOS Nie jest to typowy problem z zegarem śledzenia lokalizacji w tle

Długo działający licznik czasu w tle w systemie iOS z trybem tła „lokalizacja”

Pełnoetatowa usługa iOS w tle oparta na śledzeniu lokalizacji

ale jeszcze nie uzyskałem minimalnego działającego przykładu . Po wypróbowaniu każdej permutacji powyższych zaakceptowanych odpowiedzi ustaliłem punkt wyjścia. Wejście w tło:

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

i metoda delegata:

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

    NSLog(@"%@", newLocation);

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

}

Bieżące zachowanie polega na tym, że backgroundTimeRemainingzmniejsza się od 180 sekund do zera (podczas rejestrowania lokalizacji), a następnie wykonuje procedurę obsługi wygaśnięcia i nie są generowane żadne dalsze aktualizacje lokalizacji. Jak zmodyfikować powyższy kod, aby otrzymywać okresowe aktualizacje lokalizacji w tle przez czas nieokreślony ?

Aktualizacja : Celuję w iOS 7 i wydaje się, że istnieją dowody na to, że zadania w tle zachowują się inaczej:

Uruchom Menedżera lokalizacji w iOS 7 z zadania w tle


Nie wspominasz, na którą wersję iOS kierujesz reklamy. iOS 7 (na przykład) ma inny system wielozadaniowości niż jego poprzednik.
Jason Whitehorn,

@JasonWhitehorn Słuszna uwaga. Zaktualizowałem pytanie.
przeniesieniu

2
@pcoving: czy Twoje rozwiązanie będzie działać, nawet jeśli aplikacja zostanie zabita lub zamknięta?
Nirav

Odpowiedzi:


62

Wygląda na stopUpdatingLocationto, że to właśnie uruchamia licznik czasu watchdoga w tle, więc zastąpiłem go didUpdateLocation:

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

co wydaje się skutecznie wyłączać GPS. Selektor tła NSTimerzmienia się wtedy w:

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

Wszystko, co robię, to okresowe przełączanie dokładności, aby uzyskać współrzędną o wysokiej dokładności co kilka minut, a ponieważ locationManagernie został zatrzymany, backgroundTimeRemainingpozostaje na maksymalnej wartości. To zmniejszone zużycie baterii z ~ 10% na godzinę (ze stałym kCLLocationAccuracyBestw tle) do ~ 2% na godzinę na moim urządzeniu.


4
Czy możesz zaktualizować swoje pytanie, podając pełny przykład roboczy? Coś w rodzaju: „UPDATE: rozgryzłem to. Oto moje rozwiązanie…”
smholloway

@Virussmca Wróciłem do poprzedniej odpowiedzi, która jest znacznie bardziej niezawodna i ma podobną wydajność. Wydaje się, że całkowite zatrzymanie aktualizacji lokalizacji wyzwala stan wyścigu (lub inny niedeterministyczny scenariusz).
puszczam

To podejście nie będzie działać zgodnie z oczekiwaniami: 1) Zwiększenie wartości właściwości distanceFilter nie powoduje oszczędzania baterii, ponieważ GPS nadal musi określać Twoją lokalizację. 2) W iOS 7 czas w tle nie jest ciągły. 3) Możesz wziąć pod uwagę znaczące usługi ChangeLocation. 4) Poza twoim rozwiązaniem w iOS 7 nie możesz uruchomić UIBackgroundModes - lokalizacja z tła ...
aMother

2
@aMother 1) Zdaję sobie sprawę, że zwiększenie filtra odległości (a nie przetwarzanie wywołań zwrotnych) nie oszczędza dużo baterii. Jednak setDesiredAccuracy tak! Opiera się na informacjach z wieży komórkowej, które są w zasadzie bezpłatne. 2) nieciągłe? 3) Wytłuszczonym drukiem zaznaczyłem, że wymagam dużej dokładności. Nie zapewniają tego znaczące zmiany lokalizacji. 4) Usługi lokalizacyjne nie są uruchamiane / zatrzymywane w tle.
puszczam

2
Wiem, że to już stary post, ale użytkownicy mojej aplikacji korzystającej z tej sztuczki zauważyli znaczny wzrost zużycia baterii od czasu aktualizacji do iOS9. Jeszcze nie zbadałem, ale mam wrażenie, że ta „sztuczka” może już nie działać?
Gary Wright,

45

Napisałem aplikację przy użyciu usług lokalizacyjnych, aplikacja musi wysyłać lokalizację co 10 sekund. I działało bardzo dobrze.

Po prostu użyj metody „ allowDeferredLocationUpdatesUntilTraveled: timeout ”, zgodnie z dokumentem Apple.

Kroki są następujące:

Wymagane: zarejestruj tryb tła dla aktualizacji lokalizacji.

1. Utwórz LocationManger i rozpocznijUpdatingLocation z dokładnością i filterDistance tak, jak 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 wiecznie przy użyciu metody „allowDeferredLocationUpdatesUntilTraveled: timeout” w tle, musisz ponownie uruchomić aktualizację obiektu updateLocation z nowym parametrem, gdy aplikacja przechodzi w tło, na przykład:

- (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 normalnie przez wywołanie zwrotne „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. Ale powinieneś obsłużyć dane w następnie wywołaniu zwrotnym „locationManager: didFinishDeferredUpdatesWithError:” w swoim celu

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

     _deferringUpdates = NO;

     //do something 
}

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


To działa świetnie i uważam, że jest to właściwy sposób, jak wspomniano na WWDC w zeszłym roku !! Dzięki za post
prasad1250

@ samthui7 dzięki, to działa, ale muszę również uzyskać lokalizację po rozwiązaniu. jak to możliwe.??
Mohit Tomar

@ samthui7 .. proszę zobaczyć tę kolejkę ... stackoverflow.com/questions/37338466/…
Suraj Sukale

2
@amar: Sprawdziłem też wersję beta iOS 10, nadal działa. Zauważ, że musimy dodać kod przed startUpdatingLocation: allowsBackgroundLocationUpdates = YESi requestAlwaysAuthorizationlub requestWhenInUseAuthorization. Np .: `CLLocationManager * locationManager = [[CLLocationManager przydziel] init]; locationManager.delegate = self; locationManager.allowsBackgroundLocationUpdates = YES; [requestAlwaysAuthorization locationManager]; [locationManager startUpdatingLocation]; `
samthui7

1
@Nirav Nie. Z tego co wiem, nie sądzę, by Apple pozwolił nam na to.
samthui7

38
  1. Jeśli masz UIBackgroundModesw plistie z locationkluczem, nie musisz używać beginBackgroundTaskWithExpirationHandlermetody. To jest zbędne. Również używasz go niepoprawnie (patrz tutaj ), ale to dyskusyjne, ponieważ Twój plist jest ustawiony.

  2. Po umieszczeniu UIBackgroundModes locationw pliku plist aplikacja będzie działać w tle przez czas nieokreślony, dopóki CLLocationMangerjest uruchomiona. Jeśli zadzwoniszstopUpdatingLocation w tle, aplikacja zatrzyma się i nie uruchomi się ponownie.

    Może mógłbyś zadzwonić beginBackgroundTaskWithExpirationHandlertuż przed dzwonieniem, stopUpdatingLocationa po zakończeniu rozmowy startUpdatingLocationmógłbyś zadzwonić pod numerendBackgroundTask aby utrzymać go w tle, gdy GPS jest zatrzymany, ale nigdy tego nie próbowałem - to tylko pomysł.

    Inną opcją (której nie próbowałem) jest pozostawienie menedżera lokalizacji działającego w tle, ale gdy uzyskasz dokładną lokalizację, zmień desiredAccuracywłaściwość na 1000 m lub więcej, aby umożliwić wyłączenie chipa GPS (aby oszczędzać baterię). Następnie 10 minut później, gdy potrzebujesz kolejnej aktualizacji lokalizacji, zmień desiredAccuracytył na 100 m, aby włączyć GPS, aż uzyskasz dokładną lokalizację, powtórz.

  3. Kiedy dzwonisz startUpdatingLocationdo kierownika lokalizacji, musisz dać mu czas na zajęcie stanowiska. Nie powinieneś od razu dzwonić stopUpdatingLocation. Pozwalamy mu działać przez maksymalnie 10 sekund lub do momentu uzyskania niebuforowanej lokalizacji o wysokiej dokładności.

  4. Musisz odfiltrować lokalizacje zapisane w pamięci podręcznej i sprawdzić dokładność uzyskanej lokalizacji, aby upewnić się, że spełnia ona minimalną wymaganą dokładność (patrz tutaj ). Pierwsza otrzymana aktualizacja może mieć 10 minut lub 10 dni. Pierwsza dokładność, jaką uzyskasz, może wynosić 3000 m.

  5. Zamiast tego rozważ użycie interfejsów API zmiany lokalizacji. Gdy otrzymasz powiadomienie o znaczącej zmianie, możesz zacząć CLLocationManagerna kilka sekund, aby uzyskać pozycję o wysokiej dokładności. Nie jestem pewien, nigdy nie korzystałem z usług znaczącej zmiany lokalizacji.


Dokumentacja Apple CoreLocationwydaje się zalecać użycie beginBackgroundTaskWithExpirationHandlerpodczas przetwarzania danych o lokalizacji w tle: „Jeśli aplikacja na iOS potrzebuje więcej czasu na przetworzenie danych lokalizacji, może zażądać dłuższego czasu wykonania w tle przy użyciu metody beginBackgroundTaskWithName: expirationHandler: klasy UIApplication”. - developer.apple.com/library/ios/documentation/UserExperience/ ...
eliocs,

4
nie zapomnij dodać _locationManager.allowsBackgroundLocationUpdates = YES; na iOS9 +, w innym przypadku aplikacja przejdzie w stan uśpienia po 180 sekundach
kolancz

@kolyancz Właśnie wypróbowałem to na iOS 10.1.1 dla iPhone'a 6+. Zaśnięcie i uruchomienie się zajęło mu około 17 minut locationManagerDidPauseLocationUpdates. Sprawdziłem to zachowanie 10 razy!
Kochanie,

Naprawdę nie rozumiem, dlaczego mówisz, że to dyskusyjne, a potem mówisz, że powinieneś zawinąć to w tło
Kochanie

10

Kiedy nadchodzi czas na uruchomienie usługi lokalizacyjnej i zatrzymanie zadania w tle, zadanie w tle należy zatrzymać z opóźnieniem (powinna wystarczyć 1 sekunda). W przeciwnym razie usługa lokalizacji nie zostanie uruchomiona. Również usługa lokalizacji powinna być włączona przez kilka sekund (np. 3 sekundy).

Istnieje cocoapod APScheduledLocationManager, który umożliwia uzyskiwanie aktualizacji lokalizacji w tle co n sekund z wymaganą dokładnością lokalizacji.

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

Repozytorium zawiera również przykładową aplikację napisaną w języku Swift 3.


1

Co powiesz na wypróbowanie go z startMonitoringSignificantLocationChanges:API? Zdecydowanie strzela z mniejszą częstotliwością, a celność jest dość dobra. Dodatkowo ma o wiele więcej zalet niż używanie innych locationManagerAPI.

Wiele więcej informacji na temat tego interfejsu API zostało już omówione pod tym linkiem


1
Próbowałem tego podejścia. To narusza wymóg dokładności - z dokumentów : This interface delivers new events only when it detects changes to the device’s associated cell towers
puszczam

1

Musisz dodać tryb aktualizacji lokalizacji w swojej aplikacji, dodając następujący klucz w swoim info.plist.

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

didUpdateToLocationzostanie wywołana metoda (nawet gdy aplikacja działa w tle). Możesz wykonać dowolną rzecz na podstawie tego wywołania metody


Tryb aktualizacji lokalizacji jest już w info.plist. Jak wspomniałem, otrzymuję aktualizacje aż do przekroczenia limitu czasu w tle wynoszącego 180 sekund.
przeniesieniu

0

Po iOS 8 wprowadzono kilka zmian w pobieraniu w tle środowiska CoreLocation i aktualizacjach wprowadzonych przez firmę Apple.

1) Apple wprowadza żądanie AlwaysAuthorization w celu pobrania aktualizacji lokalizacji w aplikacji.

2) Apple wprowadza backgroundLocationupdates do pobierania lokalizacji z tła w iOS.

Aby pobrać lokalizację w tle, musisz włączyć Aktualizację lokalizacji w Możliwościach Xcode.

//
//  ViewController.swift
//  DemoStackLocation
//
//  Created by iOS Test User on 02/01/18.
//  Copyright © 2018 iOS Test User. All rights reserved.
//

import UIKit
import CoreLocation

class ViewController: UIViewController {

    var locationManager: CLLocationManager = CLLocationManager()
    override func viewDidLoad() {
        super.viewDidLoad()
        startLocationManager()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    internal func startLocationManager(){
        locationManager.requestAlwaysAuthorization()
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.delegate = self
        locationManager.allowsBackgroundLocationUpdates = true
        locationManager.startUpdatingLocation()
    }

}

extension ViewController : CLLocationManagerDelegate {

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
        let location = locations[0] as CLLocation
        print(location.coordinate.latitude) // prints user's latitude
        print(location.coordinate.longitude) //will print user's longitude
        print(location.speed) //will print user's speed
    }

}

0

Aby korzystać ze standardowych usług lokalizacyjnych, gdy aplikacja działa w tle, należy najpierw włączyć Tryby w tle na karcie Możliwości w ustawieniach celu i wybrać Aktualizacje lokalizacji.

Lub dodaj go bezpośrednio do Info.plist .

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

Następnie musisz skonfigurować CLLocationManager

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

Szybki

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


-2
locationManager.allowsBackgroundLocationUpdates = true

4
Czy możesz zmienić swoją odpowiedź i dodać wyjaśnienie, dlaczego / jak to rozwiązuje problem?
barbsan
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.