BLE在背景上扫描随机冻结

更新14/08 – 3 – 找到真正的解决方案:

您可以在下面的答案中查看解决方案!

更新16/06 – 2 – 可能是解决方案:

正如桑迪·查普曼(Sandy Chapman)在答案中的评论中所提出的,我现在可以使用以下方法在扫描开始时检索外围设备:

- (NSArray<CBPeripheral *> * nonnull)retrievePeripheralsWithIdentifiers:(NSArray<NSUUID *> * nonnull)identifiers

实际上,当我需要它时,让我的外设在扫描开始并启动一个连接(即使它不在范围内),实际上也是这样做的. iOS将保持活着,直到找到我正在寻找的设备.

还请注意,如果在使用蓝牙的发行版版本的背景中有其他应用程序,iOS 8.x中可能会出现一个错误,该错误会使扫描中的调试版本保持一个应用程序(消除我的回调).

更新16/06:

所以我检查了retrievePeripheralsWithServices如果任何设备在我开始扫描时被连接.
当我得到错误,我启动应用程序,我做的第一件事

- (void) applicationDidBecomeActive:(UIApplication *)application

是检查返回的数组的大小.它总是0,每次我得到的错误.如果我的设备在当前运行中没有提前进行任何连接,也可能会发生该错误.我也可以看到我的设备广告,并触发一个命令与第二个设备,而我得到与其他设备的错误.

更新10/06:

>我离开了我的应用程序整个晚上运行,以检查是否没有任何内存泄漏或大量的资源使用,这是在后台运行大约12-14个小时后的结果.内存/ CPU使用情况与我离开的时候完全一样.它导致我认为我的应用程序没有任何泄漏可能导致iOS关闭它以获取内存/ CPU使用率.

更新08/06:

>请注意,这不是一个广告问题,因为我们的BLE设备始终如一,而且我们使用了我们可以发现的最强大的BLE电子卡.
>在背景中iOS检测时序也不是问题.我等了很长一段时间(20〜30分钟)才确定不是这个问题.

原题

我正在处理一个处理与BLE设备通信的应用程序.我的一个约束是,只有当我必须发送命令或读取数据时,我才能连接到这个设备.我必须尽快断开连接,让其他潜在用户做同样的事情.

该应用程序的其中一项功能如下:

>用户可以在应用程序在后台启用自动命令.如果在10分钟内没有检测到设备,则会触发此自动命令.
>我的应用程序扫描,直到找到我的BLE设备.
>为了保持清醒,当我需要它,我每次重新启动扫描,因为CBCentralManagerScanOptionAllowDuplicatesKey选项无知.
>当被检测到时,我正在检查上次检测是否超过10分钟前.如果是这样,我连接到设备,然后写入与我需要的服务相对应的特性.

目标是在用户进入范围时触发此设备.超过几个小时可能会发生几分钟,这取决于我的用户习惯.

一切都以这种方式工作正常,但有时候(似乎发生在随机的时间),扫描类型“冻结”.我的过程做得很好,但是经过几次,我看到我的应用扫描,但是我的didDiscoverPeripheral:即使我的测试设备正好在我的BLE设备之前,也不会调用回叫.有时可能需要一段时间来检测它,但在这里,几分钟之后什么都不会发生.

我在想,iOS可能已经杀死了我的应用程序来回复内存,但是当我关闭蓝牙时,centralManagerDidUpdateState:被称为正确的方法.如果我的应用程序死亡,那应该是不对的?
如果我打开我的应用程序,扫描将重新启动,它将恢复生机.
我还检查了iOS在180秒的活动后没有关闭我的应用程序,但事实并非如此,因为在这段时间之后它运行良好.

我设置了我的.plist来设置正确的设置(UIBackgroundModes中的蓝牙中心).管理所有BLE处理的课程存储在我的AppDelegate中,作为一个可以通过我所有应用程序访问的单身人士.我也测试了切换我创建此对象的位置.目前我正在应用程序中创建它:didFinishLaunchingWithOptions:method.我试图将它放在我的AppDelegate init中:但是如果我这样做,我在背景中的扫描失败.

