Запустите Location Manager в iOS 7 из фоновой задачи
Похоже, что в iOS 7 приложение больше не может запускать Location Manager (вызывая startUpdatingLocation) из фоновой задачи.
В iOS 6 я использовал описанный здесь подход: /questions/21934034/kak-mne-poluchat-fonovoe-obnovlenie-mestopolozheniya-kazhdyie-n-minut-v-moem-prilozhenii-ios/21934049#21934049 для запуска фонового обновления местоположения каждые n минут. Идея заключалась в том, чтобы запустить фоновое задание с таймером и запустить диспетчер местоположения, когда таймер запускает его. После этого выключите Location Manager и запустите другую фоновую задачу.
После обновления до iOS 7 этот подход больше не работает. После запуска Location Manager приложение не получает никакого locationManager:didUpdateLocations. Есть идеи?
3 ответа
Я нашел проблему / решение. Когда пришло время запустить службу определения местоположения и остановить фоновую задачу, фоновую задачу следует остановить с задержкой (я использовал 1 секунду). В противном случае служба определения местоположения не запустится. Также Location Service должен оставаться включенным на пару секунд (в моем примере это 3 секунды).
Еще одно важное замечание: максимальное время фона в iOS 7 теперь составляет 3 минуты вместо 10 минут.
Обновлено 29 октября '16
Есть кокосовый модуль https://github.com/paleksandrs/APScheduledLocationManager, который позволяет получать фоновые обновления местоположения каждые n секунд с желаемой точностью определения местоположения.
let manager = APScheduledLocationManager(delegate: self)
manager.startUpdatingLocation(interval: 170, acceptableLocationAccuracy: 100)
Хранилище также содержит пример приложения, написанного на Swift 3.
Обновлено 27 мая '14
Пример Objective-C:
1) В наборе файлов ".plist" UIBackgroundModes
в "местоположение".
2) Создать экземпляр ScheduledLocationManager
где угодно.
@property (strong, nonatomic) ScheduledLocationManager *slm;
3) Настройте это
self.slm = [[ScheduledLocationManager alloc]init];
self.slm.delegate = self;
[self.slm getUserLocationWithInterval:60]; // replace this value with what you want, but it can not be higher than kMaxBGTime
4) Реализуйте методы делегата
-(void)scheduledLocationManageDidFailWithError:(NSError *)error
{
NSLog(@"Error %@",error);
}
-(void)scheduledLocationManageDidUpdateLocations:(NSArray *)locations
{
// You will receive location updates every 60 seconds (value what you set with getUserLocationWithInterval)
// and you will continue to receive location updates for 3 seconds (value of kTimeToGetLocations).
// You can gather and pick most accurate location
NSLog(@"Locations %@",locations);
}
Вот реализация ScheduledLocationManager:
ScheduledLocationManager.h
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
@protocol ScheduledLocationManagerDelegate <NSObject>
-(void)scheduledLocationManageDidFailWithError:(NSError*)error;
-(void)scheduledLocationManageDidUpdateLocations:(NSArray*)locations;
@end
@interface ScheduledLocationManager : NSObject <CLLocationManagerDelegate>
-(void)getUserLocationWithInterval:(int)interval;
@end
ScheduledLocationManager.m
#import "ScheduledLocationManager.h"
int const kMaxBGTime = 170; // 3 min - 10 seconds (as bg task is killed faster)
int const kTimeToGetLocations = 3; // time to wait for locations
@implementation ScheduledLocationManager
{
UIBackgroundTaskIdentifier bgTask;
CLLocationManager *locationManager;
NSTimer *checkLocationTimer;
int checkLocationInterval;
NSTimer *waitForLocationUpdatesTimer;
}
- (id)init
{
self = [super init];
if (self) {
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.distanceFilter = kCLDistanceFilterNone;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
}
return self;
}
-(void)getUserLocationWithInterval:(int)interval
{
checkLocationInterval = (interval > kMaxBGTime)? kMaxBGTime : interval;
[locationManager startUpdatingLocation];
}
- (void)timerEvent:(NSTimer*)theTimer
{
[self stopCheckLocationTimer];
[locationManager startUpdatingLocation];
// in iOS 7 we need to stop background task with delay, otherwise location service won't start
[self performSelector:@selector(stopBackgroundTask) withObject:nil afterDelay:1];
}
-(void)startCheckLocationTimer
{
[self stopCheckLocationTimer];
checkLocationTimer = [NSTimer scheduledTimerWithTimeInterval:checkLocationInterval target:self selector:@selector(timerEvent:) userInfo:NULL repeats:NO];
}
-(void)stopCheckLocationTimer
{
if(checkLocationTimer){
[checkLocationTimer invalidate];
checkLocationTimer=nil;
}
}
-(void)startBackgroundTask
{
[self stopBackgroundTask];
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
//in case bg task is killed faster than expected, try to start Location Service
[self timerEvent:checkLocationTimer];
}];
}
-(void)stopBackgroundTask
{
if(bgTask!=UIBackgroundTaskInvalid){
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
}
-(void)stopWaitForLocationUpdatesTimer
{
if(waitForLocationUpdatesTimer){
[waitForLocationUpdatesTimer invalidate];
waitForLocationUpdatesTimer =nil;
}
}
-(void)startWaitForLocationUpdatesTimer
{
[self stopWaitForLocationUpdatesTimer];
waitForLocationUpdatesTimer = [NSTimer scheduledTimerWithTimeInterval:kTimeToGetLocations target:self selector:@selector(waitForLoactions:) userInfo:NULL repeats:NO];
}
- (void)waitForLoactions:(NSTimer*)theTimer
{
[self stopWaitForLocationUpdatesTimer];
if(([[UIApplication sharedApplication ]applicationState]==UIApplicationStateBackground ||
[[UIApplication sharedApplication ]applicationState]==UIApplicationStateInactive) &&
bgTask==UIBackgroundTaskInvalid){
[self startBackgroundTask];
}
[self startCheckLocationTimer];
[locationManager stopUpdatingLocation];
}
#pragma mark - CLLocationManagerDelegate methods
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
if(checkLocationTimer){
//sometimes it happens that location manager does not stop even after stopUpdationLocations
return;
}
if (self.delegate && [self.delegate respondsToSelector:@selector(scheduledLocationManageDidUpdateLocations:)]) {
[self.delegate scheduledLocationManageDidUpdateLocations:locations];
}
if(waitForLocationUpdatesTimer==nil){
[self startWaitForLocationUpdatesTimer];
}
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
if (self.delegate && [self.delegate respondsToSelector:@selector(scheduledLocationManageDidFailWithError:)]) {
[self.delegate scheduledLocationManageDidFailWithError:error];
}
}
#pragma mark - UIAplicatin notifications
- (void)applicationDidEnterBackground:(NSNotification *) notification
{
if([self isLocationServiceAvailable]==YES){
[self startBackgroundTask];
}
}
- (void)applicationDidBecomeActive:(NSNotification *) notification
{
[self stopBackgroundTask];
if([self isLocationServiceAvailable]==NO){
NSError *error = [NSError errorWithDomain:@"your.domain" code:1 userInfo:[NSDictionary dictionaryWithObject:@"Authorization status denied" forKey:NSLocalizedDescriptionKey]];
if (self.delegate && [self.delegate respondsToSelector:@selector(scheduledLocationManageDidFailWithError:)]) {
[self.delegate scheduledLocationManageDidFailWithError:error];
}
}
}
#pragma mark - Helpers
-(BOOL)isLocationServiceAvailable
{
if([CLLocationManager locationServicesEnabled]==NO ||
[CLLocationManager authorizationStatus]==kCLAuthorizationStatusDenied ||
[CLLocationManager authorizationStatus]==kCLAuthorizationStatusRestricted){
return NO;
}else{
return YES;
}
}
@end
Я попробовал твой метод, но он не сработал на моей стороне. Можете ли вы показать мне свой код?
Я на самом деле нашел решение для решения проблемы службы определения местоположения в iOS 7.
В iOS 7 нельзя запускать службу определения местоположения в фоновом режиме. Если вы хотите, чтобы служба определения местоположения продолжала работать в фоновом режиме, вы должны запустить ее на переднем плане, и она будет продолжать работать в фоновом режиме.
Если вы похожи на меня, остановите службу определения местоположения и используйте таймер для ее повторного запуска в фоновом режиме, это не будет работать в iOS 7.
Для получения более подробной информации вы можете посмотреть первые 8 минут видео 307 с WWDC 2013: https://developer.apple.com/wwdc/videos/
Обновление: служба определения местоположения может работать в фоновом режиме. Пожалуйста, проверьте фоновые сервисы определения местоположения, не работающие в iOS 7, для получения обновленной публикации с полным решением, размещенным на Github, и публикацией в блоге, объясняющей детали.
Шаги для реализации этого заключаются в следующем:
Добавьте "Регистры приложений для обновлений местоположения" в пункте 0 "Необходимые фоновые режимы" в info.plist вашего проекта.
Напишите код ниже, когда приложение завершит запуск.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(startFetchingLocationsContinously) name:START_FETCH_LOCATION object:nil];
Напишите ниже код, с которого вы хотите начать отслеживать
[[NSNotificationCenter defaultCenter] postNotificationName:START_FETCH_LOCATION object:nil]; AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate]; [appDelegate startUpdatingDataBase];
Вставьте следующий код в AppDelegate.m
#pragma mark - Location Update -(void)startFetchingLocationsContinously{ NSLog(@"start Fetching Locations"); self.locationUtil = [[LocationUtil alloc] init]; [self.locationUtil setDelegate:self]; [self.locationUtil startLocationManager]; } -(void)locationRecievedSuccesfullyWithNewLocation:(CLLocation*)newLocation oldLocation:(CLLocation*)oldLocation{ NSLog(@"location received successfullly in app delegate for Laitude: %f and Longitude:%f, and Altitude:%f, and Vertical Accuracy: %f",newLocation.coordinate.latitude,newLocation.coordinate.longitude,newLocation.altitude,newLocation.verticalAccuracy); } -(void)startUpdatingDataBase{ UIApplication* app = [UIApplication sharedApplication]; bgTask = UIBackgroundTaskInvalid; bgTask = [app beginBackgroundTaskWithExpirationHandler:^(void){ [app endBackgroundTask:bgTask]; }]; SAVE_LOCATION_TIMER = [NSTimer scheduledTimerWithTimeInterval:300 target:self selector:@selector(startFetchingLocationsContinously) userInfo:nil repeats:YES]; }
Добавьте класс по имени "LocationUtil" и вставьте следующий код в заголовочный файл:
#import <Foundation/Foundation.h> #import <CoreLocation/CoreLocation.h> @protocol LocationRecievedSuccessfully <NSObject> @optional -(void)locationRecievedSuccesfullyWithNewLocation:(CLLocation*)newLocation oldLocation:(CLLocation*)oldLocation; -(void)addressParsedSuccessfully:(id)address; @end @interface LocationUtil : NSObject <CLLocationManagerDelegate> { } //Properties @property (nonatomic,strong) id<LocationRecievedSuccessfully> delegate; -(void)startLocationManager;
И вставьте следующий код в LocationUtil.m
-(void)startLocationManager{ locationManager = [[CLLocationManager alloc] init]; locationManager.delegate = self; [locationManager setPausesLocationUpdatesAutomatically:YES]; //Utkarsh 20sep2013 //[locationManager setActivityType:CLActivityTypeFitness]; locationManager.distanceFilter = kCLDistanceFilterNone; locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation; [locationManager startUpdatingLocation]; //Reverse Geocoding. geoCoder=[[CLGeocoder alloc] init]; //set default values for reverse geo coding. } //for iOS<6 - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation { //call delegate Method [delegate locationRecievedSuccesfullyWithNewLocation:newLocation oldLocation:oldLocation]; NSLog(@"did Update Location"); } //for iOS>=6. - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { CLLocation *newLocation = [locations objectAtIndex:0]; CLLocation *oldLocation = [locations objectAtIndex:0]; [delegate locationRecievedSuccesfullyWithNewLocation:newLocation oldLocation:oldLocation]; NSLog(@"did Update Locationsssssss"); }