Очередь отправки iOS CoreBluetooth для фоновой обработки приложения

Прежде всего вопрос, как лучше всего использовать ядро ​​​​Bluetooth в центральной роли для отправки данных на устройства Bluetooth LE. Данные должны быть обработаны, и это занимает достаточно времени, чтобы вызвать проблемы в потоке пользовательского интерфейса, если он работает на нем. Пользователь инициирует процесс с открытым приложением телефона, а затем либо продолжает использовать приложение, либо закрывает приложение и ожидает, что данные будут продолжать отправляться на устройство.

Я нашел 2 действительно плохих способа сделать это, которые, кажется, работают

  • Поместите объект Bluetooth CBCentralManager в основную очередь и рискуете заблокировать пользовательский интерфейс.
  • Игнорируйте указания Bluetooth-стека iOS о том, что он не готов к передаче, и рискуете потерять данные.

Похоже, что это имеет свои корни как в очередях потоковой передачи / отправки iOS, так и во внутренних устройствах iOS Bluetooth.

Приложение Bluetooth LE iOS подключается к устройству Bluetooth LE в качестве центральной роли. CBCentralManager инициализируется в соответствии с документацией Apple. Очередь определяется как:

Очередь отправки, используемая для отправки событий центральной роли. Если значение равно nil, центральный менеджер отправляет события центральной роли, используя основную очередь.

Как было предложено vladiulianbogdan answer Swift: выберите очередь для центрального диспетчера Bluetooth, мы должны создать последовательную очередь для CBCentralManager. Кажется, это имеет смысл, и какое-то время я следовал этому совету. Также комментарий allprog к Swift CoreBluetooth: должен ли CentralManager работать в отдельный поток предполагает, что основная очередь будет приостановлена, а другие очереди — нет, что противоположно тому, что я вижу.

При использовании последовательной очереди для Bluetooth предпочтительнее использовать другую очередь, чем основной поток. Есть проблема: Обратный вызов:

    -(void)peripheralIsReadyToSendWriteWithoutResponse:(CBPeripheral *)peripheral
    {
        [self sendNextBluetoothLePacket];
    }

перестать звонить. Существует еще один способ проверить, готово ли периферийное устройство отправить дополнительные данные: переменная-член canSendWriteWithoutResponse, которая возвращает true, если ее можно отправить. Эта переменная также начинает перенастраивать значение false и никогда не возвращается к значению true.

Я нашел этот комментарий от Sandeep Bhandari, в котором говорится, что все потоки очереди останавливаются, когда приложение переходит в фоновый режим, если они не являются одним из фоновых режимов, предоставляемых Apple. Биниу обнаружил, что ему удалось решить свою основную проблему с фоном Bluetooth путем инициализации в контроллере представления вместо делегата приложения. Это не имеет смысла для меня.

В моем приложении в info.plist выбран фоновый режим core-bluetooth, поэтому он должен считаться одним из этих фоновых режимов. Я обнаружил, что когда мое приложение переходит в фоновый режим, оно продолжает обрабатывать данные. Я вижу сообщения журнала из циклов опроса, которые запускаются каждые 100 миллисекунд.

Если я запускаю запись Bluetooth LE из этих циклов опроса, я могу продолжать отправлять данные. Проблема в том, что я не могу определить безопасную скорость для отправки данных, либо она очень медленная, либо данные иногда теряются.

Я не уверен, как лучше с этим справиться. Мы ценим любые предложения. Кажется, что независимо от того, что я делаю, когда я перехожу в фоновый режим, я теряю способность определять, безопасно ли отправлять данные.

Я вижу этот комментарий, который указывает, что единственным решением было бы изменить способ подключения устройства Bluetooth к телефону, так ли это? Я не уверен, что изменение аппаратного обеспечения является вариантом для меня на данный момент.

Идеальным решением было бы найти способ поместить CBCentralManager в свою собственную последовательную очередь, но создать эту очередь таким образом, чтобы очередь не останавливалась, когда приложение переходит в фоновый режим. Если кто-то знает, как это сделать, я считаю, что это решит мою проблему.

Мой текущий код выглядит следующим образом. Когда служба Bluetooth создается в обратном вызове applicationDidFinishLaunchingWithOptions для моего AppDelegate

    self.cm = [[CBCentralManager alloc] initWithDelegate:self
                                                   queue:nil
                                                 options:@{CBCentralManagerOptionShowPowerAlertKey:@(0)}];

Или с последовательной очередью, которая должна работать, но не

    dispatch_queue_t bt_queue = dispatch_queue_create("BT_queue", 0);
    self.cm = [[CBCentralManager alloc] initWithDelegate:self
                                                   queue:bt_queue
                                                 options:@{CBCentralManagerOptionShowPowerAlertKey:@(0)}];

Когда пришло время отправить некоторые данные, я использую один из этих

        [self.cbPeripheral writeValue:data
                    forCharacteristic:self.rxCharacteristic
                                 type:CBCharacteristicWriteWithoutResponse];

Если я просто продолжу вызывать это writeValue без задержки между ними, не пытаясь проверить, безопасно ли отправлять данные. В конце концов это потерпит неудачу.

После установления соединения с этим кодом запрашивается дополнительное время выполнения в фоновом режиме.

    - (void)   centralManager:(CBCentralManager *)central
     didConnectPeripheral:(CBPeripheral *)peripheral
    {
        UIApplication *app = [UIApplication sharedApplication];
        if (self.globalBackgroundTask != UIBackgroundTaskInvalid) {
            [app endBackgroundTask:self.globalBackgroundTask];
            self.globalBackgroundTask = UIBackgroundTaskInvalid;
        }
        self.globalBackgroundTask = [app beginBackgroundTaskWithExpirationHandler:^{
            [app endBackgroundTask:_globalBackgroundTask];
            _globalBackgroundTask = UIBackgroundTaskInvalid;
        }];

Будем очень признательны за любые мысли по этому поводу или предложения о том, как я могу предоставить CBCentralManager очередь, которая не была в потоке пользовательского интерфейса и не закрывалась, когда приложение переходит в фоновый режим. Если это невозможно, мне нужно попробовать выбрать один из обходных путей.


person Marc    schedule 15.06.2019    source источник
comment
Вы запрашиваете globalBackgroundTask в основном потоке?   -  person Eugene Dudnyk    schedule 18.06.2019
comment
Я подозревал, что проблема может быть в нем. Я попытался обернуть это в явном dispatch_async в созданной мной очереди, и мне не повезло с этим. Спасибо   -  person Marc    schedule 19.06.2019
comment
Похоже, что проблема с необработанными событиями ввода-вывода связана с циклом выполнения фоновой очереди. Попробуйте поиграть с CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopCommonModes, ^{ [self.cbPeripheral writeValue:data forCharacteristic:self.rxCharacteristic type:CBCharacteristicWriteWithoutResponse]; } )   -  person Eugene Dudnyk    schedule 20.06.2019