我不知道我的代码的哪一部分可以告诉你帮助你更好地了解我的过程.这是一些可能有帮助的示例.请注意,为了访问我的AppDelegate,“AT_appDelegate”是一个maccro.

// Init of my DeviceManager class that handles all BLE processing
- (id) init {
   self = [super init];

   // Flags creation
   self.autoConnectTriggered = NO;
   self.isDeviceReady = NO;
   self.connectionUncomplete = NO;
   self.currentCommand = NONE;
   self.currentCommand_index = 0;

   self.signalOkDetectionCount = 0; // Helps to find out if device is at a good range or too far
   self.connectionFailedCount = 0;  // Helps in a "try again" process if a command fails

   self.main_uuid = [CBUUID UUIDWithString:MAINSERVICE_UUID];
   self.peripheralsRetainer = [[NSMutableArray alloc] init];
   self.lastDeviceDetection = nil;

   // Ble items creation
   dispatch_queue_t queue = dispatch_queue_create("com.onset.corebluetooth.queue",DISPATCH_QUEUE_SERIAL);
   self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:queue];

[self startScanning];
return self;
}

   // The way i start the scan
- (void) startScanning {

   if (!self.isScanning && self.centralManager.state == CBCentralManagerStatePoweredOn) {

    CLS_LOG(@"### Start scanning ###");
    self.isScanning = YES;

    NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:!self.isBackground] forKey:CBCentralManagerScanOptionAllowDuplicatesKey];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{

    [self.centralManager scanForPeripheralsWithServices:@[self.main_uuid] options:options];

    });
  }
  }

  // The way i stop and restart the scan after i've found our device. Contains    some of foreground (UI update) process that you can ignore
  - (void) stopScanningAndRestart: (BOOL) restart {

  CLS_LOG(@"### Scanning terminated ###");
  if (self.isScanning) {

    self.isScanning = NO;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,^{

    [self.centralManager stopScan];
  });

  // Avoid clearing the connection when waiting for notification (remote + learning)

     if (!self.isWaitingNotifiy && !self.isSynchronizing && self.currentCommand == NONE ) {
        // If no device found during scan,update view

        if (self.deviceToReach == nil && !self.isBackground) {

            // Check if any connected devices last
            if (![self isDeviceStillConnected]) {
               CLS_LOG(@"--- Device unreachable for view ---");

            } else {

                self.isDeviceInRange = YES;
                self.deviceToReach = AT_appDelegate.user.device.blePeripheral;
            }

            [self.delegate performSelectorOnMainThread:@selector(updateView) withObject:nil waitUntilDone:YES];       

        }

        // Reset var
        self.deviceToReach = nil;
        self.isDeviceInRange = NO;
        self.signalOkDetectionCount = 0;

        // Check if autotrigger needs to be done again - If time interval is higher enough,// reset autoConnectTriggered to NO. If user has been away for <AUTOTRIGGER_INTERVAL>
        // from the device,it will trigger again next time it will be detected.

        if ([[NSDate date] timeIntervalSinceReferenceDate] - [self.lastDeviceDetection timeIntervalSinceReferenceDate] > AUTOTRIGGER_INTERVAL) {

            CLS_LOG(@"### Auto trigger is enabled ###");
            self.autoConnectTriggered = NO;
        }
    }
}


   if (restart) {
    [self startScanning];
   }
  }

  // Here is my detection process,the flag "isInBackground" is set up each    time the app goes background
  - (void) centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {

CLS_LOG(@"### : %@ -- %@",peripheral.name,RSSI);
BOOL deviceAlreadyShown = [AT_appDelegate isDeviceAvailable];

// If current device has no UUID set,check if peripheral is the right one
// with its name,containing his serial number (macaddress) returned by
// the server on remote adding

NSString *p1 = [[[peripheral.name stringByReplacingOccurrencesOfString:@":" withString:@""] stringByReplacingOccurrencesOfString:@"Extel " withString:@""] uppercaseString];

NSString *p2 = [AT_appDelegate.user.device.serial uppercaseString];

if ([p1 isEqualToString:p2]) {
    AT_appDelegate.user.device.scanUUID = peripheral.identifier;
}

// Filter peripheral connection with uuid
if ([AT_appDelegate.user.device.scanUUID isEqual:peripheral.identifier]) {
    if (([RSSI intValue] > REQUIRED_SIGNAL_STRENGTH && [RSSI intValue] < 0) || self.isBackground) {
        self.signalOkDetectionCount++;
        self.deviceToReach = peripheral;
        self.isDeviceInRange = (self.signalOkDetectionCount >= REQUIRED_SIGNAL_OK_DETECTIONS);

        [peripheral setDelegate:self];
        // Reset blePeripheral if daughter board has been switched and there were
        // not enough time for the software to notice connection has been lost.
        // If that was the case,the device.blePeripheral has not been reset to nil,// and might be different than the new peripheral (from the new daugtherboard)

       if (AT_appDelegate.user.device.blePeripheral != nil) {
            if (![AT_appDelegate.user.device.blePeripheral.name isEqualToString:peripheral.name]) {
                AT_appDelegate.user.device.blePeripheral = nil;
            }
        }

        if (self.lastDeviceDetection == nil ||
            ([[NSDate date] timeIntervalSinceReferenceDate] - [self.lastDeviceDetection timeIntervalSinceReferenceDate] > AUTOTRIGGER_INTERVAL)) {
            self.autoConnectTriggered = NO;
        }

        [peripheral readRSSI];
        AT_appDelegate.user.device.blePeripheral = peripheral;
        self.lastDeviceDetection = [NSDate date];

        if (AT_appDelegate.user.device.autoconnect) {
            if (!self.autoConnectTriggered && !self.autoTriggerConnectionLaunched) {
                CLS_LOG(@"--- Perform trigger ! ---");

                self.autoTriggerConnectionLaunched = YES;
                [self executeCommand:W_TRIGGER onDevice:AT_appDelegate.user.device]; // trigger !
                return;
            }
        }
    }

    if (deviceAlreadyShown) {
        [self.delegate performSelectorOnMainThread:@selector(updateView) withObject:nil waitUntilDone:YES];
    }
}

if (self.isBackground && AT_appDelegate.user.device.autoconnect) {
    CLS_LOG(@"### Relaunch scan ###");
    [self stopScanningAndRestart:YES];
}
  }

