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

207

Я ищу способ получать фоновое обновление местоположения каждые n минут в моем приложении iOS. Я использую iOS 4.3, и решение должно работать на не взломанных iPhone.

Я пробовал / рассматривал следующие варианты:

  • CLLocationManager startUpdatingLocation/startMonitoringSignificantLocationChanges: Это работает в фоновом режиме, как и ожидалось, на основе настроенных свойств, но кажется невозможным принудительно обновлять местоположение каждые n минут
  • NSTimer: Работает, когда приложение работает на переднем плане, но, похоже, не предназначено для фоновых задач
  • Локальные уведомления. Локальные уведомления можно планировать каждые n минут, но невозможно выполнить какой-либо код для получения текущего местоположения (без необходимости запуска приложения через уведомление). Этот подход также не кажется чистым подходом, поскольку это не то, для чего должны использоваться уведомления.
  • UIApplication:beginBackgroundTaskWithExpirationHandlerНасколько я понимаю, это следует использовать для завершения некоторой работы в фоновом режиме (также ограниченной по времени), когда приложение перемещается в фоновый режим, а не для реализации «длительных» фоновых процессов.

Как я могу реализовать эти регулярные фоновые обновления местоположения?

wjans
источник
полезное продолжение: stackoverflow.com/questions/10235203/…
Lolo
1
Если вы пытаетесь заставить его работать на iOS 7, вы можете попробовать это решение здесь: stackoverflow.com/questions/18946881/… Если у вас есть какие-либо вопросы, вы можете присоединиться к нам для обсуждения здесь: mobileoop.com/background -location-update -рограммирования-для-IOS-7
Рики
1
Все ваши выводы верны (четыре пункта). Ценная информация, то есть, зная, что не соответствует вашему варианту использования? И да, когда в режиме SUSPENDED MODE или NOT RUNNING не существует окончательного метода обновления каждые n минут.
LenArt

Ответы:

113

Я нашел решение для реализации этого с помощью форумов разработчиков Apple:

  • Уточнить location background mode
  • Создайте NSTimerв фоновом режиме сUIApplication:beginBackgroundTaskWithExpirationHandler:
  • Когда nэто меньше , чем UIApplication:backgroundTimeRemainingон будет работать нормально. Когда nоно больше , его location managerследует снова включить (и отключить), прежде чем не останется времени, чтобы избежать уничтожения фоновой задачи.

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

Примечание: я потерял некоторое время, протестировав это в симуляторе, где он не работает. Тем не менее, он отлично работает на моем телефоне.

wjans
источник
24
у вас есть ссылка на форум? Я пытаюсь реализовать тот же тип отображения местоположения и не смог заставить его работать. Или пример кода будет принята с благодарностью.
Ютвитхак
4
Не могли бы вы объяснить, почему фоновая задача не прекращается через 10 минут (максимально допустимое время), если вы просто остановитесь и запустите диспетчер местоположений? это какая-то предполагаемая функциональность? это больше похоже на ошибку в Apple SDK, если так происходит. На какой версии iOS вы это пробовали?
Саураб
5
@all: Да, наше приложение доступно в AppStore. Я не собираюсь публиковать весь код, все упомянутые маркеры являются четко задокументированными функциями. Если у вас возникла конкретная проблема, опубликуйте свой вопрос, объясняя, что вы пробовали и что пошло не так.
wjans
3
@ user836026: Да, именно это я имею в виду, задавая фоновый режим. После остановки обновления местоположения его следует запустить снова в течение 10 минут, чтобы избежать завершения работы приложения.
wjans
12
Для всех тех, кто заинтересован в том, чтобы увидеть какой-то реальный код, отражающий то, что здесь обсуждается, проверьте stackoverflow.com/questions/10235203/…
Lolo
55

На iOS 8/9/10 для обновления фонового местоположения каждые 5 минут сделайте следующее:

  1. Зайдите в Проект -> Возможности -> Фоновые режимы -> выберите Обновления местоположения

  2. Перейдите в Project -> Info -> добавьте ключ NSLocationAlwaysUsageDescription с пустым значением (или, необязательно, любым текстом)

  3. Чтобы ваше местоположение работало, когда ваше приложение находится в фоновом режиме, и отправляйте координаты в веб-службу или делайте что-нибудь с ними каждые 5 минут, реализуйте это, как показано в коде ниже.

Я не использую фоновые задачи или таймеры. Я тестировал этот код на своем устройстве с iOS 8.1, которое несколько часов лежало на моем столе, а приложение работало в фоновом режиме. Устройство было заблокировано, и код работал правильно все время.

@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
Это работает в течение более нескольких часов? Похоже, что даже если приложение не завершает работу, устройство перестает загружать обновления местоположения примерно через час после того, как приложение переходит в фоновый режим.
Myxtic
2
Фоновая выборка отключена в моем проекте (не важно, выключена она или включена). Однако для правильной работы pausesLocationUpdatesAutomatics должно быть установлено значение NO. Он НЕ возобновится автоматически, как только вы снова начнете движение, если система была приостановлена ​​ранее. Вот почему в приведенном выше примере я установил это свойство на NO.
Лешек Сзары
1
Я думаю, что вы абсолютно правы. Более подробную информацию об этой проблеме вы
найдете
2
@LeszekS вам нужно добавить код ниже для поддержки фона iOS 9 - if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) { [self.locationManager setAllowsBackgroundLocationUpdates:YES]; }
Fenil
2
В чем удобство этого? Вы все еще разряжаете батарею, имея постоянную высокую точность. Единственное, что вы не делаете, вы фактически не используете уже полученное местоположение, пока не достигнете интервала в 5 минут ...
Дорогой,
34

Я сделал это в приложении, которое я разрабатываю. Таймеры не работают, когда приложение находится в фоновом режиме, но приложение постоянно получает обновления местоположения. Я где-то прочитал в документации (я не могу найти ее сейчас, я опубликую обновление, когда я это сделаю), что метод может вызываться только в активном цикле выполнения, когда приложение находится в фоновом режиме. У делегата приложения есть активный цикл выполнения даже в bg, поэтому вам не нужно создавать свой собственный, чтобы это работало. [Я не уверен, что это правильное объяснение, но вот как я понял из того, что я прочитал]

Прежде всего, добавьте locationобъект для ключа UIBackgroundModesв info.plist вашего приложения. Теперь вам нужно запустить обновление местоположения в любом месте вашего приложения:

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

Затем напишите метод обработки обновлений местоположения, скажем -(void)didUpdateToLocation:(CLLocation*)location, в делегате приложения. Затем реализовать метод locationManager:didUpdateLocation:fromLocationв CLLocationManagerDelegateв классе , в котором вы начали менеджер местоположения (поскольку мы установили местонахождение менеджер делегат «я»). Внутри этого метода вам нужно проверить, истек ли временной интервал, после которого вам придется обрабатывать обновления местоположения. Вы можете сделать это, сохраняя текущее время каждый раз. Если это время истекло, вызовите метод UpdateLocation из вашего делегата приложения:

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

Это будет вызывать ваш метод каждые 5 минут, даже если ваше приложение находится в фоновом режиме. Imp: эта реализация разряжает батарею, если точность данных о вашем местоположении не критична, вы должны использовать[locationManager startMonitoringSignificantLocationChanges]

Перед добавлением этого в ваше приложение, пожалуйста, прочитайте Руководство по программированию для определения местоположения

Бушра Шахид
источник
3
Таким образом, службы определения местоположения постоянно включены (действительно разряжают батарею), я не хочу этого. Я хочу включить службу определения местоположения каждые n минут и сразу же отключить ее, когда исправлюсь (просто заметил, что я не объяснил это четко в своем вопросе). Я могу добиться такого поведения в решении, которое я описал.
wjans
3
Вы можете установить точность менеджера местоположения на 1 км - это оставит вашу батарею почти нетронутой. Через 5 минут вы устанавливаете точность до 1 метра. Когда вы получите подходящее местоположение (обычно через 5 с), просто установите точность обратно на 1 км.
knagode
knagode, я попробовал ваше предлагаемое решение для проблемы разрядки батареи, но даже после повышения точности через N минут метод locationManager: didUpdateLocations больше не вызывается. Я попытался startUpdating и stopUpdating, вместо увеличения и уменьшения точности, он называется успешно делегировать locationManager: метод didUpdateLocations, через N минут, но не работает в фоновом режиме ...
Hardik Darji
Что касается ссылок документации. Смотрите здесь : «Методы вашего объекта-делегата вызываются из потока, в котором вы запустили соответствующие службы определения местоположения. Этот поток должен сам иметь активный цикл выполнения, подобный тому, который находится в основном потоке вашего приложения».
Мед
24

