简体   繁体   English

iBeacon触发的iOS 8背景位置更新

[英]iOS 8 Background Location Update triggered by iBeacon

I am trying to make an app that can be trigged by an iBeacon to wake up (from being killed/suspended/terminated) to record second-by-second GPS information. 我正在尝试制作一个可由iBeacon触发以唤醒(从被杀死/挂起/终止)的应用程序,以记录每秒GPS信息。 The GPS recording should then stop when the phone gets out of range of the beacon. 当手机超出信标范围时,GPS记录应停止。 I have successfully gotten my app to recognize the didEnterRegion and didExitRegion methods when it comes in and out of range of the iBeacon. 我已经成功获得了我的应用程序,可以识别出进入和超出iBeacon范围的didEnterRegion和didExitRegion方法。 In the didEnterRegion method I want to basically say something like [locationManager startUpdatingLocation] so that I can start tracking the user's location. 在didEnterRegion方法中,我基本上想说些类似[locationManager startUpdatingLocation]以便可以开始跟踪用户的位置。 However, when I try to add this line of code, the location updates stop after about 10 seconds. 但是,当我尝试添加此行代码时,位置更新将在大约10秒钟后停止。

Later I found an article about background location updates that came with this Github project . 后来我找到了这个Github 项目附带的关于背景位置更新的文章 I added the BackgroundTaskManager, LocationShareModel, and LocationTracker files to my project. 我将BackgroundTaskManager,LocationShareModel和LocationTracker文件添加到我的项目中。 Basically, the idea behind this solution is to continually restart the location manager so it doesn't have the chance for the background task to expire and stop sending updates. 基本上,此解决方案背后的想法是连续重新启动位置管理器,这样后台任务就没有机会过期并停止发送更新。 However, even with this solution, I only get location updates for a little over 3 minutes. 但是,即使采用这种解决方案,我也只能获得3分钟多一点的位置更新信息。

I have the "Location Updates" and "Use Bluetooth LE accessories" background modes enables. 我启用了“位置更新”和“使用蓝牙LE配件”背景模式。 The "Background Fetch" (Background App Refresh) is not enabled, in accordance with this quote from Apple: "In iOS 8 and later, disabling the Background App Refresh setting for the current app or for all apps does not prevent the delivery of location events in the background." 根据Apple的以下说法,未启用“后台获取”(后台应用刷新):“在iOS 8及更高版本中,为当前应用或所有应用禁用后台应用刷新设置不会阻止位置传递事件。” My app requests "Always" authorization for location updates. 我的应用程序请求“始终”授权进行位置更新。

I cannot figure out how to solve this issue, despite reviewing seemingly endless StackOverflow articles and tutorials. 尽管回顾了无休止的StackOverflow文章和教程,但我仍然不知道如何解决此问题。 I am testing it on an iPhone 5S running iOS 8.3.0. 我正在运行iOS 8.3.0的iPhone 5S上对其进行测试。 Any insight would be appreciated. 任何见识将不胜感激。 See code excerpts below. 请参阅下面的代码摘录。

In AppDelegate.m : 在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");
}

In LocationTracker.m (from tutorial cited above, change 60 sec and 10 sec time intervals to 5 sec and 2 sec). 在LocationTracker.m中(从上面引用的教程中,将60秒和10秒的时间间隔更改为5秒和2秒)。 Basically these are the startLocationTracking, didUpdateLocations, and stopLocationTracking methods. 基本上,这些是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];
}

Thank you all for your responses. 谢谢大家的答复。 It is possible to wake your app up from being killed/suspended/terminated using iBeacons, contrary to what Øyvind Hauge said. 它可以被杀死/暂停/使用iBeacons,相反的是终止唤醒你的应用程式上岛之风豪格说。 And unfortunately, adding the background location mode to your plist does not enable indefinite location updates, as others suggested; 不幸的是,将后台位置模式添加到您的plist并不会启用不确定的位置更新,正如其他人所建议的那样。 I was only ever able to get 3 minutes of execution using that method. 使用该方法,我只能执行3分钟。

I actually found the solution to my question in this StackOverflow article . 实际上,我在这篇StackOverflow 文章中找到了解决我问题的方法。 The solution is to add just a few lines of code to your app delegate - you need to start another location manager that is monitoring for significant location updates. 解决方案是仅向您的应用程序代理添加几行代码-您需要启动另一个监视重要位置更新的位置管理器。 Here are the lines of code that I added to my didFinishLaunchingWithOptions method in my AppDelegate.m file after declaring anotherLocationManager as a property... 这是在将anotherLocationManager声明为属性之后,添加到AppDelegate.m文件中的didFinishLaunchingWithOptions方法中的代码行。

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

I never do anything else using this location manager, I just leave it perpetually running in the background, and for some reason this enables indefinite location updates from a regular call to [locationManager startUpdatingLocation] . 我从来没有使用此位置管理器做任何其他事情,只是让它永久地在后台运行,并且由于某种原因,这使得可以从对[locationManager startUpdatingLocation]的常规调用中进行不确定的位置更新。 I am no longer having the location updates mysteriously stop after 3 minutes. 3分钟后,我不再神秘地停止位置更新。 It seems very strange that this was the solution, but it was pretty simple to implement, and hopefully this will help others who are dealing with the same problem. 这是解决方案似乎很奇怪,但是实现起来非常简单,希望这可以帮助其他处理相同问题的人。

If you set the location background mode in your plist, you can range beacons and get GPS location updates indefinitely. 如果在plist中设置位置背景模式,则可以定位信标并无限期获取GPS位置更新。 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. 尽管博客文章中提到的时间限制为3分钟,但是当您将位置背景模式添加到plist时,该时间限制就消失了。

Understand that you may not get AppStore approval for using this background mode unless Apple appreciates your justification for doing so. 请理解,除非Apple感谢您这样做的合理性,否则您可能不会获得使用此后台模式的AppStore批准。

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. 因此,在iOS中,只有在以下情况下,位置更新才会无限期地在后台运行:1.您已在前台启动位置更新,并且2.已在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. 就您而言,操作系统会在后台唤醒您,正如您已正确说过的那样,在操作系统挂起您的应用程序之前,您只有10秒的执行时间。 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). 解决此问题的方法基本上是开始执行后台任务,因为您已经获得了额外的180秒执行时间(此数字可以根据OS版本而改变)。

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. 要深入了解您的问题,您需要知道只有某些事件(例如地理围栏/ ibeacon /重要位置更新)会在后台唤醒您的应用程序,因此我们将其称为“唤醒”事件。 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. 一旦发生这些事件中的任何一个,您将有最多180秒的后台执行时间(使用后台任务),在此之后您的应用将被挂起,除非再次触发了这些事件中的任何一个,之后您需要重新启动后台任务。 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. 只是,我看到很多博客文章声称保留计时器并使用该计时器定期重新启动位置更新是可行的,但我从未成功使用过它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM