核心蓝牙 + iBeacon:我的 iBeacon 测距永远不会停止,即使在后台或应用程序被杀

问题描述

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

  • 用户登录后,Location Manager 和 Central Manager 被初始化

      func initLocationManager() {
          DDLogInfo("ACBLEManager > initLocationManager")
    
          locationManager = CLLocationManager()
          locationManager?.delegate = self
          locationManager?.requestAlwaysAuthorization()
      }
    
    func initBLEManager() {
        DDLogInfo("ACBLEManager > initBLEManager")

        var dic : [String : Any] = Dictionary()
        dic[CBCentralManagerOptionShowPowerAlertKey] = false
        bleCentralManager = CBCentralManager(delegate: self,queue: nil,options: dic)
    }
  • 针对预定义 UUID 的 iBeacon 测距和监控开始如下
    func startScanningiBeacon() {
        guard let iBeaconUUID = BTUUIDs.iBeaconUUID else {
            return
        }
        
        if #available(iOS 13.0,*) {
            self.beaconRegion = CLBeaconRegion(uuid: iBeaconUUID,identifier: beaconRegionIdentifier)
        } else {
            self.beaconRegion = CLBeaconRegion(proximityUUID: iBeaconUUID,identifier: beaconRegionIdentifier)
        }

        if let region = self.beaconRegion {
            region.notifyOnExit = true
            region.notifyOnEntry = true
            
            locationManager?.startMonitoring(for: region)
            locationManager?.startRangingBeacons(in: region)
        }
    }

这个,开始为信标测距,我可以在 didRangeBeacon 中使用检测到的信标获得回调

  • 一旦检测到信标,我就会得到它的主要和次要值。基于此,我准备了一个动态服务 UUID 并开始扫描 BLE Peripheral,如下所示
    var computedDoorID: String = "" {
        willSet(newValue) {
            if !(newValue.isEmpty) {
                computedServiceUUID = CBUUID(string: "\(newValue)-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
                dispatchQueue.global().async { [weak self] in
                    self?.bleCentralManager.scanForperipherals(withServices: [newValue],options: [CBCentralManagerScanoptionAllowDuplicatesKey: false])
                }
            }
        }
    }

当检测到任何 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) 

接收信标更新,我在我的视图控制器中使用最新的 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);
                }
            }
        }
    }

当我通过位置管理器的 didEnter 或 didExit 区域方法进入或离开区域时,这会通知我。

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

现在,当我进入信标范围时 > 我收到了 didEnterRegion 并且信标测距开始,然后 BLE 扫描恢复,即使我的应用程序没有运行,我也可以执行开门操作。

现在,我再次离开信标区域(我的应用程序仍未打开),我从未收到 didExitRegion 通知,并且它一直在后台运行。我可以看到打印了很多信标测距日志,并且当我在信标范围之外时不会收到回调。

但是,当我再次进入范围时,它会通知我并且 BLE 扫描恢复!

请帮我找到这个场景的解决方案(关于应用永不停止以及为什么 didExitRegion 没有被调用

我怀疑应用可能会在 appStore 审核过程中被拒绝,因为它永远不会停止,以及为什么我没有收到关于 didExitRegion 的回电?

注意:

  • 我已启用位置更新、外部附件通信、使用蓝牙 LE 附件和后台处理功能

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...