wja*_*ans 202 objective-c background-process core-location ios
我正在寻找一种在iOS应用程序中每n分钟更新一次后台位置的方法.我正在使用iOS 4.3,该解决方案适用于非越狱的iPhone.
我尝试/考虑了以下选项:
CLLocationManager startUpdatingLocation/startMonitoringSignificantLocationChanges:这根据预期在后台工作,基于配置的属性,但似乎不可能强制它每n分钟更新一次位置NSTimer:当应用程序在前台运行但似乎不是为后台任务设计时,是否有效UIApplication:beginBackgroundTaskWithExpirationHandler:据我所知,当应用程序移动到后台而不是实现"长时间运行"的后台进程时,这应该用于在后台完成一些工作(也限制时间).如何实施这些常规后台位置更新?
wja*_*ans 113
我找到了一个在Apple Developer Forums的帮助下实现这个的解决方案:
location background modeNSTimer在后台创建一个UIApplication:beginBackgroundTaskWithExpirationHandler:n是小的比UIApplication:backgroundTimeRemaining它会工作得很好.如果n是较大的,则location manager应该启用(和残疾人)前再次有剩余,避免后台任务被杀死没有时间. 这是有效的,因为location是三种允许的后台执行类型之一.
注意:我在模拟器中测试它时失去了一些时间.但是,它在我的手机上工作正常.
Les*_*ary 54
在iOS 8/9/10上,每5分钟更新一次背景位置,请执行以下操作:
转到项目 - >功能 - >背景模式 - >选择位置更新
转到项目 - >信息 - >使用空值(或可选的任何文本)添加密钥NSLocationAlwaysUsageDescription
为了使您的应用程序在后台运行时位置正常工作,并将坐标发送到Web服务或每隔5分钟对它们执行任何操作,就像在下面的代码中一样.
我没有使用任何后台任务或计时器.我已经使用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
Run Code Online (Sandbox Code Playgroud)
Bus*_*hid 35
我在我正在开发的应用程序中这样做了.当应用程序在后台但应用程序不断收到位置更新时,计时器不起作用.我在文档中的某处读过(我现在似乎无法找到它,当我这样做时会发布更新),当应用程序在后台时,只能在活动的运行循环上调用方法.即使在bg中,app委托也有一个活跃的运行循环,所以你不需要创建自己的循环来使这个工作.[我不确定这是否是正确的解释,但这就是我从我所读到的内容中理解的]
首先,在应用程序的info.plist中添加location密钥对象UIBackgroundModes.现在,您需要做的是在应用中的任何位置启动位置更新:
CLLocationManager locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;//or whatever class you have for managing location
[locationManager startUpdatingLocation];
Run Code Online (Sandbox Code Playgroud)
接下来,编写一个方法来处理位置更新,例如-(void)didUpdateToLocation:(CLLocation*)location,在应用程序委托中.然后实现方法locationManager:didUpdateLocation:fromLocation的CLLocationManagerDelegate在其中您开始位置管理类(因为我们设置的位置经理授人以"自我").在此方法中,您需要检查是否已经过了必须处理位置更新的时间间隔.您可以通过每次保存当前时间来完成此操作.如果该时间已过,请从您的app delegate调用方法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];
}
}
}
Run Code Online (Sandbox Code Playgroud)
即使您的应用处于后台,这也会每隔5分钟调用您的方法.Imp:如果你应该使用你的位置数据的准确性并不重要,这个实现会耗尽电池电量[locationManager startMonitoringSignificantLocationChanges]
在将此添加到您的应用程序之前,请阅读位置感知编程指南
Ale*_*ngo 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]);
}
Run Code Online (Sandbox Code Playgroud)
刚刚测试过它:
我启动了应用程序,去了背景并在车上移动了几分钟.然后我回家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;
}
Run Code Online (Sandbox Code Playgroud)
Hel*_*miB 13
对于有噩梦的人来说,找出这个.我有一个简单的解决方案.
使用以下方法添加计时
-(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];
}
Run Code Online (Sandbox Code Playgroud)只是不要忘记在info.plist中添加"应用寄存器以进行位置更新".
这是我使用的:
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)
}
}
Run Code Online (Sandbox Code Playgroud)
我在AppDelegate中开始跟踪:
BackgroundLocationManager.instance.start()
Run Code Online (Sandbox Code Playgroud)
不幸的是,你所有的假设似乎都是正确的,我认为没有办法做到这一点.为了节省电池寿命,iPhone的定位服务基于移动.如果手机位于一个位置,则位置服务不可见.
该CLLocationManager会只调用locationManager:didUpdateToLocation:fromLocation:当手机接收到一个位置更新,其中只有三个位置服务中的一个(手机信号塔,GPS,WIFI)感知的变化发生.
其他一些可能有助于进一步解决方案的事情:
启动和停止服务会导致didUpdateToLocation调用委托方法,但newLocation可能具有旧的时间戳.
在后台运行时,请注意可能很难获得Apple批准的"完整"LocationServices支持.从我所看到的,他们专门设计startMonitoringSignificantLocationChanges为需要后台位置支持的应用程序的低功耗替代品,并强烈鼓励开发人员使用它,除非应用程序绝对需要它.
祝好运!
更新:这些想法现在可能已经过时了.看起来好像人们在上面的@wjans回答中取得了成功.
我确实使用位置服务编写应用程序,应用程序必须每10秒发送一次位置.而且效果很好.
只需按照Apple的doc 使用" allowDeferredLocationUpdatesUntilTraveled:timeout "方法即可.
我做的是:
必需:注册更新位置的后台模式.
1.创建LocationManger和startUpdatingLocation,以accuracy和filteredDistance为任何你想要的:
-(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];
}
Run Code Online (Sandbox Code Playgroud)
2.为了让app永远allowDeferredLocationUpdatesUntilTraveled:timeout在后台使用方法运行,updatingLocation当app移动到后台时你必须使用新参数重启,如下所示:
- (void)applicationWillResignActive:(UIApplication *)application {
_isBackgroundMode = YES;
[_locationManager stopUpdatingLocation];
[_locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
[_locationManager setDistanceFilter:kCLDistanceFilterNone];
_locationManager.pausesLocationUpdatesAutomatically = NO;
_locationManager.activityType = CLActivityTypeAutomotiveNavigation;
[_locationManager startUpdatingLocation];
}
Run Code Online (Sandbox Code Playgroud)
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];
}
}
Run Code Online (Sandbox Code Playgroud)
4.但是,你应该处理的数据,然后locationManager:didFinishDeferredUpdatesWithError:回调你的目的
- (void) locationManager:(CLLocationManager *)manager didFinishDeferredUpdatesWithError:(NSError *)error {
_deferringUpdates = NO;
//do something
}
Run Code Online (Sandbox Code Playgroud)
5. 注意:我认为我们应该重置LocationManager每次应用程序在背景/地面模式之间切换的参数.
if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) {
[self.locationManager setAllowsBackgroundLocationUpdates:YES];
}
Run Code Online (Sandbox Code Playgroud)
这是iOS 9以来的背景位置跟踪所必需的.
| 归档时间: |
|
| 查看次数: |
171049 次 |
| 最近记录: |