用于应用程序后台处理的iOS CoreBluetooth调度队列

时间:2019-06-15 18:30:39

标签: ios grand-central-dispatch core-bluetooth ios-bluetooth dispatch-queue

首先,在核心角色中使用核心蓝牙发送数据以将数据发送到蓝牙LE设备的最佳方法是什么。它发送的数据需要进行处理,并且如果在其上运行,则需要花费足够的时间在UI线程上引起问题。用户将在打开手机应用程序的情况下启动该过程,然后继续使用该应用程序或关闭该应用程序,并期望数据继续发送到设备。

我发现2种非常糟糕的方法似乎奏效了

  • 将蓝牙CBCentralManager对象放置在主队列上,并有可能阻塞用户界面
  • 忽略来自iOS蓝牙堆栈的指示,表明它尚未准备好传输并有丢失数据的风险。

这似乎起源于iOS线程/调度队列以及iOS Bluetooth内部。

蓝牙LE iOS应用程序作为中心角色连接到蓝牙LE设备。 CBCentralManager根据apples documentation初始化。队列定义为:

  

用于调度中央角色事件的调度队列。如果   值为零,中央管理器使用以下命令调度中央角色事件   主队列。

根据vladiulianbogdan answer to Swift: Choose queue for Bluetooth Central manager的建议,我们应该为CBCentralManager创建一个串行队列。这似乎很有意义,有一段时间我一直在遵循这个建议。另外,allprog comment to Swift CoreBluetooth: Should CentralManager run in a separate thread 建议将主队列挂起,而其他队列则不会挂起,这与我所看到的相反。

虽然将串行队列用于蓝牙,但使用与主线程不同的蓝牙队列则更可取。有一个问题:回调:

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

别再打电话了。还有一种检查外围设备是否准备好发送更多数据的方法,CBPeripheral has a member variable canSendWriteWithoutResponse如果可以发送,则返回true。此变量还将开始重新调整为false,并且永远不会恢复为true。

我从Sandeep Bhandari中发现了一条评论,该评论说,当应用程序在后台运行时,所有队列线程都将停止,除非它们是apple提供的后台模式之一。 Biniou found that he was able to solve his core bluetooth background issue by initializing in a view controller instead of the app delegate。这对我来说没有意义。

我的应用程序确实在其info.plist中选择了核心蓝牙背景模式,因此它应该作为这些背景模式之一。我发现,当我的应用程序进入后台时,该应用程序确实会继续处理数据。我看到每100毫秒运行一次的轮询循环中的日志消息。

如果我从这些轮询循环中触发了蓝牙LE写入,则能够继续发送数据。问题是我无法确定发送数据的安全速率,而且传输速度非常慢或有时会丢失。

我不确定如何最好地处理此问题。任何建议,将不胜感激。似乎无论进入后台时做什么,我都会失去确定数据发送是否安全的能力。

我看到this comment表示唯一的解决方案是更改蓝牙设备与电话的连接方式,是这种情况吗?我不确定目前是否可以更改硬件。

理想的解决方案是找到一种方法,将CBCentralManager放在其自己的串行队列中,但以使该应用程序进入后台时该队列不会停止的方式创建该队列。如果有人知道该怎么做,我相信它将解决我的问题。

我当前的代码是这样的。在我的AppDelegate的applicationDidFinishLaunchingWithOptions回调中创建蓝牙服务后

    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一个不在UI线程上并且当应用程序进入后台时都不会关闭的队列,将不胜感激。如果那不可能,我需要尝试选择一种解决方法。

0 个答案:

没有答案