Теперь, когда iOS6 - лучший способ иметь постоянно работающие службы определения местоположения, это ...

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

}

Просто проверил это так:

Я запустил приложение, поехал на задний план и поехал на машине на несколько минут. Затем я иду домой на 1 час и снова начинаю двигаться (не открывая снова приложение). Места начались снова. Затем остановился на два часа и начал снова. Все снова хорошо ...

НЕ ЗАБУДЬТЕ ИСПОЛЬЗОВАТЬ новые сервисы определения местоположения в 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
Если приложение падает или убивается, система не перезагружается, верно?
Кен
1
Для более читабельного кода вы можете сделать [местоположения lastObject]; вместо [местоположения objectAtIndex: [количество мест] - 1]
Axello
твой метод только на ios6?
Pengwang
Мы должны использовать это? Я думал, что просто иметь код менеджера местоположения в viewDidLoad класса, и установить фоновый ключ в файле plist, который приложение регистрирует для обновлений местоположения должно быть достаточно хорошо? Можете ли вы помочь мне с этим!
nithinreddy
Да, он будет работать как charm nithinreddy, но перестанет работать через 10 минут, так как после этого периода iOS убивает длинные потоки. Мое решение идеально, если вы хотите, чтобы эти услуги были запущены навсегда. Я провел несколько тестов два дня назад, и каждый час батарея разряжается на 6%. Использование [locationManager startUpdatingLocation] вместо этого опустошит 17%
Алехандро Луенго
13

