如何在iOS应用程序中每n分钟获取一次后台位置更新?


206

我正在寻找一种方法来在我的iOS应用程序中每隔n分钟获取一次后台位置更新。我正在使用iOS 4.3,该解决方案应适用于非越狱iPhone。

我尝试/考虑了以下选项:

  • CLLocationManager startUpdatingLocation/startMonitoringSignificantLocationChanges:此操作基于配置的属性,可在预期的后台运行,但是似乎无法强制每n分钟更新一次位置
  • NSTimer:当应用程序在前台运行时可以工作,但似乎不是为后台任务而设计的
  • 本地通知:可以每n分钟安排一次本地通知,但是无法执行一些代码来获取当前位置(无需用户通过通知启动应用程序)。这种方法似乎也不是一种干净的方法,因为这不是通知的用途。
  • UIApplication:beginBackgroundTaskWithExpirationHandler:据我所知,当应用程序移至后台时,应使用此方法在后台完成一些工作(时间也受限制),而不是实施“长时间运行”的后台进程。

如何实施这些常规的后台位置更新?




1
如果您想使其在iOS 7上运行,则可以在这里尝试以下解决方案:stackoverflow.com/questions/18946881/…如果您有任何疑问,欢迎您加入我们的讨论区:mobileoop.com/background -location-update-programming-for-ios-7
Ricky

1
您所有的发现都是正确的(四个要点)。那就是有价值的信息,知道什么与您的用例不符?是的,当处于“挂起模式”或“未运行”时,没有最终的方法可以每n分钟更新一次。
LenArt 2014年

Answers:


113

我找到了一个在Apple开发者论坛的帮助下实现此目的的解决方案:

  • 指定 location background mode
  • NSTimer在后台创建一个UIApplication:beginBackgroundTaskWithExpirationHandler:
  • 如果n小的UIApplication:backgroundTimeRemaining它会工作得很好。如果n较大的,则location manager应该启用(和残疾人)再之前还有剩余,避免后台任务被杀死没有时间。

之所以有效,是因为位置是后台执行的三种允许类型之一

注意:我通过在不起作用的模拟器中进行测试来浪费了一些时间。但是,它可以在我的手机上正常工作。


24
您是否碰巧拥有该论坛的链接。我正在寻求实现相同类型的位置映射,但无法使其正常工作。或者一些示例代码将不胜感激。
utahwithak 2011年

4
您能否解释一下为什么只停止并启动位置管理器后10分钟(最长允许时间)后不杀死后台任务?这是某种预期的功能吗?如果发生这种情况,听起来更像是Apple SDK中的错误。您尝试使用哪个iOS版本?
saurabh'1

5
@all:是的,我们的应用程序可在AppStore中找到。我不会发布所有代码,所有提到的项目要点都是明确记录的功能。如果您遇到特定问题,请发表您自己的问题,以说明您尝试过的内容以及出了什么问题。
wjans 2012年

3
@ user836026:是的,这就是我指定背景模式的意思。停止位置更新后,应在10分钟内重新启动它,以免该应用终止。
wjans 2012年

12
对于那些希望看到一些实际的代码代表正在这里讨论的内容,检查stackoverflow.com/questions/10235203/...
洛洛

55

iOS 8/9/10上,要使背景位置每5分钟更新一次,请执行以下操作:

  1. 转到项目->功能->后台模式->选择位置更新

  2. 转到项目->信息->添加具有空值(或可选的任何文本)的键NSLocationAlwaysUsageDescription

  3. 要在您的应用程序处于后台运行时使位置正常工作,并每5分钟将坐标发送到Web服务或对它们执行任何操作,请按照以下代码进行实现。

我没有使用任何后台任务或计时器。我已经在装有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 2015年

2
我的项目中禁用了后台抓取功能(无论打开还是关闭都无关紧要)。但是,必须将pausesLocationUpdatesAutomatically自动设置为NO,以上示例才能正常工作。如果先前被系统暂停,它将不会自动恢复,一旦您再次开始移动它。这就是为什么在上面的示例中将此属性设置为NO的原因。
Leszek Szary

