由iBeacon触发的iOS 8后台位置更新

时间:2015-08-06 20:31:22

标签: ios objective-c iphone gps ibeacon

我正在尝试创建一个可以被iBeacon触发的应用程序,以唤醒(从被杀/暂停/终止)到记录逐秒的GPS信息。当手机超出信标范围时,GPS录音应该停止。我成功地让我的应用程序识别出进入和超出iBeacon范围的didEnterRegion和didExitRegion方法。在didEnterRegion方法中,我想基本上说[locationManager startUpdatingLocation]之类的内容,以便我可以开始跟踪用户的位置。但是,当我尝试添加此行代码时,位置更新在大约10秒后停止。

稍后我发现了article关于此Github project附带的后台位置更新。我将BackgroundTaskManager,LocationShareModel和LocationTracker文件添加到我的项目中。基本上,此解决方案背后的想法是不断重新启动位置管理器,因此它没有机会让后台任务过期并停止发送更新。但是,即使使用此解决方案,我也只能在3分钟内获得位置更新。

我有"位置更新"和"使用蓝牙LE配件"后台模式启用。 "背景提取" (背景应用程序刷新)未启用,根据Apple的引用:"在iOS 8及更高版本中,禁用当前应用程序或所有应用程序的后台应用程序刷新设置不会阻止在背景。"我的应用程序请求"始终"地理位置更新授权。

我无法弄清楚如何解决这个问题,尽管回顾了看似无穷无尽的StackOverflow文章和教程。我在运行iOS 8.3.0的iPhone 5S上测试它。任何见解将不胜感激。请参阅下面的代码摘录。

在AppDelegate.m中:

- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
    if ([region isKindOfClass:[CLBeaconRegion class]]) {
        UILocalNotification *notification = [[UILocalNotification alloc] init];
        notification.alertBody = @"Start recording trip";
        notification.soundName = @"Default";
        [[UIApplication sharedApplication] presentLocalNotificationNow:notification];

        self.recording = YES;
        [self startAutoTrip];
    }
}

- (void) startAutoTrip {
    self.locationTracker = [[LocationTracker alloc]init];
    [self.locationTracker startLocationTracking];
}

- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
    if ([region isKindOfClass:[CLBeaconRegion class]]) {
        UILocalNotification *notification = [[UILocalNotification alloc] init];
        notification.alertBody = @"Stop recording trip";
        notification.soundName = @"Default";
        [[UIApplication sharedApplication] presentLocalNotificationNow:notification];

        [self stopAutoTrip];
        self.recording = NO;
    }
}

- (void)stopAutoTrip {

    // stop recording the locations
    CLSLog(@"Trying to stop location updates");
    [self.locationTracker stopLocationTracking:self.managedObjectContext];
    CLSLog(@"Stop location updates");
}

在LocationTracker.m中(从上面引用的教程中,将60秒和10秒的时间间隔更改为5秒和2秒)。基本上这些是startLocationTracking,didUpdateLocations和stopLocationTracking方法。

- (void)startLocationTracking {
NSLog(@"startLocationTracking");

    if ([CLLocationManager locationServicesEnabled] == NO) {
        NSLog(@"locationServicesEnabled false");
        UIAlertView *servicesDisabledAlert = [[UIAlertView alloc] initWithTitle:@"Location Services Disabled" message:@"You currently have all location services for this device disabled" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
        [servicesDisabledAlert show];
    } else {
        CLAuthorizationStatus authorizationStatus= [CLLocationManager authorizationStatus];

        if(authorizationStatus == kCLAuthorizationStatusDenied || authorizationStatus == kCLAuthorizationStatusRestricted){
            NSLog(@"authorizationStatus failed");
        } else {
            NSLog(@"authorizationStatus authorized");
            CLLocationManager *locationManager = [LocationTracker sharedLocationManager];
            locationManager.delegate = self;
            locationManager.desiredAccuracy = kCLLocationAccuracyBest;
            locationManager.distanceFilter = 10; //meters
            locationManager.activityType = CLActivityTypeAutomotiveNavigation;
            locationManager.pausesLocationUpdatesAutomatically = NO;

            if(IS_OS_8_OR_LATER) {
                [locationManager requestAlwaysAuthorization];
            }
            [locationManager startUpdatingLocation];
        }
    }
}

-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{

    NSLog(@"locationManager didUpdateLocations");

    for(int i=0;i<locations.count;i++){
        CLLocation * newLocation = [locations objectAtIndex:i];

        NSDate *eventDate = newLocation.timestamp;

        NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];

        if (fabs(howRecent) < 10.0 && newLocation.horizontalAccuracy < 20 && locations.count > 0) {


            CLLocationCoordinate2D theLocation = newLocation.coordinate;
            CLLocationAccuracy theAccuracy = newLocation.horizontalAccuracy;

            self.myLastLocation = theLocation;
            self.myLastLocationAccuracy= theAccuracy;

            CLLocationCoordinate2D coords[2];
            coords[0] = ((CLLocation *)locations.lastObject).coordinate;
            coords[1] = newLocation.coordinate;

            [self.shareModel.myLocationArray addObject:newLocation];

        }
    }

    //If the timer still valid, return it (Will not run the code below)
    if (self.shareModel.timer) {
        return;
    }

    self.shareModel.bgTask = [BackgroundTaskManager sharedBackgroundTaskManager];
    [self.shareModel.bgTask beginNewBackgroundTask];

    //Restart the locationMaanger after 1 minute (5 sec)
    self.shareModel.timer = [NSTimer scheduledTimerWithTimeInterval:5 target:self
                                                       selector:@selector(restartLocationUpdates)
                                                       userInfo:nil
                                                        repeats:NO];

    //Will only stop the locationManager after 10 seconds, so that we can get some accurate locations
    //The location manager will only operate for 10 seconds to save battery
    // 2 sec
    if (self.shareModel.delay10Seconds) {
        [self.shareModel.delay10Seconds invalidate];
        self.shareModel.delay10Seconds = nil;
    }

    self.shareModel.delay10Seconds = [NSTimer scheduledTimerWithTimeInterval:2 target:self
                                                selector:@selector(stopLocationDelayBy10Seconds)
                                                userInfo:nil
                                                 repeats:NO];

}