Для кого-то еще с кошмаром выяснить это. У меня есть простое решение.

  1. посмотрите этот пример от raywenderlich.com -> есть пример кода, он работает отлично, но, к сожалению, нет таймера в фоновом режиме. это будет работать бесконечно.
  2. Добавить таймер с помощью:

    -(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. Только не забудьте добавить «Регистры приложений для обновлений местоположения» в info.plist.

HelmiB
источник
Получается ли это местоположение более 3 минут?
SleepNot
Необходимо установить местоположение в Capabilities -> Background Mode.
Серхио Андреотти
Это не работает!! Я проверил на iOS 8 и iOS 10
Kirti
Поправьте меня если я ошибаюсь. Исходя из того, что я прочитал, вы запускаете locationManager только один раз. После этого все интервалы являются избыточными. Так как это уже было начато
Honey
он работает, но останавливается через 170 секунд. я хочу выполнять свою задачу в фоновом режиме в течение неограниченного времени
anshuman burmman
8

Вот что я использую:

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

Я запускаю отслеживание в AppDelegate следующим образом:

BackgroundLocationManager.instance.start()
hmitkov
источник
Спасибо. Наш проект должен отслеживать местоположение пользователя + отправлять события PubNub в фоновом режиме, и ваше решение работает очень хорошо.
user921509
Привет Хмитков, где я могу вызвать метод sendLocationToServer для отправки местоположения пользователя на сервер
AmanGupta007
@ AmanGupta007 Вы можете вызвать sendLocationToServer в func locationManager (manager: didUpdateLocations :). Обратите внимание на комментарий // TODO в коде.
hmitkov
@hmitkov Можно ли запускать и останавливать Location Services с этим, пока приложение находится в фоновом режиме? Например, запустите службу определения местоположения из push-уведомления, возьмите некоторое значение широты / долготы, отправьте в веб-службу, затем прекратите обновление местоположения. Делайте это каждый раз, когда «content-available» = 1 включается в тело push-сообщения.
DookieMan
1
Я пробовал этот код, но кажется, что он не работает на iOS11? (Я не проверял это ни на каких других версиях.)
Том
6

К сожалению, все ваши предположения кажутся правильными, и я не думаю, что есть способ сделать это. В целях экономии заряда аккумулятора службы определения местоположения iPhone основаны на движении. Если телефон находится в одном месте, он невидим для служб определения местоположения.

CLLocationManagerБудет вызывать толькоlocationManager:didUpdateToLocation:fromLocation: тогда, когда телефон получит обновление местоположения, что происходит только в том случае, если одна из трех служб определения местоположения (сотовая вышка, GPS, Wi-Fi) воспринимает изменение.

Несколько других вещей, которые могли бы помочь сообщить дальнейшие решения:

  • Запуск и остановка служб вызывает didUpdateToLocationметод делегата, но newLocationможет иметь старую временную метку.

  • Мониторинг региона может помочь

  • При работе в фоновом режиме помните, что может быть трудно получить «полную» поддержку LocationServices, одобренную Apple. Из того, что я видел, они специально разработаны startMonitoringSignificantLocationChangesкак альтернатива с низким энергопотреблением для приложений, которым требуется фоновая поддержка местоположения, и настоятельно рекомендуют разработчикам использовать это, если приложение не нуждается в этом.

Удачи!

ОБНОВЛЕНИЕ: Эти мысли могут быть устаревшими к настоящему времени. Похоже, что люди с успехом с ответом @wjans, выше.

Chazbot
источник
1
В AppStore есть приложения (например, «Мой локус»), которые позволяют получать обновления местоположения в фоновом режиме. Они не поддерживают активную службу определения местоположения, а просто включаются в соответствии с заданным интервалом. Как они это делают?
wjans
2
В описываемой вами ситуации приложение, скорее всего, использует подход startMonitoringSignificantLocationChanges. В этом случае телефон временно «просыпается» при получении обновления местоположения, но интервал, который можно установить для «пингования» этой службы в фоновом режиме, отсутствует. Когда телефон перемещается (или переключается с сотового на GPS или на Wi-Fi), он запускает обновление. Лекция на эту тему в Stanford iTunes U была для меня очень полезной - надеюсь, она поможет вам найти обходной путь: itunes.apple.com/us/itunes-u/iphone-application-development/…
Chazbot
2
Спасибо за указатель. Однако я все еще не понимаю, что делает приложение. Даже если мой телефон у меня на столе, он вообще не двигается, я вижу, что служба определения местоположения срабатывает каждые 10 минут (точно). Если я правильно понимаю, startMonitoringSignificantLocationChangesне даст никаких обновлений в этом случае.
wjans
1
@wjans как у вас расходуется заряд батареи телефона, вы заметили, что он быстро разряжается из-за приложения Mylocus?
user836026
6

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

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

Что я сделал:

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

1. Создайте LocationMangerи startUpdatingLocation, с accuracyи filteredDistanceкак угодно:

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

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

samthui7
источник
@ Какое решение, описанное выше или по объяснению wjans, лучше использовать для экономии заряда батареи устройства, или оба будут влиять на одно и то же?
Яш
2
Я попробовал оба решения, о которых вы упомянули, и увидел, что у @ wjans немного больше экономии батареи. Но после выхода iOS 8 кажется, что это решение больше не работает должным образом. Для более подробной информации: в большинстве случаев приложение не может долго жить в фоновом режиме.
samthui7
@ samthui7 Почему вы устанавливаете pausesLocationUpdatesAutomatics = false?
CedricSoubrie
Требование моего приложения состоит в том, чтобы отправлять userLocation каждые 10 секунд, в то время как pausesLocationUpdatesAutomatically = trueдиспетчер местоположений должен приостанавливать обновления, если, по-видимому, нет изменения местоположения (документ Apple ). Во всяком случае, я еще явно не тестировал случай location manager pauses updates: D.
samthui7
@CedricSoubrie: Установка pausesLocationUpdatesAutomatically = trueзаставляет мое приложение перестать обновлять местоположение.
samthui7
5
if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) {
    [self.locationManager setAllowsBackgroundLocationUpdates:YES];
}

