gpt4 book ai didi

iOS - 后台 BLE 扫描随机卡住

转载 作者:技术小花猫 更新时间:2023-10-29 10:10:15 24 4
gpt4 key购买 nike

更新 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 使用率的泄漏。

  • Resource usage analysis

    更新 08/06:
  • 请注意,这不是广告问题,因为我们的 BLE 设备始终供电,并且我们使用了我们能找到的最强的 BLE 电子卡。
  • 后台的 iOS 检测时间也不是问题。我等了很长时间(20~30 分钟)才确定这不是这个问题。

  • 原问题

    我目前正在开发一个处理与 BLE 设备通信的应用程序。我的一个限制是,只有当我必须发送命令或读取数据时,我才必须连接到该设备。完成后我必须尽快断开连接,以便其他潜在用户也能这样做。

    该应用程序的功能之一如下:
  • 用户可以在应用程序处于后台时启用自动命令。如果在 10 分钟内未检测到设备,则会触发此自动命令。
  • 我的应用程序会一直扫描,直到找到我的 BLE 设备。
  • 为了在我需要它时保持清醒,我每次都重新启动扫描,因为 CBCentralManagerScanOptionAllowDuplicatesKey 选项无知。
  • 当它被检测到时,我正在检查最后一次检测是否超过 10 分钟前。如果是这种情况,我会连接到设备,然后写入与我需要的服务相对应的特征。

  • 目标是在用户进入范围时触发此设备。它可能会在超出范围(例如几个小时)后几分钟发生,这取决于我的用户习惯。

    一切都以这种方式工作正常,但有时(似乎是随机发生的),扫描有点“卡住”。我的过程做得很好,但过了一段时间,我看到我的应用程序正在扫描,但我的 didDiscoverPeripheral: 回调从未被调用,即使我的测试设备就在我的 BLE 设备前面。有时可能需要一段时间才能检测到它,但在这里,几分钟后什么也没有发生。

    我在想 iOS 可能杀死了我的应用程序以收回内存,但是当我关闭和打开蓝牙时, centralManagerDidUpdateState:被称为正确的方法。如果我的应用程序被杀死了,应该不会是这样吧?
    如果我打开我的应用程序,扫描将重新启动,它又恢复原状。
    我还检查了 iOS 在事件 180 秒后没有关闭我的应用程序,但事实并非如此,因为它在这段时间后运行良好。

    我已经将我的 .plist 设置为具有正确的设置( bluetooth-central in UIBackgroundModes )。我管理所有 BLE 处理的类存储在我的 中AppDelegate 作为可通过我的所有应用程序访问的单例。我还测试了切换创建此对象的位置。目前我正在 中创建它应用程序:didFinishLaunchingWithOptions:方法。我试着把它放在我的 AppDelegate 初始化:但是每次我在后台时扫描都会失败,如果我这样做的话。

    我不知道我可以向您展示我代码的哪一部分,以帮助您更好地理解我的流程。以下是一些可能有帮助的示例。请注意“ AT_appDelegate ”是一个宏,以便访问我的 AppDelegate .
    // 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, 0), ^{

    [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)serviceUUIDs- (NSArray<CBPeripheral *> * nonnull)retrievePeripheralsWithIdentifiers:(NSArray<NSUUID *> * nonnull)identifiers
    您可能正处于等待 didDiscoverPeripheral 的状态。这永远不会发生,因为系统已经连接。实例化您的 CBCentralManager 后,首先通过调用这些方法之一来检查您的外围设备是否已连接。

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

  • 从恢复状态检查可用的外围设备(在您的情况下不需要)。
  • 检查从之前的扫描中发现的外围设备(我将它们保存在可用外围设备的数组中)。
  • 检查连接的外围设备(使用上述方法)。
  • 如果以上都没有返回外围设备,则开始扫描。

  • 您可能已经发现的另一件事提示:iOS 将缓存外设广告的特征和 UUID。如果这些更改,清除缓存的唯一方法是在 iOS 系统设置中关闭和打开蓝牙。

    关于iOS - 后台 BLE 扫描随机卡住,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30682376/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com