Ios 核心蓝牙&x2B;iBeacon:即使在后台或应用程序中,我的iBeacon测距也不会停止

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 =

我开发了一个应用程序,它使用iBeacon+BLE(核心蓝牙)技术执行车门解锁操作。我的要求是这样的

  • 用户登录后,将初始化位置管理器和中央管理器

      func initLocationManager() {
          DDLogInfo("ACBLEManager > initLocationManager")
    
          locationManager = CLLocationManager()
          locationManager?.delegate = self
          locationManager?.requestAlwaysAuthorization()
      }
    
  • iBeacon对预定义UUID的测距和监控如下所示
这样,开始为信标测距,我可以在检测到信标的didRangeBeacon中得到回调

  • 一旦检测到信标,我就得到它的主要值和次要值。基于此,我准备了一个动态服务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附件和后台处理功能

@davidgyoung你能帮我一下吗?从我读到的,你是说你最终没能得到一个
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)
    }