Это необходимо для фонового отслеживания местоположения начиная с iOS 9.

Nilesh
источник
1
Это спасло мой день! используя цель развертывания iOS8, с устройством iOS9
Яро
4

Я использовал метод xs2bush для получения интервала (используя timeIntervalSinceDate ) в немного расширил его. Я хотел убедиться, что я получаю требуемую точность, которая мне нужна, а также что я не разряжаю батарею, поддерживая радио GPS более чем необходимо.

Я постоянно работаю со следующими настройками:

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

это относительно низкий расход батареи. Когда я готов получить мое следующее периодическое чтение местоположения, я сначала проверяю, находится ли местоположение в пределах моей желаемой точности, если это так, я затем использую местоположение. Если это не так, я увеличу точность с помощью этого:

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

получить мое местоположение, а затем, когда у меня есть местоположение, я снова понижаю точность, чтобы минимизировать разрядку батареи. Я написал полный рабочий пример этого, а также я написал исходный код для серверной стороны, чтобы собрать данные о местоположении, сохранить их в базе данных и позволить пользователям просматривать данные GPS в режиме реального времени или получать и просматривать ранее сохраненные маршруты. У меня есть клиенты для iOS, Android, Windows Phone и Java Me. Все клиенты изначально написаны, и все они работают правильно в фоновом режиме. Проект имеет лицензию MIT.

Проект iOS предназначен для iOS 6 с использованием базового SDK iOS 7. Вы можете получить код здесь .

Пожалуйста, отправьте вопрос на github, если вы видите какие-либо проблемы с ним. Спасибо.

nickfox
источник
Я попробовал ваше решение, но оно не работает ... когда приложение переходит в фоновый режим, даже после повышения точности приложение в моем случае не вызывает метод
didUpdateToLocation
@HardikDarji важный вопрос. Ты двигаешься? Если нет, обновление местоположения может прекратиться. Попробуйте взять телефон с собой на прогулку или поездку и посмотрите, исправит ли это.
Никфокс
Спасибо за ваш быстрый ответ. Но я хочу, чтобы обновления местоположения происходили каждые 2 минуты, независимо от того, движется мой телефон или нет. В этом случае метод didUpdateToLocation не вызывается. Я ищу здесь: Как я могу получить обновление местоположения каждые n минут !!!
Хардик Дарджи,
попробуйте установить значение timeIntervalInSeconds равным 120 и раскомментируйте эту строку: locationManager.pausesLocationUpdatesAutomatics = NO;
Никфокс
1
Решение, которое вы разместили в приведенном выше комментарии, сокращает срок службы батареи, или вы все еще проводите оптимизацию для этого?
Самфр
2

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

Амит Шелгаонкар
источник
2

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

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

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

кушак
источник
у вас есть что-нибудь подобное в цели c?
Моксарт
1

В iOS 9 и watchOS 2.0 в CLLocationManager есть новый метод, который позволяет запрашивать текущее местоположение: CLLocationManager: requestLocation (). Это завершается немедленно, а затем возвращает местоположение делегату CLLocationManager.

Вы можете использовать NSTimer для запроса местоположения каждую минуту с помощью этого метода сейчас, и вам не нужно работать с методами startUpdatingLocation и stopUpdatingLocation.

Однако, если вы хотите захватывать местоположения на основе изменения X метров от последнего местоположения, просто установите для свойства distanceFilter CLLocationManger и значение X для startUpdatingLocation ().

Малькольм
источник
-1

Прилагается решение Swift, основанное на:

Определить App registers for location updatesв info.plist

Постоянно работайте locationManager

Переключение kCLLocationAccuracyмежду BestForNavigation(на 5 секунд для определения местоположения) и ThreeKilometersна оставшуюся часть периода ожидания, чтобы избежать разряда батареи

Этот пример обновляет местоположение каждые 1 минуту на переднем плане и каждые 15 минут на заднем плане.

Пример отлично работает с Xcode 6 Beta 6, работающей на устройстве iOS 7.

В делегате приложения (mapView является необязательным указателем на контроллер 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
}

В mapView (locationManager указывает на объект в 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
источник