iOS iBeacon/蓝牙连接,当应用程序失效时 [英] iOS iBeacon / Bluetooth connectivity when app is dead and gone

查看:28
本文介绍了iOS iBeacon/蓝牙连接,当应用程序失效时的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要什么:

启动 iBeacon 委托方法(例如 didDetermineStatedidRangeBeaconsdidEnterRegiondidExitRegion当应用程序已死且设备已插入且在附近时.

A predictable, solid and reliable way of launching iBeacon delegate methods such as didDetermineState, didRangeBeacons, didEnterRegion or didExitRegion when the app is dead and the device is plugged in and nearby.

现状

我正在为父母制作一款应用,让他们的孩子在重要时刻帮助他们关闭手机.该应用程序采用 Objective-C,即使在应用程序生命周期结束后,它也需要保持与蓝牙设备的持久连接.

I am making an app for parents to use for their kids to help them shut down their phones during important times. The app is in Objective-C and it needs to maintain a persistent connection to a bluetooth device even after the life of the application.

我一直在努力让它发挥作用,我得到了很多 S.O. 的帮助.海报,目前我知道我必须在我的设备中使用 iBeacon 才能从终止状态启动(这是我使用它的唯一原因,如果有另一种方法可以从终止状态启动应用程序,我很乐意转储它).为了澄清,我需要在同一个设备(我已经构建)中的两个东西 iBeacon 和一个可靠的 BT 连接.我需要此设备连接配对,因为这是从 BT 设备发送/接收命令的唯一方法.我发现在后台触发的 didRangedidEnter 委托方法充其量是不可靠的.它们并不总是立即启动,它们只启动几次,然后整个过程就会消失(我现在知道这个 10 秒窗口是终止应用程序的预期行为).我什至有一整天的时间插上/拔下它,不断寻找任何迹象表明该应用程序已经恢复正常,但没有任何反应......

I have been trying for a long time to get this to work and I have had help from a lot of S.O. posters and currently I know that I must use iBeacon in my device to launch from terminated (that's the only reason I use it, I would gladly dump it if there was another way to launch an app from terminated). To clarify, I need 2 things here in the same device (which I have already built) iBeacon and a solid BT connection. I need this device connection pairing because this is the only way to send/receive commands from the BT device. What I have discovered is that the didRange or didEnter delegate methods that fire in the background are unreliable at best. They don't always fire right away and they only fire a few times and the whole thing dies (which I now know this 10 second window is expected behaviour from a terminated app). I have even had full entire days where I plug/unplug it constantly looking for any sign that the app has come back to life and nothing happens...

当应用程序打开时,一切正常,但是当应用程序靠近我的信标/蓝牙时,我希望它在应用程序内启动一种临时锁定屏幕.当应用程序处于前台时,我已经很好地完成了这部分工作.如果一个孩子试图关闭应用程序或后台,我想通过让我的 BT 设备在终止后启动到后台来做出响应(我知道 UI 不会出现,这很好,我只需要一系列功能来触发).然后它将连接到蓝牙并从设备接收一些命令.听起来很简单吧?事情变得混乱了.

When the app is open, things work fine, however when the app is nearby to my beacon/bluetooth I want it to launch a sort of makeshift lock screen inside the app. I am already doing this part fairly well when the app is in the foreground. If a kid tries to close the app or background it I want to respond by having my BT device launch into the background once it's terminated (I know the UI won't come up and that's fine I just need a series of functions to fire). It will then connect to bluetooth and receive some commands from the device. Sounds simple enough eh? Here's were things get messy.

一些上下文:我在 info.plist 中为蓝牙和信标添加了所有背景模式,当应用程序处于前台时一切正常......

如果在范围内检测到 iBeacon,那么我想使用 10 秒窗口通过 BT 配对连接到我的盒子并通过命令发送.到目前为止,这很奇怪……当应用程序终止时,iBeacon 测距功能不会触发,它们只会在最奇怪的用例中触发.我似乎无法预测他们什么时候会开火.

If the iBeacon is detected in range, I then want to use that 10 second window to connect via BT pairing to my box and send through a command. So far, this is wonky... The iBeacon ranging functions do not fire when the app is terminated they only fire on the strangest of use cases. I cannot seem to predict when they are going to fire.

ibeaconManager.h

@interface IbeaconManager : NSObject

@property (nonatomic) BOOL waitingForDeviceCommand;
@property (nonatomic, strong) NSTimer *deviceCommandTimer;

+ (IbeaconManager *) sharedInstance;
- (void)startMonitoring;
- (void)stopMonitoring;
- (void)timedLock:(NSTimer *)timer;

@end

ibeaconManager.m

@interface IbeaconManager () <CLLocationManagerDelegate>

@property (nonatomic, strong) BluetoothMgr *btManager;
@property (nonatomic, strong) CLLocationManager *locationManager;
@property (nonatomic, strong) CLBeaconRegion *region;
@property (nonatomic) BOOL connectedToDevice;

@end

NSString *const PROXMITY_UUID = @"00000000-1111-2222-3333-AAAAAAAAAAAA";
NSString *const BEACON_REGION = @"MY_CUSTOM_REGION";

const int REGION_MINOR = 0;
const int REGION_MAJOR = 0;



@implementation IbeaconManager
+ (IbeaconManager *) sharedInstance {
    static IbeaconManager *_sharedInstance = nil;
    static dispatch_once_t oncePredicate;

    dispatch_once(&oncePredicate, ^{
        _sharedInstance = [[IbeaconManager alloc] init];
    });

    return _sharedInstance;

}


- (id)init {
    self = [super init];

    if(self) {
        self.locationManager = [[CLLocationManager alloc] init];
        self.locationManager.delegate = self;
        [self.locationManager requestAlwaysAuthorization];
        self.connectedToDevice = NO;
        self.waitingForDeviceCommand = NO;

        self.region = [[CLBeaconRegion alloc] initWithProximityUUID:[[NSUUID alloc] initWithUUIDString:PROXMITY_UUID]
                                                              major:REGION_MAJOR
                                                              minor:REGION_MINOR
                                                         identifier:BEACON_REGION];

        self.region.notifyEntryStateOnDisplay = YES;
        self.region.notifyOnEntry = YES;
        self.region.notifyOnExit = YES;
    }

    return self;
}


- (void)startMonitoring {
    if(self.region != nil) {
        NSLog(@"**** started monitoring with beacon region **** : %@", self.region);

        [self.locationManager startMonitoringForRegion:self.region];
        [self.locationManager startRangingBeaconsInRegion:self.region];
    }
}


- (void)stopMonitoring {
    NSLog(@"*** stopMonitoring");

    if(self.region != nil) {
        [self.locationManager stopMonitoringForRegion:self.region];
        [self.locationManager stopRangingBeaconsInRegion:self.region];
    }
}


- (void)triggerCustomLocalNotification:(NSString *)alertBody {
    UILocalNotification *localNotification = [[UILocalNotification alloc] init];
    localNotification.alertBody = alertBody;
    [[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];
}




#pragma mark - CLLocationManager delegate methods

- (void)locationManager:(CLLocationManager *)manager
      didDetermineState:(CLRegionState)state
              forRegion:(CLRegion *)region {

    NSLog(@"did determine state STATE: %ld", (long)state);
    NSLog(@"did determine state region: %@", region);

    [self triggerCustomLocalNotification:@"made it into the did determine state method"];

    NSUInteger appState = [[UIApplication sharedApplication] applicationState];
    NSLog(@"application's current state: %ld", (long)appState);

    if(appState == UIApplicationStateBackground || appState == UIApplicationStateInactive) {
        NSString *notificationText = @"Did range beacons... The app is";
        NSString *notificationStateText = (appState == UIApplicationStateInactive) ? @"inactive" : @"backgrounded";
        NSString *notificationString = [NSString stringWithFormat:@"%@ %@", notificationText, notificationStateText];

        NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init];
        bool isAppLockScreenShowing = [userDefaults boolForKey:@"isAppLockScreenShowing"];

        if(!isAppLockScreenShowing && !self.waitingForDeviceCommand) {
            self.waitingForDeviceCommand = YES;

            self.deviceCommandTimer = [NSTimer scheduledTimerWithTimeInterval:2.0
                                                                       target:self
                                                                     selector:@selector(timedLock:)
                                                                     userInfo:notificationString
                                                                      repeats:NO];
        }

    } else if(appState == UIApplicationStateActive) {

        if(region != nil) {
            if(state == CLRegionStateInside) {
                NSLog(@"locationManager didDetermineState INSIDE for %@", region.identifier);
                [self triggerCustomLocalNotification:@"locationManager didDetermineState INSIDE"];

            } else if(state == CLRegionStateOutside) {
                NSLog(@"locationManager didDetermineState OUTSIDE for %@", region.identifier);
                [self triggerCustomLocalNotification:@"locationManager didDetermineState OUTSIDE"];

            } else {
                NSLog(@"locationManager didDetermineState OTHER for %@", region.identifier);
            }
        }

        //Upon re-entry, remove timer
        if(self.deviceCommandTimer != nil) {
            [self.deviceCommandTimer invalidate];
            self.deviceCommandTimer = nil;
        }
    }
}


- (void)locationManager:(CLLocationManager *)manager
        didRangeBeacons:(NSArray *)beacons
               inRegion:(CLBeaconRegion *)region {

    NSLog(@"Did range some beacons");

    NSUInteger state = [[UIApplication sharedApplication] applicationState];
    NSString *notificationStateText = (state == UIApplicationStateInactive) ? @"inactive" : @"backgrounded";
    NSLog(@"application's current state: %ld", (long)state);

    [self triggerCustomLocalNotification:[NSString stringWithFormat:@"ranged beacons, application's current state: %@", notificationStateText]];

    if(state == UIApplicationStateBackground || state == UIApplicationStateInactive) {
        NSString *notificationText = @"Did range beacons... The app is";
        NSString *notificationString = [NSString stringWithFormat:@"%@ %@", notificationText, notificationStateText];

        NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init];
        bool isAppLockScreenShowing = [userDefaults boolForKey:@"isAppLockScreenShowing"];

        if(!isAppLockScreenShowing && !self.waitingForDeviceCommand) {
            self.waitingForDeviceCommand = YES;

            self.deviceCommandTimer = [NSTimer scheduledTimerWithTimeInterval:2.0
                                                                       target:self
                                                                     selector:@selector(timedLock:)
                                                                     userInfo:notificationString
                                                                      repeats:NO];
        }

    } else if(state == UIApplicationStateActive) {
        if(self.deviceCommandTimer != nil) {
            [self.deviceCommandTimer invalidate];
            self.deviceCommandTimer = nil;
        }
    }
}


- (void)timedLock:(NSTimer *)timer {
    self.btManager = [BluetoothMgr sharedInstance];

    [self.btManager sendCodeToBTDevice:@"magiccommand"
                        characteristic:self.btManager.lockCharacteristic];

    [self triggerCustomLocalNotification:[timer userInfo]];

    self.waitingForDeviceCommand = NO;
}


- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
    NSLog(@"Did Enter Region: %@", region);
    [self triggerCustomLocalNotification:[NSString stringWithFormat:@"Did enter region: %@", region.identifier]];
}


- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
    NSLog(@"Did Exit Region: %@", region);
    [self triggerCustomLocalNotification:[NSString stringWithFormat:@"Did exit region: %@", region.identifier]];

    //Upon exit, remove timer
    if(self.deviceCommandTimer != nil) {
        [self.deviceCommandTimer invalidate];
        self.deviceCommandTimer = nil;
    }
}


- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error {
    NSLog(@"monitoringDidFailForRegion EPIC FAIL for region %@ withError %@", region.identifier, error.localizedDescription);
}




@end

推荐答案

我为 iOS 构建了一个类似的系统,它使用 iBeacon 传输在后台唤醒,然后连接蓝牙 LE 交换数据.请放心,这一切都是可能的,只是工作起来很棘手,调试起来也很棘手.

I have built a similar system for iOS that uses iBeacon transmissions to wake up in the background and then connect to bluetooth LE to exchange data. Rest assured that this is all possible, it's just tricky to get working and trickier to debug.

使用蓝牙 LE 连接执行此操作的一些提示:

A few tips on doing this with Bluetooth LE connecting:

  • 当应用程序被杀死时,信标测距功能不会触发除非您还监视信标并获得 didEnterdidExit> 转换,这将按照您的描述将应用程序重新启动到后台 10 秒.同样,这只会在您从区域内转换到区域外时发生,反之亦然.这很难测试,因为您可能没有意识到 CoreLocation 在您杀死应用程序时认为您在区域内",但您不会收到用于检测信标的唤醒事件.

  • Beacon ranging functions will not fire when the app is killed unless you also monitor for the beacons and get a didEnter or didExit transition, which will re-launch the app into the background for 10 secs as you describe. Again, this will only happen if you transition from in region to out of region or vice versa. This is tricky to test, because you may not realize CoreLocation thinks you are "in region" when you kill the app, but you won't get a wakeup event for detecting the beacon.

