Периодические фоновые обновления местоположения iOS

92

Я пишу приложение, которое требует обновления местоположения в фоновом режиме с высокой точностью и низкой частотой . Решением, похоже, является фоновая задача NSTimer, которая запускает обновления диспетчера местоположения, которые затем немедленно закрываются. Этот вопрос задавали раньше:

Как мне получать фоновое обновление местоположения каждые n минут в моем приложении iOS?

Получение местоположения пользователя каждые n минут после перехода приложения в фоновый режим

iOS Не типичная проблема с таймером отслеживания местоположения в фоновом режиме

Долгосрочный фоновый таймер iOS с фоновым режимом "местоположение"

Постоянная фоновая служба iOS на основе отслеживания местоположения

но мне еще предстоит получить минимальный рабочий пример . Попробовав каждую перестановку принятых выше ответов, я составил отправную точку. Ввод фона:

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

и метод делегата:

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

    NSLog(@"%@", newLocation);

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

}

Текущее поведение заключается в том, что время backgroundTimeRemainingуменьшается со 180 секунд до нуля (во время регистрации местоположения), а затем выполняется обработчик истечения срока действия, и дальнейшие обновления местоположения не генерируются. Как изменить приведенный выше код, чтобы получать периодические обновления местоположения в фоновом режиме на неопределенный срок ?

Обновление : я нацелен на iOS 7, и, похоже, есть некоторые свидетельства того, что фоновые задачи ведут себя по-другому:

Запустите диспетчер расположения в iOS 7 из фоновой задачи

pcoving
источник
Вы не указываете, на какую версию iOS вы нацеливаетесь. iOS 7 (например) имеет другую систему многозадачности, чем ее предшественник.
Джейсон Уайтхорн,
@JasonWhitehorn Хороший вопрос. Я обновил вопрос.
pcoving
2
@pcoving: Будет ли ваше решение работать, даже если приложение будет убито или прекращено?
Nirav

Ответы:

62

Похоже, stopUpdatingLocationэто то, что запускает фоновый сторожевой таймер, поэтому я заменил его на didUpdateLocation:

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

который, кажется, эффективно отключает GPS. Селектор фона NSTimerстановится таким:

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

Все, что я делаю, это периодически переключаю точность, чтобы получать координаты высокой точности каждые несколько минут, и, поскольку locationManagerона не была остановлена, backgroundTimeRemainingостается на максимальном значении. Это снизило потребление заряда батареи с ~ 10% в час (с постоянным kCLLocationAccuracyBestв фоновом режиме) до ~ 2% в час на моем устройстве.

pcoving
источник
4
Не могли бы вы обновить свой вопрос полным рабочим примером? Что-то вроде: «ОБНОВЛЕНИЕ: я понял это. Вот мое решение ...»
smholloway
@Virussmca Я вернулся к предыдущему ответу, который намного надежнее с аналогичной производительностью. Полная остановка обновления местоположения, похоже, вызывает состояние гонки (или какой-либо другой недетерминированный сценарий).
pcoving
Этот подход не будет работать так, как вы ожидаете: 1) Увеличение значения свойства distanceFilter не приводит к экономии заряда батареи, потому что GPS по-прежнему должен определять ваше местоположение. 2) В iOS 7 фоновое время не непрерывно. 3) Вы можете принять во внимание сервисы importantChangeLocation. 4) Помимо вашей работы в iOS 7, вы не можете запустить UIBackgroundModes - определение местоположения из фона ...
aMother
2
@aMother 1) Я понимаю, что увеличение фильтра расстояния (а не обработка обратных вызовов) не сильно экономит батарею. Однако setDesiredAccuracy делает! Он использует информацию о вышках сотовой связи, которая в основном предоставляется бесплатно. 2) Несмежные? 3) Я выделил жирным шрифтом, что мне нужна высокая точность. Существенные изменения местоположения этого не обеспечивают. 4) Службы определения местоположения не запускаются / не останавливаются в фоновом режиме.
pcoving
2
Я знаю, что это старый пост, но пользователи моего приложения, которые используют этот трюк, заметили значительное увеличение использования батареи после обновления до iOS9. Я еще не исследовал, но у меня такое чувство, что этот «трюк» может больше не работать?
Гэри Райт,
45

