我正在寻找一种方法来获得背景位置更新每n分钟在我的iOS应用程序。我使用的是iOS 4.3,解决方案应该适用于未越狱的iphone。

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

CLLocationManager startUpdatingLocation/startMonitoringSignificantLocationChanges: This works in the background as expected, based on the configured properties, but it seems not possible to force it to update the location every n minutes NSTimer: Does work when the app is running in the foreground but doesn't seem to be designed for background tasks Local notifications: Local notifications can be scheduled every n minutes, but it's not possible to execute some code to get the current location (without the user having to launch the app via the notification). This approach also doesn't seem to be a clean approach as this is not what notifications should be used for. UIApplication:beginBackgroundTaskWithExpirationHandler: As far as I understand, this should be used to finish some work in the background (also limited in time) when an app is moved to the background rather than implementing "long-running" background processes.

如何实现这些定期的后台位置更新?


当前回答

I did this in an application I'm developing. The timers don't work when the app is in the background but the app is constantly receiving the location updates. I read somewhere in the documentation (i can't seem to find it now, i'll post an update when i do) that a method can be called only on an active run loop when the app is in the background. The app delegate has an active run loop even in the bg so you dont need to create your own to make this work. [Im not sure if this is the correct explanation but thats how I understood from what i read]

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

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

Next, write a method to handle the location updates, say -(void)didUpdateToLocation:(CLLocation*)location, in the app delegate. Then implement the method locationManager:didUpdateLocation:fromLocation of CLLocationManagerDelegate in the class in which you started the location manager (since we set the location manager delegate to 'self'). Inside this method you need to check if the time interval after which you have to handle the location updates has elapsed. You can do this by saving the current time every time. If that time has elapsed, call the method UpdateLocation from your app delegate:

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]

在将此添加到应用程序之前,请阅读位置感知编程指南

其他回答

似乎stopUpdatingLocation是什么触发后台看门狗定时器,所以我在didUpdateLocation替换它:

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

这似乎有效地关闭了GPS。然后,背景NSTimer的选择器变成:

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

我所做的是每隔几分钟定期切换精度以获得高精度坐标,因为locationManager没有停止,backgroundtimerremain保持在其最大值。这将我的设备上的电池消耗从每小时~10%(在后台使用恒定的kCLLocationAccuracyBest)降低到每小时~2%

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

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

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

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

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

然而,如果你想要根据上次位置X米的变化来捕获位置,只需要设置CLLocationManger的distanceFilter属性并调用startUpdatingLocation()。

附件是基于以下的Swift解决方案:

在info.plist中定义位置更新的App寄存器

保持locationManager一直运行

在BestForNavigation和threekm之间切换kCLLocationAccuracy(5秒获得位置),以避免电池耗尽

这个例子在前台每1分钟更新一次位置,在后台每15分钟更新一次位置。

这个例子适用于运行在iOS 7设备上的Xcode 6 Beta 6。

在App委托中(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")
        }
    }

不幸的是,你所有的假设似乎都是正确的,我认为没有办法做到这一点。为了节省电池寿命,iPhone的定位服务是基于移动的。如果手机放在一个位置,定位服务就看不见它。

CLLocationManager只会在手机接收到位置更新时调用locationManager:didUpdateToLocation:fromLocation:,这只会发生在三个位置服务(蜂窝塔,gps, wifi)之一感知到变化时。

其他一些事情可能有助于进一步的解决方案:

Starting & Stopping the services causes the didUpdateToLocation delegate method to be called, but the newLocation might have an old timestamp. Region Monitoring might help When running in the background, be aware that it may be difficult to get "full" LocationServices support approved by Apple. From what I've seen, they've specifically designed startMonitoringSignificantLocationChanges as a low power alternative for apps that need background location support, and strongly encourage developers to use this unless the app absolutely needs it.

好运!

更新:这些想法现在可能已经过时了。看起来人们在@wjans的回答上取得了成功。