应用已经消失,iOS iBeacon /蓝牙连接

我需要的:

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

目前的情况

我正在为父母制作一个应用程序,让他们的孩子帮助他们在重要时间关闭手机。 该应用程序位于Objective-C中,即使在应用程序生命周期之后,它仍需要与蓝牙设备保持持久连接。

我已经尝试了很长时间才能让这个工作起来我已经得到了很多SO海报的帮助,目前我知道我必须在我的设备中使用iBeacon从终止启动(这是我使用它的唯一原因,我如果有另一种方式从终止启动应用程序,很乐意转储它。 为了澄清,我在同一个设备(我已经建立)iBeacon和一个可靠的BT连接这里需要2件事。 我需要此设备连接配对,因为这是从BT设备发送/接收命令的唯一方法。 我发现的是,在didRange didEnterdidRangedidEnter委托方法充其量是不可靠的。 他们并不总是立即开火,他们只开了几次​​而整个事情都死了(我现在知道这个10秒窗口是终止应用程序的预期行为)。 我甚至整天都在插拔电源,不断寻找应用程序恢复生机的任何迹象……

当应用程序打开时,工作正常,但是当应用程序在我的灯塔/蓝牙附近时,我希望它在应用程序内部启动一种临时锁定屏幕。 当应用程序处于前台时,我已经相当好地完成了这一部分。 如果一个孩子试图关闭应用程序或后台,我想通过让我的BT设备一旦终止就启动到后台进行响应(我知道用户界面不会出现,而且没关系,我只需要一系列function即可开启) 。 然后它将连接到蓝牙并从设备接收一些命令。 听起来很简单呃? 事情变得混乱了。

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

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


我的代码

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 ()  @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以交换数据。 请放心,这一切都是可能的,让工作变得棘手并且调试起来比较棘手。

关于蓝牙LE连接的一些提示:

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

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

     UIBackgroundModes  bluetooth-central  

    如果不存在,你绝对不会在后台获得对didDiscoverPeripheral回调。

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

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

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