定期更新 iOS 后台位置

我正在编写一个应用程序,需要与 高精度低频率的后台位置更新。解决方案似乎是一个后台 NSTimer 任务,它启动位置管理器的更新,然后立即关闭。以前也有人问过这个问题:

如何在我的 iOS 应用程序中获得每 n 分钟的后台位置更新?

在应用程序进入后台后每 n 分钟获取用户位置

IOS 不是典型的后台位置跟踪定时器问题

IOS 长时间运行的后台定时器与“位置”后台模式

基于位置跟踪的 iOS 全时后台服务

但是我还没有让 最低限度的例子运作起来。在尝试了以上所有可接受的答案之后,我总结出了一个出发点。进入背景:

- (void)applicationDidEnterBackground:(UIApplication *)application
{
self.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
NSLog(@"ending background task");
[[UIApplication sharedApplication] endBackgroundTask:self.bgTask];
self.bgTask = UIBackgroundTaskInvalid;
}];




self.timer = [NSTimer scheduledTimerWithTimeInterval:60
target:self.locationManager
selector:@selector(startUpdatingLocation)
userInfo:nil
repeats:YES];
}

以及委托方法:

- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {


NSLog(@"%@", newLocation);


NSLog(@"background time: %f", [UIApplication sharedApplication].backgroundTimeRemaining);
[self.locationManager stopUpdatingLocation];


}

当前的行为是,backgroundTimeRemaining从180秒减少到零(同时记录位置) ,然后执行到期处理程序,不再生成进一步的位置更新。如何修改以上代码以接收 定期在后台更新位置

更新 : 我的目标是 iOS7,并且有证据表明后台任务的行为是不同的:

从后台任务启动 iOS7中的位置管理器

97612 次浏览

How about giving it a try with startMonitoringSignificantLocationChanges: API? It definitely fires with less frequency and the accuracy is reasonably good. Additionally it has lot more advantages than using other locationManager API's.

Lot more regarding this API has already been discussed on this link

You need to add the location update mode in your application by adding following key in you info.plist.

<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>