Я написал приложение, использующее службы определения местоположения, приложение должно отправлять местоположение каждые 10 секунд. И это сработало очень хорошо.

Просто используйте метод allowDeferredLocationUpdatesUntilTraveled: timeout , следуя документу Apple.

Шаги следующие:

Обязательно: Зарегистрируйте фоновый режим для обновления. Местоположение.

1. Создайте LocationManger и startUpdatingLocation с точностью и отфильтрованным расстоянием, как хотите:

-(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. Чтобы приложение постоянно работало с использованием метода allowDeferredLocationUpdatesUntilTraveled: timeout в фоновом режиме, необходимо перезапустить updateLocation с новым параметром, когда приложение переходит в фоновый режим, например:

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

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

3. Приложение получает updateLocations как обычно с помощью обратного вызова "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. Но вы должны обрабатывать данные в обратном вызове "locationManager: didFinishDeferredUpdatesWithError:" для ваших целей.

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

     _deferringUpdates = NO;

     //do something 
}

5. ПРИМЕЧАНИЕ. Я думаю, что мы должны сбрасывать параметры LocationManager каждый раз, когда приложение переключается между фоновым / внешним режимом.

samthui7
источник
Это отлично работает, и я считаю, что это правильный способ сделать, как упоминалось на прошлогодней конференции WWDC !! Спасибо за пост
prasad1250
@ samthui7 спасибо, что это работает, но мне также нужно получить местоположение после завершения. как это может быть.??
Mohit Tomar
@ samthui7 .. пожалуйста, посмотрите эту очередь ... stackoverflow.com/questions/37338466/…
Сурадж Сукале,
2
@amar: Я тоже проверял бета-версию iOS 10, она все еще работает. Обратите внимание, что нам нужно добавить код перед startUpdatingLocation: allowsBackgroundLocationUpdates = YESи requestAlwaysAuthorizationили requestWhenInUseAuthorization. Например: `CLLocationManager * locationManager = [[CLLocationManager alloc] init]; locationManager.delegate = self; locationManager.allowsBackgroundLocationUpdates = ДА; [locationManager requestAlwaysAuthorization]; [locationManager startUpdatingLocation]; `
samthui7
1
@Nirav Нет. Я не думаю, что Apple могла позволить нам это сделать, насколько мне известно.
samthui7
38
  1. Если у вас есть список UIBackgroundModesс locationключом, вам не нужно использовать beginBackgroundTaskWithExpirationHandlerметод. Это избыточно. Также вы используете его неправильно (см. Здесь ), но это спорный вопрос, поскольку ваш файл plist установлен.

  2. Если UIBackgroundModes locationв списке есть список, приложение будет продолжать работать в фоновом режиме бесконечно долго, пока CLLocationMangerоно работает. Если вы позвоните stopUpdatingLocationв фоновом режиме, приложение остановится и больше не запустится.

    Может быть, вы могли бы позвонить beginBackgroundTaskWithExpirationHandlerнепосредственно перед звонком, stopUpdatingLocationа затем после звонка startUpdatingLocationвы могли бы позвонить, endBackgroundTaskчтобы он оставался на связи, пока GPS отключен, но я никогда не пробовал этого - это просто идея.

    Другой вариант (который я не пробовал) - оставить диспетчер местоположения работающим в фоновом режиме, но как только вы получите точное местоположение, измените desiredAccuracyсвойство на 1000 м или выше, чтобы позволить чипу GPS отключиться (для экономии заряда батареи). Затем через 10 минут, когда вам понадобится еще одно обновление местоположения, измените значение desiredAccuracyназад на 100 м, чтобы включить GPS, пока не получите точное местоположение, повторите.

  3. Когда вы звоните startUpdatingLocationменеджеру по местоположению, вы должны дать ему время, чтобы занять позицию. Не стоит сразу звонить stopUpdatingLocation. Мы даем ему поработать максимум 10 секунд или пока не получим некэшированное высокоточное местоположение.

  4. Вам необходимо отфильтровать кэшированные местоположения и проверить точность полученного местоположения, чтобы убедиться, что оно соответствует минимально необходимой точности (см. Здесь ). Первое обновление, которое вы получите, может быть давным через 10 минут или 10 дней. Первая точность, которую вы получите, может быть 3000 м.

  5. Вместо этого рассмотрите возможность использования API значительного изменения местоположения. Как только вы получите уведомление о значительном изменении, вы можете начать CLLocationManagerв течение нескольких секунд, чтобы получить положение с высокой точностью. Я не уверен, я никогда не пользовался услугами значительного изменения местоположения.

прогрмр
источник
CoreLocationСсылка Apple, кажется, рекомендует использовать beginBackgroundTaskWithExpirationHandlerпри обработке данных о местоположении в фоновом режиме: «Если приложению iOS требуется больше времени для обработки данных о местоположении, оно может запросить дополнительное время выполнения в фоновом режиме с помощью метода beginBackgroundTaskWithName: expirationHandler: класса UIApplication». - developer.apple.com/library/ios/documentation/UserExperience/…
eliocs
4
не забудьте добавить _locationManager.allowsBackgroundLocationUpdates = YES; для iOS9 +, иначе приложение перейдет в спящий режим через 180 секунд
kolyancz 02
@kolyancz Я только что попробовал на iOS 10.1.1 для iPhone 6+. Чтобы он заснул и сработал, потребовалось примерно 17 минут locationManagerDidPauseLocationUpdates. Я проверял это поведение 10 раз!
Дорогая,
Я действительно не понимаю, почему вы говорите, что это спорный вопрос, а затем говорите, что вам следует обернуть это в фоновую
Дорогая,
10

Когда пришло время запустить службу определения местоположения и остановить фоновую задачу, фоновая задача должна быть остановлена ​​с задержкой (достаточно 1 секунды). В противном случае служба определения местоположения не запустится. Также службу определения местоположения следует оставить включенной на пару секунд (например, 3 секунды).

Существует cocoapod APScheduledLocationManager, который позволяет получать фоновые обновления местоположения каждые n секунд с желаемой точностью определения местоположения.

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

Репозиторий также содержит пример приложения, написанного на Swift 3.

пояс
источник
1

Как насчет того, чтобы попробовать с startMonitoringSignificantLocationChanges:API? Он определенно стреляет с меньшей частотой и достаточно хорошей точностью. Кроме того, у него намного больше преимуществ, чем у других locationManagerAPI.

Подробнее об этом API уже говорилось по этой ссылке.

Thatzprem
источник
1
Я пробовал такой подход. Это нарушает требования к точности - из документов : This interface delivers new events only when it detects changes to the device’s associated cell towers
pcoving
1

Вам нужно добавить режим обновления местоположения в свое приложение, добавив следующий ключ в свой info.plist.

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

didUpdateToLocationбудет вызван метод (даже если ваше приложение находится в фоновом режиме). Вы можете делать что угодно на основе вызова этого метода

аахсанали
источник
Режим обновления местоположения уже есть в info.plist. Как я уже упоминал, я получаю обновления, пока не истечет время ожидания фона в 180 секунд.
pcoving
0

После iOS 8 произошли некоторые изменения в фоновой выборке и обновлениях CoreLocation framework, сделанные Apple.

1) Apple представила запрос AlwaysAuthorization для получения обновления местоположения в приложении.

2) Apple представляет backgroundLocationupdates для получения местоположения из фона в iOS.

Для получения местоположения в фоновом режиме вам необходимо включить обновление местоположения в возможностях 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

Чтобы использовать стандартные службы определения местоположения, когда приложение работает в фоновом режиме, вам необходимо сначала включить фоновые режимы на вкладке «Возможности» в настройках цели и выбрать «Обновления местоположения».

Или добавьте его прямо в Info.plist .

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

Затем вам нужно настроить CLLocationManager

Цель 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];

Swift

self.locationManager.delegate = self
if #available (iOS 8.0,*) {
    self.locationManager.requestAlwaysAuthorization()
}
if #available (iOS 9.0,*) {
    self.locationManager.allowsBackgroundLocationUpdates = true
}
self.locationManager.startUpdatingLocation()

Ссылка: https://medium.com/@javedmultani16/location-service-in-the-background-ios-942c89cd99ba

Г-н Джавед Мултани
источник
-2
locationManager.allowsBackgroundLocationUpdates = true
Папон Smc
источник
4
Не могли бы вы отредактировать свой ответ и добавить объяснение, почему / как это решает проблему?
barbsan