- (void) restartLocationUpdates
{
    NSLog(@"restartLocationUpdates");

    if (self.shareModel.timer) {
        [self.shareModel.timer invalidate];
        self.shareModel.timer = nil;
    }

    CLLocationManager *locationManager = [LocationTracker sharedLocationManager];
    locationManager.delegate = self;
    locationManager.desiredAccuracy = kCLLocationAccuracyBest;
    locationManager.distanceFilter = 10; //meters
    locationManager.activityType = CLActivityTypeAutomotiveNavigation;
    locationManager.pausesLocationUpdatesAutomatically = NO;

    if(IS_OS_8_OR_LATER) {
        [locationManager requestAlwaysAuthorization];
    }
    [locationManager startUpdatingLocation];
}

- (void)stopLocationTracking:(NSManagedObjectContext *)managedObjectContext       {
    NSLog(@"stopLocationTracking");
    CLSLog(@"stopLocationTracking");

    CLSLog(@"set managedObjectContext %@", managedObjectContext);
    self.managedObjectContext = managedObjectContext;

    if (self.shareModel.timer) {
        [self.shareModel.timer invalidate];
        self.shareModel.timer = nil;
    }

    CLLocationManager *locationManager = [LocationTracker sharedLocationManager];
    [locationManager stopUpdatingLocation];

    [self saveRun];
    [self sendRun];
}

3 个答案:

答案 0 :(得分:1)

谢谢大家的回复。与Øyvind Hauge所说的相反,可以使用iBeacons唤醒你的应用程序被杀死/暂停/终止。不幸的是,将背景位置模式添加到您的plist不会像其他人建议的那样启用无限期的位置更新;我只能用这种方法获得3分钟的执行时间。

我实际上在StackOverflow article中找到了我的问题的解决方案。解决方案是只向您的应用代表添加几行代码 - 您需要启动另一个位置管理器来监控重要的位置更新。以下是在将didFinishLaunchingWithOptions声明为属性后,我在AppDelegate.m文件中添加到anotherLocationManager方法的代码行...

self.anotherLocationManager = [[CLLocationManager alloc] init];
self.anotherLocationManager.delegate = self;
[self.anotherLocationManager startMonitoringSignificantLocationChanges];

我从不使用此位置管理器做任何其他操作,我只是让它永久在后台运行,并且由于某种原因,这可以通过常规调用[locationManager startUpdatingLocation]实现无限期的位置更新。 3分钟后,我不再神秘地停止位置更新。这个解决方案似乎很奇怪,但实施起来非常简单,希望这会帮助处理同样问题的其他人。

答案 1 :(得分:0)

If you set the location background mode in your plist, you can range beacons and get GPS location updates indefinitely. The key to getting this to work is starting a background thread.

You can see an example of how to do this in this blog post about extending beacon ranging on the background. While the blog post mentions this is limited to 3 minutes, when you add the location background mode to your plist, that time limit goes away.

Understand that you may not get AppStore approval for using this background mode unless Apple appreciates your justification for doing so.

答案 2 :(得分:0)

So in iOS, location updates will work in background indefinitely ONLY if - 1. You have started location updates in foreground AND 2. You have added Background Location in your plist.

In your case, the OS is waking you up in background and as you've said correctly, you only get 10 seconds of execution time before the OS suspends your app. The workaround for this is basically starting a background task, as you have done to get additional 180 seconds of execution time (this number can change based on OS version).

To understand your issue in depth, you need to know that there are only certain events(like geofence/ibeacon/significant location update) which will wake your app in background, let us call them "wakeup" events. Once any of these event occurs, you have a maximum of 180 seconds of background execution time (using background task) after which your app WILL be suspended, unless any of these events is triggered again, after which you need to restart your background task. I'm not sure how your application works exactly, but if you can ensure that you keep getting these "wakeup" events from the OS for the duration for which you need location updates, you can pretty much keep your app awake in background.

Just to add, I've seen a lot of blog posts that claim that keeping a timer and restarting location updates periodically using that timer works, but I have never been able to use it successfully.