iOS-BLE后台扫描会随机冻结
更新14/08-3-找到了真正的解决方案: 您可以在下面的答案中查看解决方案 更新16/06-2-可能是解决方案: 正如Sandy Chapman在其回答中的评论中所建议的,我现在可以使用以下方法在扫描开始时检索外围设备:iOS-BLE后台扫描会随机冻结,ios,objective-c,iphone,background-process,core-bluetooth,Ios,Objective C,Iphone,Background Process,Core Bluetooth,更新14/08-3-找到了真正的解决方案: 您可以在下面的答案中查看解决方案 更新16/06-2-可能是解决方案: 正如Sandy Chapman在其回答中的评论中所建议的,我现在可以使用以下方法在扫描开始时检索外围设备: - (NSArray<CBPeripheral *> * nonnull)retrievePeripheralsWithIdentifiers:(NSArray<NSUUID *> * nonnull)identifiers 是检查返回数组的大小。每
- (NSArray<CBPeripheral *> * nonnull)retrievePeripheralsWithIdentifiers:(NSArray<NSUUID *> * nonnull)identifiers
是检查返回数组的大小。每次出现错误时,它总是0。如果我的设备在当前运行之前没有进行任何连接,也可能会发生此错误。我还能够看到我的设备广告,并在另一台设备出现错误时,用另一台设备触发命令
2006年10月更新:
- 我让我的应用程序运行了一整晚,以检查是否有任何内存泄漏或大量资源使用,以下是我在后台运行12-14小时后的结果。内存/CPU使用率与我离开时完全相同。这让我觉得我的应用程序没有任何漏洞,可能导致iOS关闭它以恢复内存/CPU使用李>
- 请注意,这不是一个广告问题,因为我们的BLE设备始终通电,并且我们使用了我们能找到的最强的BLE电子卡
- 这也不是iOS在后台检测时间的问题。我等了很长时间(20~30分钟)才确定不是这个问题
- 用户可以在应用程序处于后台时启用自动命令。如果在10分钟内未检测到设备,则会触发此自动命令
- 我的应用程序会一直扫描,直到找到我的设备
- 为了在需要时让它保持清醒,我每次都会重新启动扫描,因为CBCentralManagerScanOptionAllowDuplicatesKey选项不存在
- 当它被检测到时,我正在检查最后一次检测是否是在10分钟前。如果是这样,我连接到设备,然后写入与我需要的服务对应的特征
// 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];
}
}
//我的DeviceManager类的Init,该类处理所有BLE处理
-(id)init{
self=[super init];
//标志创建
self.autoConnectTriggered=否;
self.isdevicerady=否;
self.connectionUncomplete=否;
self.currentCommand=NONE;
self.currentCommand_index=0;
self.signalOkDetectionCount=0;//有助于确定设备是否处于良好范围内或距离过远
self.connectionFailedCount=0;//如果命令失败,将在“重试”过程中提供帮助
self.main_uuid=[CBUUID uuid withstring:MAINSERVICE_uuid];
self.peripheralstainer=[[NSMutableArray alloc]init];
self.lastDeviceDetection=nil;
//可编辑项目创建
dispatch\u queue\u t queue=dispatch\u queue\u create(“com.inition.corebooth.queue”,dispatch\u queue\u SERIAL);
self.centralManager=[[CBCentralManager alloc]initWithDelegate:self-queue:queue];
[自启动扫描];
回归自我;
}
//我开始扫描的方式
-(无效)开始扫描{
如果(!self.isScanning&&self.centralManager.state==cbcentralmanager状态poweredon){
CLS#U日志(@“####开始扫描###”);
self.isScanning=是;
NSDictionary*options=[NSDictionary Dictionary WithObject:[NSNumber numberWithBool:!self.isBackground]forKey:CBCentralManager-CanOptionalLowDuplicateSkey];
调度异步(调度获取全局队列(调度队列优先级默认为0)^{
[self.centralManager扫描外设服务:@[self.main_uuid]选项:选项];
});
}
}
//找到设备后停止并重新启动扫描的方式。包含一些可以忽略的前台(UI更新)进程
-(无效)停止扫描和启动:(布尔)
// 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];
}
}
// 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];
});
@property (atomic, strong) dispatch_queue_t bluetoothQueue;