我正在寻找一种方法来获得背景位置更新每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.

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


当前回答

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

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

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

其他回答

不幸的是,你所有的假设似乎都是正确的,我认为没有办法做到这一点。为了节省电池寿命,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的回答上取得了成功。

给其他做噩梦的人想想这个。我有一个简单的解决办法。

look this example from raywenderlich.com-> have sample code, this works perfectly, but unfortunately no timer during background location. this will run indefinitely. Add timer by using : -(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]; } Just don't forget to add "App registers for location updates" in info.plist.

似乎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%

以下是我使用的方法:

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

附件是基于以下的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")
        }
    }