简体   繁体   中英

Beacon Ranging in Background on iOS

I understand the difference between monitoring and ranging, and I understand the limitation of iOS in that beacon ranging can only happen in the foreground or in the background when entering and exiting regions as explained here ( http://developer.radiusnetworks.com/2013/11/13/ibeacon-monitoring-in-the-background-and-foreground.html ). But I'm trying to figure out how to solve a common scenario.

If I had a bunch of beacons installed in a department store, how am I supposed to detect when a person moves within range of those beacons? With the way it currently works, the app will get an event when the user enters the store ( didEnterRegion ) because the collection of all beacons acts as one big region. But there's no way to know that a user is moving between different sections of the store unless the beacons are placed far enough for the user to exit and enter the region again which is probably not practical.

The reason I want to range for beacons in the background is that I might need to know that a user is at a specific section/product in the store to display specific offers/information (through a notification) for that section without needing the user to have the app open.

This seems to me like a very common scenario for malls and museums, etc... I'm wondering how other developers solved this or whether there's another way of achieving what I want.

I didn't include code snippets here because the issue is not with the code, it's just a conceptual issue. If any clarification or code is needed I can add that too.

Thanks

The biggest part of the answer is the technique documented by my colleague @csexton in another answer to this question.

In order to solve the second problem of only getting 10 seconds of ranging time after a transition, you can request additional time to keep ranging. iOS allows you to continue ranging in the background for up to 180 seconds. This requires no background modes and no special permission from the AppStore.

Here's how you set that up:

- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
    if (_inBackground) {
        [self extendBackgroundRunningTime];
    }
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    [self logString: [NSString stringWithFormat:@"applicationDidEnterBackground"]];
    [self extendBackgroundRunningTime];
    _inBackground = YES;
}


- (void)extendBackgroundRunningTime {
    if (_backgroundTask != UIBackgroundTaskInvalid) {
        // if we are in here, that means the background task is already running.
        // don't restart it.
        return;
    }
    NSLog(@"Attempting to extend background running time");

    __block Boolean self_terminate = YES;

    _backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"DummyTask" expirationHandler:^{
        NSLog(@"Background task expired by iOS");
        if (self_terminate) {
            [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
            _backgroundTask = UIBackgroundTaskInvalid;
        }
    }];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"Background task started");

        while (true) {
            NSLog(@"background time remaining: %8.2f", [UIApplication sharedApplication].backgroundTimeRemaining);
            [NSThread sleepForTimeInterval:1];
        }

    });
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    [self logString: [NSString stringWithFormat:@"applicationDidBecomeActive"]];
    _inBackground = NO;
}

Getting 180 seconds to range in the background is no silver bullet, but it solves many use cases that 10 seconds does not.

You can read a full writeup on how this works, along with test results here: https://github.com/RadiusNetworks/ibeacon-background-demo/tree/background-task

I would probably model this by breaking up the store into multiple regions. How they would be modeled would be based on the use-cases you have for when a notification is triggered.

I would do this by giving them all the same UUID, but different Major values. Then use the minor values to differentiate between stores. This way as they move from region the app will register a didEnter event.

You can have about 20 regions registered, so I would be careful when grouping beacons together.

For Example:

  • UUID: 754A5D70-C59E-4E39-AA56-ED646903EF5B Major: 1 → Entrance
  • UUID: 754A5D70-C59E-4E39-AA56-ED646903EF5B Major: 2 → Cash Registers
  • UUID: 754A5D70-C59E-4E39-AA56-ED646903EF5B Major: 3 → Clothing
  • UUID: 754A5D70-C59E-4E39-AA56-ED646903EF5B Major: 4 → Housewares
  • ...

Then as the users of the app move through the store you can trigger actions upon crossing the boundaries of the regions.

This way you can:

  1. Get the enter and exit callback
  2. Start ranging to find out the Minor values (which could be per store or unique for each beacon)
  3. Send the local notification

What you need to do is make sure all of your beacons have the same UUID. Then, register a single CLBeaconRegion for monitoring that specifies ONLY that UUID. Do not include a major or minor value. This will cause didEnterRegion: to be called when ANY beacon matching that UUID is entered (major and minor values are wildcarded). However, the CLBeaconRegion returned here will only have the UUID, and no major/minor, just like how you registered it so you don't know exactly which beacon fired. To figure out exactly which beacon your device is seeing, when didEnterRegion: is called, tell the location manager to startRangingBeaconsInRegion: with the entered region. The location manager will then call back to you with didRangeBeacons: passing an array of CLBeacons. These CLBeacons will know their major and minor values, from which you can determine exactly which part of the store the user is in (because you know where the beacon with that major/minor is deployed). This can all be done while the app is in the background.

This way you are only registering a single CLBeaconRegion, yet you can still interact with any beacon matching the UUID that you've registered with.

I've used this method to do successful deployments of 80+ beacons in one area, which all successfully fired in the foreground and background. You do not need the user to open the app to accomplish this.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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