Прежде всего вопрос, как лучше всего использовать ядро 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 очередь, которая не была в потоке пользовательского интерфейса и не закрывалась, когда приложение переходит в фоновый режим. Если это невозможно, мне нужно попробовать выбрать один из обходных путей.
globalBackgroundTask
в основном потоке? - person Eugene Dudnyk   schedule 18.06.2019CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopCommonModes, ^{ [self.cbPeripheral writeValue:data forCharacteristic:self.rxCharacteristic type:CBCharacteristicWriteWithoutResponse]; } )
- person Eugene Dudnyk   schedule 20.06.2019