为了在后台获取蓝牙事件,您需要确保您的 Info.plist 声明:

In order to get bluetooth events in the background, you need to make sure your Info.plist declares this:

<key>UIBackgroundModes</key>
<array>
   <string>bluetooth-central</string>
</array>

如果不存在,您绝对不会在后台收到对 didDiscoverPeripheral 的回调.

If that is not present, you absolutely will not get callbacks to didDiscoverPeripheral in the background.

您需要在应用程序启动时开始扫描蓝牙,并在收到回调到 func centralManager(_ central: CBCentralManager, didDiscoverperipheral: CBPeripheral, AdvertisingData: [String : Any], rssi RSSI: NSNumber)

You will need to start scanning for bluetooth when your app starts up, and connect when you get a callback to func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber)

从上面保存一个 peripheral 实例的副本,因为您只能在后台获得 一个 回调,以便从每个独特的蓝牙设备中进行发现.如果您的连接失败,您可以使用相同的 peripheral 对象实例重试.

Save off a copy of the peripheral instance from above because you only get one callback in the background for discovery from each unique bluetooth device. If your connection fails, you can retry with the same peripheral object instance.

为了调试从终止状态重新启动,我添加了许多 NSLog 语句(我添加了在代码中打开和关闭它们的功能),然后查找这些在 XCode 的 Windows -> Devices -> My iPhone 面板中,您可以在其中展开屏幕底部的小箭头以显示设备上所有应用程序的日志.如果您的应用从终止状态重新启动,您绝对会在此处看到该应用的日志.

In order to debug re-launching from a killed state, I add lots of NSLog statements (I add the ability to turn them on and off in code) and then look for these in XCode's Windows -> Devices -> My iPhone panel, where you can expand the little arrow at the bottom of the screen to show the logs for all apps on the device. You absolutely will see logs here for your app if it is relaunched from a killed state.

这篇关于iOS iBeacon/蓝牙连接,当应用程序失效时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