解决方法

在你的示例代码中,它不像您在CBCentralManager上调用这些方法之一:

– (NSArray< CBPeripheral *> * nonnull)retrieveConnectedPeripheralsWithServices:(NSArray< CBUUID *> * nonnull)serviceUUID

– (NSArray< CBPeripheral *> * nonnull)retrievePeripheralsWithIdentifiers:(NSArray< NSUUID *> * nonnull)标识符

有可能您处于等待系统已连接的didDiscoverPeripheral永远不会来的状态.在实例化CBCentralManager之后,首先检查外围设备是否已经通过调用其中一种方法连接.

使用状态恢复的中央经理的启动流程如下:

>从恢复状态检查可用的外围设备(在您的情况下不需要).
>检查从先前扫描发现的外围设备(我将其保留在可用外设阵列中).
>检查连接的外围设备(使用上述方法).
>如果上面没有返回外设,请开始扫描.

您可能已经发现了另外一件事:iOS将缓存由外设发布的特性和UUID.如果这些更改,清除缓存的唯一方法是在iOS系统设置中关闭和打开蓝牙.

以上是来客网为你收集整理的iOS – BLE在背景上扫描随机冻结全部内容,希望文章能够帮你解决iOS – BLE在背景上扫描随机冻结所遇到的程序开发问题。

如果觉得来客网网站内容还不错,欢迎将来客网网站推荐给程序员好友。