1
我认为你是绝对正确的。在此处找到有关此问题的更多信息:stackoverflow.com/q/17484352/1048331
Myxtic 2015年

2
@LeszekS,您需要添加以下代码来支持iOS 9对背景的支持 if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) { [self.locationManager setAllowsBackgroundLocationUpdates:YES]; }
Fenil

2
这样做有什么方便?恒定的高精度仍在耗尽电池电量。您唯一不做的事情是直到达到5分钟的间隔才真正使用已经收到的位置...
Honey

34

我在正在开发的应用程序中做到了这一点。当应用程序在后台运行但应用程序不断接收位置更新时,计时器不起作用。我在文档中的某处读到了(我现在似乎找不到了,我将在以后发布更新),该方法只能在后台运行时在活动运行循环上调用。即使在bg中,应用程序委托也有一个活动的运行循环,因此您无需创建自己的循环即可执行此工作。[我不确定这是否是正确的解释,但这就是我从阅读中了解的方式]

首先,在应用程序的info.plist中添加location密钥的对象UIBackgroundModes。现在,您需要做的是在应用程序中的任何位置启动位置更新:

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

接下来,-(void)didUpdateToLocation:(CLLocation*)location在应用程序委托中编写一种方法来处理位置更新。然后实现方法locationManager:didUpdateLocation:fromLocationCLLocationManagerDelegate在其中您开始位置管理类(因为我们设置的位置经理授人以“自我”)。在此方法内部,您需要检查必须经过的时间间隔(必须在该时间间隔之后)。您可以通过每次保存当前时间来做到这一点。如果时间已过,请从您的应用程序委托中调用方法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分钟调用一次您的方法。小信息:此实现会消耗掉电池电量,如果您的位置数据的准确性不是很关键,则应使用[locationManager startMonitoringSignificantLocationChanges]

将其添加到您的应用之前,请阅读位置感知编程指南


3
这样一来,位置服务就一直处于启用状态(确实消耗了电池电量),我不希望这样。我想每隔n分钟启用一次定位服务,并在获得良好修复后立即将其禁用(只是注意到我没有在问题中清楚地说明这一点)。我可以在我描述的解决方案中实现此行为。
wjans 2011年

3
您可以将位置管理器的精度设置为1公里-这将使您的电池几乎完好无损。5分钟后,将精度设置为1m。当您获得满意的位置时(通常在5s之后),只需将精度设置回1km。
knagode 2013年

knagode,我尝试了建议的电池耗尽问题解决方案,但即使在N分钟后提高了准确性,也不会再次调用locationManager:didUpdateLocations方法。我尝试了startUpdating和stopUpdating,而不是提高和降低准确性,而是成功地调用了locationManager:didUpdateLocations方法,经过了N分钟,但在背景模式下无法使用...
Hardik Darji 2014年

至于文档的链接。请参见此处:“从启动相应位置服务的线程中调用委托对象的方法。该线程本身必须具有活动的运行循环,就像在应用程序主线程中找到的那样。”
亲爱的

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
如果应用程序崩溃或被杀死,则系统不会重启,对吗?
2012年

1
要获得更具可读性的代码,您可以执行[locations lastObject];而不是[locations objectAtIndex:[locations count]-1]
axello 2012

您的方法仅适用于ios6?
pengwang 2012年

我们必须使用这个吗?我以为在类的viewDidLoad中只有位置管理器代码,并且已在plist文件中设置了背景键,应用程序为位置更新注册了该键就足够了?您能帮我这个忙吗?
nithinreddy

是的,它的工作方式就像魅力nithinreddy,但是它将在10分钟后停止工作,因为在此期间iOS会杀死长线程。如果您想永久启动这些服务,那么我的解决方案是完美的。我两天前做了一些测试,每小时耗电量6%。使用[locationManager startUpdatingLocation]会消耗17%
Alejandro Luengo

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中添加“用于位置更新的应用程序注册”。


这样会超过3分钟吗?
SleepNot

必须在功能->后台模式中设置位置。
塞尔吉奥·安德烈奥蒂

它不起作用!?我已经在iOS 8检查ANS iOS的10
格尔登

如我错了请纠正我。根据我的阅读,您只启动一次locationManager。之后,所有间隔都是多余的。由于它已经开始
蜂蜜

它正在工作,但是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()

谢谢。我们的项目需要跟踪用户位置+在后台发送PubNub事件,您的解决方案才能很好地运行。
user921509 '16

嗨,Hmitkov,在这里我可以调用sendLocationToServer方法来发送服务器上的用户位置
AmanGupta007

@ AmanGupta007您可以在func locationManager(管理器:didUpdateLocations :)中调用sendLocationToServer。注意代码中的// TODO注释。
hmitkov

@hmitkov是否可以在后台运行应用程序时启动和停止定位服务?例如,从推送通知中启动位置服务获取一些经/纬度,发送到Web服务,然后停止更新位置。每当推送主体中包含“ content-available” = 1时,请执行此操作。
DookieMan

1
我已经尝试过此代码,但似乎无法在iOS11上运行?(我尚未在其他任何版本上对其进行测试。)
汤姆(Tom

6

不幸的是,您的所有假设似乎都是正确的,而且我认为没有办法做到这一点。为了节省电池寿命,iPhone的定位服务基于移动性。如果电话位于一个位置,则位置服务将看不到它。

CLLocationManager会只调用locationManager:didUpdateToLocation:fromLocation:当手机接收到一个位置更新,其中只有三个位置服务中的一个(手机信号塔,GPS,WIFI)感知的变化发生。

其他可能有助于进一步解决方案的问题:

  • 启动和停止服务将导致didUpdateToLocation委托方法被调用,但是newLocation可能具有旧的时间戳。

  • 区域监控可能会有所帮助

  • 在后台运行时,请注意,可能很难获得Apple批准的“完整” LocationServices支持。据我所知,它们是startMonitoringSignificantLocationChanges为需要后台位置支持的应用程序专门设计的低功耗替代方案,并强烈鼓励开发人员使用该应用程序,除非该应用程序绝对需要它。

祝好运!

更新:这些想法可能现在已经过时。上面的@wjans回答似乎使人们获得了成功。


1
AppStore中有可用的应用程序(例如“ My Locus”),这些应用程序确实可以在后台获取位置更新。他们没有使定位服务保持活动状态,但是只是按照定义的时间间隔启用了定位服务。他们如何做到这一点?
wjans 2011年

2
在您描述的情况下,该应用程序很可能使用startMonitoringSignificantLocationChanges方法。在这里,手机在收到位置更新信息后会暂时“唤醒”,但是没有间隔可以设置为在后台“ ping”该服务。当手机移动(或从蜂窝移动到GPS或Wifi)时,它会触发更新。在话题斯坦福大学的iTunes U的讲座对我来说是非常有帮助的-希望它可以帮助你找到一个解决办法:itunes.apple.com/us/itunes-u/iphone-application-development/...
Chazbot

2
指针的Thx。但是,我仍然不知道该应用程序在做什么。即使我的手机在我的办公桌前,甚至根本不动,我仍然可以看到定位服务每10分钟触发一次(准确)。如果我理解正确,startMonitoringSignificantLocationChanges在这种情况下不会进行任何更新。
wjans 2011年

1
@wjans手机的电池消耗情况如何,您是否注意到它可能是由于Mylocus应用程序而迅速消耗掉了?
user836026

6

我确实使用定位服务编写了一个应用,该应用必须每10秒发送一次位置。而且效果很好。

只需按照Apple的文档使用“ allowDeferredLocationUpdatesUntilTraveled:timeout ”方法即可。

我所做的是:

必需:注册后台模式以更新位置。

1.使用和根据需要创建LocationManger和:startUpdatingLocationaccuracyfilteredDistance

-(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:回调正常获取updatedLocations :

-(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:根据自己的目的在then 回调中处理数据

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

     _deferringUpdates = NO;

     //do something 
}

5. 注意:我认为我们应该在LocationManager应用程序每次在背景/地面模式之间切换时重置参数。


@wjans说明的解决方案或以上解决方案,我应该更喜欢节省设备电池电量,否则两者都会影响相同?
Yash 2014年

2
我确实尝试了您提到的两种解决方案,并发现@wjans的解决方案可以节省更多电量。但是,在iOS 8出现之后,该解决方案似乎不再能正常工作。有关更多详细信息:大多数情况下,应用程序在后台模式下不能长时间使用。
samthui7 2015年

@ samthui7为什么要设置pausesLocationUpdatesAutomatically = false?
CedricSoubrie

我的应用程序的要求是保持每10秒发送一次userLocation,同时pausesLocationUpdatesAutomatically = true告诉位置管理器如果似乎没有位置更改(Apple doc)则暂停更新。无论如何,我没有明确测试location manager pauses updates尚未:D 的情况。
samthui7 2013年

@CedricSoubrie:设置pausesLocationUpdatesAutomatically = true导致我的应用停止更新位置。
samthui7年

5
if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) {
    [self.locationManager setAllowsBackgroundLocationUpdates:YES];
}

自iOS 9起,这对于后台位置跟踪来说是必需的。


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的客户端。所有客户端都是本地编写的,它们都在后台正常工作。该项目是麻省理工学院许可的。

iOS项目使用iOS 7的基本SDK针对iOS6。您可以在此处获取代码。

如果发现任何问题,请在github上提交问题。谢谢。


我尝试了您的解决方案,但不起作用...当应用程序进入后台时,即使提高准确性后,在我的情况下,应用程序也未获得名为didUpdateToLocation方法的
吸引力

@HardikDarji重要问题。你要搬家吗 否则,位置更新可能会停止。尝试将手机带出去散步或开车,看看是否可以解决问题。
nickfox 2014年

谢谢你快速的回复。但是我希望每2分钟更新一次位置,而无需担心手机是否在移动。在这种情况下,不会调用didUpdateToLocation方法。我在这里寻找:如何每n分钟更新一次位置信息!
Hardik Darji 2014年

尝试将timeIntervalInSeconds设置为120并取消注释此行:locationManager.pausesLocationUpdatesAutomatically = NO;
nickfox 2014年

1
您在上述注释中发布的解决方案会导致电池寿命缩短,还是您还在为此进行一些优化?
samfr

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

有一个cocoapod APScheduledLocationManager,它允许每n秒以所需的位置精度获取后台位置更新。

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

该存储库还包含一个用Swift 3编写的示例应用程序。


您在目标c中有类似的东西吗?
Moxarth'7

1

在iOS 9和watchOS 2.0中,CLLocationManager上有一个新方法,可让您请求当前位置:CLLocationManager:requestLocation()。这将立即完成,然后将位置返回给CLLocationManager委托。

您现在可以使用NSTimer使用此方法每分钟请求一个位置,而不必使用startUpdatingLocation和stopUpdatingLocation方法。

但是,如果要基于与上一个位置之间X米的变化来捕获位置,只需将CLLocationManger的distanceFilter属性设置为X即可调用startUpdatingLocation()。


-1

随附基于以下内容的Swift解决方案:

App registers for location updates在info.plist中定义

保持locationManager一直运行

开关kCLLocationAccuracy之间BestForNavigation(5秒钟,以获得位置)与ThreeKilometers用于所述等待时段的其余部分,以避免电池的排水

本示例在前景中每1分钟更新一次位置,在背景中每15分钟更新一次位置。

该示例可与在iOS 7设备上运行的Xcode 6 Beta 6完美配合。

在应用程序委托中(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")
        }
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.