应用已经消失,iOS iBeacon /蓝牙连接 [英] iOS iBeacon / Bluetooth connectivity when app is dead and gone

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

问题描述

我需要什么:

启动iBeacon委托方法的可预测,可靠且可靠的方法,例如 didDetermineState didRangeBeacons didEnterRegion didExitRegion 当应用程序死机且设备已插入附近时。

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.

我已经尝试了很长时间让这个工作,我得到了很多SO的帮助海报,目前我知道我必须在我的设备中使用iBeacon从终止启动(这是我使用它的唯一原因,如果有其他方式从终止启动应用程序,我很乐意转储它)。为了澄清,我在同一个设备(我已经建立)iBeacon和一个可靠的BT连接这里需要2件事。我需要此设备连接配对,因为这是从BT设备发送/接收命令的唯一方法。我发现的是 didRange didEnter 在后台触发的委托方法充其量是不可靠的。他们并不总是立即开火,他们只开了几次​​而整个事情都死了(我现在知道这个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设备一旦终止就启动到后台进行响应(我知道用户界面不会出现,而且没关系,我只需要一系列功能即可开启) 。然后它将连接到蓝牙并从设备接收一些命令。听起来很简单呃?事情变得一团糟。

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:


  • 当应用程序被杀时,信标测距函数不会触发除非你也监视信标并获得 didEnter didExit 转换,这将重新启动应用程序到后台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,didDiscover peripheral:CBPeripheral,advertisementData:[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)

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

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中查找这些语句 - >设备 - >我的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天全站免登陆