Периодические обновления фона iOS

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

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

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

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

Длительный фоновый таймер iOS с фоновым режимом "location"

Постоянный фоновый сервис 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, и есть некоторые доказательства того, что фоновые задачи ведут себя по-разному:

Запустите Location Manager в iOS 7 из фоновой задачи

8 ответов

Решение

Кажется, что 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% в час на моем устройстве.

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

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

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

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

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. Приложение получает обновленные местоположения как обычно с обратным вызовом "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 каждый раз, когда приложение переключается между фоновым / фоновым режимами.

  1. Если у вас есть UIBackgroundModes в вашем списке location ключ, то вам не нужно использовать beginBackgroundTaskWithExpirationHandler метод. Это избыточно. Кроме того, вы используете его неправильно (см здесь), но это спорный вопрос, так как ваш PLIST установлен.

  2. С UIBackgroundModes location в plist приложение будет работать в фоновом режиме бесконечно только до тех пор, пока 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 в течение нескольких секунд, чтобы получить высокую точность позиции. Я не уверен, я никогда не пользовался услугами по значительному изменению местоположения.

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

Есть кокосовый модуль https://github.com/paleksandrs/APScheduledLocationManager, который позволяет получать фоновые обновления местоположения каждые n секунд с желаемой точностью определения местоположения.

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

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

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

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

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

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

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

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

Или добавьте его прямо в 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

После iOS 8 произошли некоторые изменения в фоновой загрузке CoreLocation, связанные с извлечением фона и обновлениями, сделанными 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
    }

}

Этот проект будет работать для управления местоположением в фоновом режиме.

ОБНОВЛЕНИЕ ФОНА РАСПОЛОЖЕНИЯ

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

locationManager.allowsBackgroundLocationUpdates = true
Другие вопросы по тегам