首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >iOS iBeacon /蓝牙连接,当应用程序死后消失

iOS iBeacon /蓝牙连接,当应用程序死后消失
EN

Stack Overflow用户
提问于 2017-07-17 13:32:01
回答 1查看 2.1K关注 0票数 3

我需要什么:

这是一种可预测的、可靠的方法,在应用程序死了,设备被插入和附近的时候,可以启动iBeacon委托方法,比如didDetermineStatedidRangeBeaconsdidEnterRegiondidExitRegion

现状

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

很长一段时间以来,我一直在努力让它发挥作用,我得到了很多S.O.海报的帮助,目前我知道我必须在我的设备中使用iBeacon才能从终端启动(这是我使用它的唯一原因,如果有其他方法从终止中启动应用程序,我会很高兴地把它扔掉)。为了澄清,我需要两样东西在同一个设备中(我已经建立了) iBeacon和一个坚实的BT连接。我需要这个设备连接配对,因为这是从BT设备发送/接收命令的唯一方法。我发现,在后台触发的didRangedidEnter委托方法充其量是不可靠的。他们不总是立即开火,他们只发射了几次,整个东西就死了(我现在知道这个10秒的窗口是一个终止的应用程序的预期行为)。我甚至有整整一天的时间,我不断地插入/拔掉它,寻找任何迹象表明该应用程序已经恢复活力,什么事情也没有发生。

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

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

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

我的密码

ibeaconManager.h

代码语言:javascript
运行
复制
@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

代码语言:javascript
运行
复制
@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
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-07-17 16:42:41

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

关于使用蓝牙LE连接实现此操作的几个提示:

  • 当应用程序被杀死时,信标测距功能不会启动,除非你还监视信标,并得到一个didEnterdidExit转换,这将重新启动应用程序到后台10秒钟,正如你所描述的。同样,只有当您从区域内过渡到区域外时,这才会发生,反之亦然。这是很难测试的,因为你可能没有意识到当你杀死应用程序的时候,CoreLocation会认为你在“区域”,但是你不会得到一个唤醒事件来检测信标。
  • 为了在后台获取蓝牙事件,需要确保Info.plist声明如下: UIBackgroundModes蓝牙中心 如果不存在这种情况,则绝对不会在后台得到对didDiscoverPeripheral的回调。
  • 您需要在应用程序启动时开始扫描蓝牙,并在回调到func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber)时进行连接。
  • 从上面保存一个peripheral实例的副本,因为您在后台只得到一个回调,以便从每个唯一的蓝牙设备中发现。如果连接失败,可以使用相同的peripheral对象实例重试。
  • 为了调试从死机状态重新启动,我添加了许多NSLog语句(我增加了在代码中打开和关闭它们的能力),然后在XCode的Windows -> device -> My iPhone面板中查找这些语句,在该面板中,您可以展开屏幕底部的小箭头,以显示设备上所有应用程序的日志。如果您的应用程序是从死机状态重新启动的,您肯定会在这里看到日志。
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/45145680

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档