[英]iOS - BLE Scanning on background freezes randomly
更新14/08 - 3 - 找到真正的解決方案:
您可以在下面的答案中查看解決方案!
更新16/06 - 2 - 可能是解決方案:
正如桑迪查普曼在回答中的評論中所說,我現在能夠在掃描開始時使用這種方法檢索我的外圍設備:
- (NSArray<CBPeripheral *> * nonnull)retrievePeripheralsWithIdentifiers:(NSArray<NSUUID *> * nonnull)identifiers
我實際上是想通過在掃描開始時讓我的外圍設備恢復並在需要時啟動連接(即使它不在范圍內)來使其工作。 iOS將保持活着,直到找到我正在尋找的設備。
另請注意,iOS 8.x中可能存在一個錯誤,如果在后台有另一個使用藍牙的發布版本的應用程序,則會不時通過掃描(我得到的消失回調)保留一個帶有調試版本的應用程序。
更新16/06:
所以我檢查了retrievePeripheralsWithServices,如果在我開始掃描時連接了任何設備。 當我得到錯誤時,我啟動應用程序和我做的第一件事
- (void) applicationDidBecomeActive:(UIApplication *)application
是檢查返回數組的大小。 它總是0,每次我得到錯誤。 如果我的設備在當前運行中之前沒有建立任何連接,也會發生錯誤。 當我收到另一台設備的錯誤時,我也能看到我的設備廣告並用第二台設備觸發命令。
更新10/06:
更新08/06:
原始問題
我目前正在開發一個處理與BLE設備通信的應用程序。 我的一個限制是,只有在必須發送命令或讀取數據時才必須連接到此設備。 我必須在完成后盡快斷開連接,以允許其他潛在用戶也這樣做。
該應用程序的一個功能如下:
目標是在用戶進入范圍時觸發此設備。 它可能會在超出范圍幾分鍾后發生幾個小時,這取決於我的用戶習慣。
一切都以這種方式運作良好,但有時(似乎在隨機時間發生),掃描類型“凍結”。 我的過程做得很好,但經過幾次,我看到我的應用程序掃描,但我的didDiscoverPeripheral:回調從未被調用,即使我的測試設備正好在我的BLE設備前面。 有時它可能需要一段時間來檢測它,但在這里,幾分鍾后沒有任何反應。
我以為iOS可能已經殺死我的應用程序以聲明內存,但當我關閉和藍牙時, centralManagerDidUpdateState:被稱為正確的方式。 如果我的應用程序被殺,那不應該是這樣嗎? 如果我打開我的應用程序,掃描將重新啟動,它將恢復生機。 我還檢查了iOS在180秒的活動后沒有關閉我的應用程序,但事實並非如此,因為它在這段時間后運行良好。
我已經設置了我的.plist以獲得正確的設置( UIBackgroundModes中的 bluetooth-central )。 管理所有BLE處理的我的班級作為單身人士存儲在我的AppDelegate中 ,可通過我的所有應用程序訪問。 我也測試過切換我正在創建這個對象的地方。 目前我正在應用程序中創建它:didFinishLaunchingWithOptions:方法。 我試着將它放在我的AppDelegate init中:但是如果我這樣做的話,每次我在后台時掃描都會失敗。
我不知道我的代碼的哪一部分可以告訴你,以幫助你更好地理解我的過程。 以下是一些可能有用的示例。 請注意,“ AT_appDelegate ”是一個maccro,以便訪問我的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系統設置中關閉和打開藍牙。
經過多次嘗試,我終於找到了真正的解決方案。
僅供參考,我與Apple的一位工程師(通過技術支持)進行了多次會談。 他以兩種方式領導我:
真正的解決方案可以參與這些要素。 我在搜索清理這個bug時添加並更正了一些內容。 但是讓它發揮作用的東西(我測試了大約4-5小時並且它根本沒有凍結)是關於dispatch_queue 。
我之前做過的事:
// Initialisation
dispatch_queue_t queue = dispatch_queue_create("com.onset.corebluetooth.queue", DISPATCH_QUEUE_SERIAL);
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:queue options:@{CBCentralManagerOptionRestoreIdentifierKey:RESTORE_KEY}];
// Start scanning
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.centralManager scanForPeripheralsWithServices:@[self.main_uuid] options:options];
});
// Stop scanning
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.centralManager stopScan];
});
我現在在做什么:
// Initialisation
self.bluetoothQueue = dispatch_queue_create("com.onset.corebluetooth.queue", DISPATCH_QUEUE_SERIAL);
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:self.bluetoothQueue options:@{CBCentralManagerOptionRestoreIdentifierKey:RESTORE_KEY}];
// Start scanning
dispatch_async(self.bluetoothQueue, ^{
[self.centralManager scanForPeripheralsWithServices:@[self.main_uuid] options:options];
});
// Stop scanning
dispatch_async(self.bluetoothQueue, ^{
[self.centralManager stopScan];
});
請注意我將此行添加到我的DeviceManager.h(我的主要Ble類):
@property (atomic, strong) dispatch_queue_t bluetoothQueue;
你可以看到它有點混亂:)
所以現在我可以根據需要進行掃描。 謝謝你的幫助 ! 我希望有一天它可能對某人有所幫助。
它可能與您嘗試配對的藍牙設備有關,其中一些具有較低的廣告費率,這通常是為了節省電池壽命。 我要檢查的第一件事是BLE設備配置廣告的頻率。
這甚至可能與iOS設備不像其他設備那樣頻繁掃描有關,如本文所述 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.