Ios 核心蓝牙&x2B;iBeacon:即使在后台或应用程序中,我的iBeacon测距也不会停止
我开发了一个应用程序,它使用iBeacon+BLE(核心蓝牙)技术执行车门解锁操作。我的要求是这样的Ios 核心蓝牙&x2B;iBeacon:即使在后台或应用程序中,我的iBeacon测距也不会停止,ios,bluetooth-lowenergy,core-bluetooth,ibeacon,ios-background-mode,Ios,Bluetooth Lowenergy,Core Bluetooth,Ibeacon,Ios Background Mode,我开发了一个应用程序,它使用iBeacon+BLE(核心蓝牙)技术执行车门解锁操作。我的要求是这样的 用户登录后,将初始化位置管理器和中央管理器 func initLocationManager() { DDLogInfo("ACBLEManager > initLocationManager") locationManager = CLLocationManager() locationManager?.delegate =
- 用户登录后,将初始化位置管理器和中央管理器
func initLocationManager() { DDLogInfo("ACBLEManager > initLocationManager") locationManager = CLLocationManager() locationManager?.delegate = self locationManager?.requestAlwaysAuthorization() }
- iBeacon对预定义UUID的测距和监控如下所示
- 一旦检测到信标,我就得到它的主要值和次要值。基于此,我准备了一个动态服务UUID,并开始扫描BLE外设,如下所示
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
DDLogInfo("ACBLEManager > CentralManager didDiscover peripheral > Peripheral name: \(peripheral.name ?? "") > Device Service UUID: \(advertisementData)")
if seenDevices[peripheral.identifier] == nil {
if let deviceIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [AnyObject], deviceIDs.count > 0 {
DDLogInfo("ACBLEManager > centralManager didDiscover peripheral > deviceIDs :\(deviceIDs)")
let deviceIDString = (deviceIDs.first as? CBUUID)!.uuidString
if (RSSI.doubleValue < 0) {
DDLogInfo("ACBLEManager > discovered peripheral's > deviceIDString :\(deviceIDString)")
let device = BTDevice(peripheral: peripheral, manager: bleCentralManager, deviceID: deviceIDString)
if isBackgroundMode {
device.latestRSSI = RSSI.doubleValue
}
seenDevices[peripheral.identifier] = device
DDLogInfo("ACBLEManager > centralManager didDiscover peripheral > Array of Seen Devices :\(seenDevices)")
delegate?.didDiscover(device: device)
} else {
DDLogError("BLUETOOTH RSSI: \(RSSI.doubleValue) is > 0 so ignoring it.")
}
}
} else {
if (RSSI.doubleValue < 0) {
DDLogInfo("ACBLEManager > Same BLE Peripheral discovered again \(peripheral) with rssi: \(RSSI)")
let device = seenDevices[peripheral.identifier]
if isBackgroundMode {
device?.latestRSSI = RSSI.doubleValue
}
seenDevices[peripheral.identifier] = device
delegate?.didDiscover(device: device!)
} else {
DDLogError("BLUETOOTH RSSI for Same Peripheral : \(RSSI.doubleValue) is > 0 so ignoring it.")
}
}
}
接收到信标更新,我用最新的RSSI值在我的视图控制器中传递它。基于此RSSI值,决定用户是靠近门还是远离门。一旦RSSI值大于预定义值(例如-50),它会认为用户离得更近,并启动BLE连接并执行开门操作
这在前景模式下工作得非常好。现在,为了使其在后台模式(或用户终止模式)下运行,我启用了区域监视并延长了后台执行时间。这样,当用户进入信标区域时,应用程序可以在一定时间内处于活动状态,用户可以执行开门操作
为此,当应用程序移动到后台时,我会延长后台运行时间并监视该区域
func applicationDidEnterBackground(_ application: UIApplication) {
DDLogInfo("∏∏∏ > AppDelegate > applicationDidEnterBackground > ∏∏∏")
if userDefaultManager.bool(forKey: UserDefaultKey.isUserLoggedIn) {
ACBLEManager.sharedInstance.extendBackgroundRunningTime()
ACBLEManager.sharedInstance.startBeaconRanging()
ACBLEManager.sharedInstance.isBackgroundMode = true
} else {
DDLogError("AppDelegate > applicationDidEnterBackground > User is logged out. So don't perform anything")
}
}
func startBeaconRangingAndMonitoring() {
if let region = self.beaconRegion {
locationManager?.startRangingBeacons(in: region)
locationManager?.startMonitoring(for: region)
}
}
func extendBackgroundRunningTime() {
if (self.backgroundTask != .invalid) {
// if we are in here, that means the background task is already running.
// don't restart it.
return
}
self.backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "DummyTask", expirationHandler: {
print("Background time Expire Handler")
self.isBackgroundMode = true
UIApplication.shared.endBackgroundTask(self.backgroundTask)
if let region = self.beaconRegion {
self.locationManager?.startMonitoring(for: region)
}
self.backgroundTask = .invalid
})
if threadStarted {
print("Background task thread already started.")
}
else {
threadStarted = true
DispatchQueue.global(qos: .default).async {
while (true) {
// A dummy tasks must be running otherwise iOS suspends immediately
Thread.sleep(forTimeInterval: 1);
}
}
}
}
当我通过Location Manager的didEnter或didExit region方法进入或离开该区域时,这会通知我
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
showLocalNotification(title: "Welcome to the Beacon Region!", body: "")
DDLogInfo("LocationManager > DidEnterRegion : \(region)")
manager.allowsBackgroundLocationUpdates = true
manager.startMonitoringSignificantLocationChanges()
manager.startRangingBeacons(in: region as! CLBeaconRegion)
if (self.isBackgroundMode) {
self.extendBackgroundRunningTime()
}
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
showLocalNotification(title: "Ohh NO!", body: "You're out of Beacon Region!")
DDLogInfo("LocationManager > DidExitRegion : \(region)")
DDLogInfo("Manager Monitored regions: \(manager.monitoredRegions) region.notifyOnExit : \(region.notifyOnExit), region.notifyOnEntry: \(region.notifyOnEntry)")
guard region is CLBeaconRegion else { return }
manager.startMonitoring(for: region)
manager.startRangingBeacons(in: region as! CLBeaconRegion)
}
但是,这在第一次使用时效果良好,即当我的应用程序第一次打开时>移动到后台>用户终止它(将其从应用程序切换器中删除)>超出信标范围>我收到预期的didExitRegion
现在,当我进入beacon range>时,我接收到didEnterRegion并开始信标测距,然后恢复BLE扫描,即使我的应用程序未运行,我也可以执行开门操作
现在,我再次离开信标区域(我的应用程序仍然没有打开),我从未收到didExitRegion通知,它在后台持续运行。我可以看到很多信标测距日志打印出来,当我在信标测距范围之外时,不会收到回调
但是,当我再次进入该范围时,它会向我发出一个关于和BLE扫描恢复的通知
请帮助我找到此场景的解决方案(关于应用程序永不停止&为什么不调用didExitRegion)
我怀疑应用程序可能会在appStore审查过程中被拒绝,因为它从未停止,为什么我没有收到关于didExitRegion的回电
注:
- 我已启用位置更新、外部附件通信、蓝牙LE附件和后台处理功能
didExitRegion
回调,继续得到didRangeBeacons
callbacks w/>1个信标。你能给我看看这个吗?我建议您在didEnterRegion
、didExitRegion
和didRangeBeacons
的顶部添加一行调试代码,并记录区域标识符,如果是didRangeBeacons
,则记录检测到的信标计数。重新运行测试并从应用程序中捕获日志(无论是从Xcode还是从控制台应用程序——即使应用程序被杀死,控制台也可以工作)。然后显示在范围计数=30秒之前,您得到了一个范围计数>1的didEnterRegion0@davidgyoung我的意思是,我要回电话了。在didRangeBeacons中,我得到了我的信标(只有一个信标在广播)。现在,基于beacon的主要和次要价值,我开始发现CoreBooth服务UUID,它会扫描外设。现在我的问题是,当我的应用程序没有运行时,一旦应用程序收到didEnterRegion通知,应用程序就不会停止对信标的测距,它会持续扫描信标和其他外围设备。此外,我可以看到didDetermineState的日志-在内部或外部,但是没有收到didExitRegion回调。我不知道y?@DavidYoung我的疑问是,我的“didRangeBeacons”和CoreBlutooth的DidDiscoveryPeripheral从未在后台/应用程序未运行模式下停止过?我的目标是,当用户进入信标范围时,启用应用程序(即使最小化/用户已被杀死),获取信标的详细信息(主要值和次要值),根据这些详细信息,发现相关的外设。一旦发现外设,建立可扩展连接,对其执行写入命令并断开连接!!一切正常,但如果我也在30分钟后操作,应用程序可以执行BLE操作(我可以在打印的日志中看到这些)。对不起,如果我无法看到我在上面的评论中请求的日志,我无法帮助您解决此问题。简单地重复这个问题是没有帮助的。我得看看详细的日志。
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
DDLogInfo("ACBLEManager > CentralManager didDiscover peripheral > Peripheral name: \(peripheral.name ?? "") > Device Service UUID: \(advertisementData)")
if seenDevices[peripheral.identifier] == nil {
if let deviceIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [AnyObject], deviceIDs.count > 0 {
DDLogInfo("ACBLEManager > centralManager didDiscover peripheral > deviceIDs :\(deviceIDs)")
let deviceIDString = (deviceIDs.first as? CBUUID)!.uuidString
if (RSSI.doubleValue < 0) {
DDLogInfo("ACBLEManager > discovered peripheral's > deviceIDString :\(deviceIDString)")
let device = BTDevice(peripheral: peripheral, manager: bleCentralManager, deviceID: deviceIDString)
if isBackgroundMode {
device.latestRSSI = RSSI.doubleValue
}
seenDevices[peripheral.identifier] = device
DDLogInfo("ACBLEManager > centralManager didDiscover peripheral > Array of Seen Devices :\(seenDevices)")
delegate?.didDiscover(device: device)
} else {
DDLogError("BLUETOOTH RSSI: \(RSSI.doubleValue) is > 0 so ignoring it.")
}
}
} else {
if (RSSI.doubleValue < 0) {
DDLogInfo("ACBLEManager > Same BLE Peripheral discovered again \(peripheral) with rssi: \(RSSI)")
let device = seenDevices[peripheral.identifier]
if isBackgroundMode {
device?.latestRSSI = RSSI.doubleValue
}
seenDevices[peripheral.identifier] = device
delegate?.didDiscover(device: device!)
} else {
DDLogError("BLUETOOTH RSSI for Same Peripheral : \(RSSI.doubleValue) is > 0 so ignoring it.")
}
}
}
locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) function updates the RSSI value (every second)
func applicationDidEnterBackground(_ application: UIApplication) {
DDLogInfo("∏∏∏ > AppDelegate > applicationDidEnterBackground > ∏∏∏")
if userDefaultManager.bool(forKey: UserDefaultKey.isUserLoggedIn) {
ACBLEManager.sharedInstance.extendBackgroundRunningTime()
ACBLEManager.sharedInstance.startBeaconRanging()
ACBLEManager.sharedInstance.isBackgroundMode = true
} else {
DDLogError("AppDelegate > applicationDidEnterBackground > User is logged out. So don't perform anything")
}
}
func startBeaconRangingAndMonitoring() {
if let region = self.beaconRegion {
locationManager?.startRangingBeacons(in: region)
locationManager?.startMonitoring(for: region)
}
}
func extendBackgroundRunningTime() {
if (self.backgroundTask != .invalid) {
// if we are in here, that means the background task is already running.
// don't restart it.
return
}
self.backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "DummyTask", expirationHandler: {
print("Background time Expire Handler")
self.isBackgroundMode = true
UIApplication.shared.endBackgroundTask(self.backgroundTask)
if let region = self.beaconRegion {
self.locationManager?.startMonitoring(for: region)
}
self.backgroundTask = .invalid
})
if threadStarted {
print("Background task thread already started.")
}
else {
threadStarted = true
DispatchQueue.global(qos: .default).async {
while (true) {
// A dummy tasks must be running otherwise iOS suspends immediately
Thread.sleep(forTimeInterval: 1);
}
}
}
}
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
showLocalNotification(title: "Welcome to the Beacon Region!", body: "")
DDLogInfo("LocationManager > DidEnterRegion : \(region)")
manager.allowsBackgroundLocationUpdates = true
manager.startMonitoringSignificantLocationChanges()
manager.startRangingBeacons(in: region as! CLBeaconRegion)
if (self.isBackgroundMode) {
self.extendBackgroundRunningTime()
}
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
showLocalNotification(title: "Ohh NO!", body: "You're out of Beacon Region!")
DDLogInfo("LocationManager > DidExitRegion : \(region)")
DDLogInfo("Manager Monitored regions: \(manager.monitoredRegions) region.notifyOnExit : \(region.notifyOnExit), region.notifyOnEntry: \(region.notifyOnEntry)")
guard region is CLBeaconRegion else { return }
manager.startMonitoring(for: region)
manager.startRangingBeacons(in: region as! CLBeaconRegion)
}