didUpdateToLocation method will be called (even when your app is in background). You can perform any thing on the bases of this method call

  1. If you have the UIBackgroundModes in your plist with location key then you don't need to use beginBackgroundTaskWithExpirationHandler method. That's redundant. Also you're using it incorrectly (see here) but that's moot since your plist is set.

  2. With UIBackgroundModes location in the plist the app will continue to run in the background indefinitely only as long as CLLocationManger is running. If you call stopUpdatingLocation while in the background then the app will stop and won't start again.

    Maybe you could call beginBackgroundTaskWithExpirationHandler just before calling stopUpdatingLocation and then after calling startUpdatingLocation you could call the endBackgroundTask to keep it backgrounded while the GPS is stopped, but I've never tried that - it's just an idea.

    Another option (which I haven't tried) is to keep the location manager running while in the background but once you get an accurate location change the desiredAccuracy property to 1000m or higher to allow the GPS chip to get turned off (to save battery). Then 10 minutes later when you need another location update, change the desiredAccuracy back to 100m to turn on the GPS until you get an accurate location, repeat.

  3. When you call startUpdatingLocation on the location manager you must give it time to get a position. You should not immediately call stopUpdatingLocation. We let it run for a maximum of 10 seconds or until we get a non-cached high accuracy location.

  4. You need to filter out cached locations and check the accuracy of the location you get to make sure it meets your minimum required accuracy (see here). The first update you get may be 10 mins or 10 days old. The first accuracy you get may be 3000m.

  5. Consider using the significant location change APIs instead. Once you get the significant change notification, you could start CLLocationManager for a few seconds to get a high accuracy position. I'm not certain, I've never used the significant location change services.

It seems that stopUpdatingLocation is what triggers the background watchdog timer, so I replaced it in didUpdateLocation with:

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

which appears to effectively power down the GPS. The selector for the background NSTimer then becomes:

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

All I'm doing is periodically toggling the accuracy to get a high-accuracy coordinate every few minutes and because the locationManager hasn't been stopped, backgroundTimeRemaining stays at its maximum value. This reduced battery consumption from ~10% per hour (with constant kCLLocationAccuracyBest in the background) to ~2% per hour on my device.

I did write an app using Location services, app must send location every 10s. And it worked very well.

Just use the "allowDeferredLocationUpdatesUntilTraveled:timeout" method, following Apple's doc.

Steps are as follows:

Required: Register background mode for update Location.

1. Create LocationManger and startUpdatingLocation, with accuracy and filteredDistance as whatever you want:

-(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. To keep app run forever using "allowDeferredLocationUpdatesUntilTraveled:timeout" method in background, you must restart updatingLocation with new parameter when app moves to background, like this:

- (void)applicationWillResignActive:(UIApplication *)application {
_isBackgroundMode = YES;


[_locationManager stopUpdatingLocation];
[_locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
[_locationManager setDistanceFilter:kCLDistanceFilterNone];
_locationManager.pausesLocationUpdatesAutomatically = NO;
_locationManager.activityType = CLActivityTypeAutomotiveNavigation;
[_locationManager startUpdatingLocation];
}

3. App gets updatedLocations as normal with "locationManager:didUpdateLocations:" callback:

-(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. But you should handle the data in then "locationManager:didFinishDeferredUpdatesWithError:" callback for your purpose

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


_deferringUpdates = NO;


//do something
}

5. NOTE: I think we should reset parameters of LocationManager each time app switches between background/forground mode.

When it is time to start location service and stop background task, background task should be stopped with a delay (1 second should be enough). Otherwise location service wont start. Also Location Service should be left ON for a couple of seconds (e.g. 3 seconds).

There is a cocoapod APScheduledLocationManager that allows to get background location updates every n seconds with desired location accuracy.

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

The repository also contains an example app written in Swift 3.

After iOS 8 their are several changes in CoreLocation framework related background fetch and updations made by apple.

1) Apple introduce the AlwaysAuthorization request for fetching the location update in the application.

2) Apple introduce backgroundLocationupdates for fetching location from background in iOS.

For fetching location in background you need to enable Location Update in Capabilities of Xcode.

//
//  ViewController.swift
//  DemoStackLocation
//
//  Created by iOS Test User on 02/01/18.
//  Copyright © 2018 iOS Test User. All rights reserved.
//


import UIKit
import CoreLocation


class ViewController: UIViewController {


var locationManager: CLLocationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
startLocationManager()
}


override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}


internal func startLocationManager(){
locationManager.requestAlwaysAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.delegate = self
locationManager.allowsBackgroundLocationUpdates = true
locationManager.startUpdatingLocation()
}


}


extension ViewController : CLLocationManagerDelegate {


func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
let location = locations[0] as CLLocation
print(location.coordinate.latitude) // prints user's latitude
print(location.coordinate.longitude) //will print user's longitude
print(location.speed) //will print user's speed
}


}
locationManager.allowsBackgroundLocationUpdates = true

To use standard location services while the application is in the background you need first turn on Background Modes in the Capabilities tab of the Target settings, and select Location updates.

Or, add it directly to the Info.plist.

<key>NSLocationAlwaysUsageDescription</key>
<string>I want to get your location Information in background</string>
<key>UIBackgroundModes</key>
<array><string>location</string> </array>

Then you need to setup the CLLocationManager

Objective C

//The Location Manager must have a strong reference to it.
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
//Request Always authorization (iOS8+)
if ([_locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) { [_locationManager requestAlwaysAuthorization];
}
//Allow location updates in the background (iOS9+)
if ([_locationManager respondsToSelector:@selector(allowsBackgroundLocationUpdates)]) { _locationManager.allowsBackgroundLocationUpdates = YES;
}
[_locationManager startUpdatingLocation];

Swift

self.locationManager.delegate = self
if #available (iOS 8.0,*) {
self.locationManager.requestAlwaysAuthorization()
}
if #available (iOS 9.0,*) {
self.locationManager.allowsBackgroundLocationUpdates = true
}
self.locationManager.startUpdatingLocation()


Ref:https://medium.com/@javedmultani16/location-service-in-the-background-ios-942c89cd99ba