后台位置更新

Posted

技术标签:

【中文标题】后台位置更新【英文标题】:Background Location Updates 【发布时间】:2020-03-02 10:11:47 【问题描述】:

我正在构建一个带有位置服务的应用程序。

我正在使用用户当前位置来获取用户周围的对象。目前运行良好。唯一的问题是,我想在后台使用“signfiicantLocationChanges”为用户创建本地通知,但是当应用程序从 AppDelegateapplicationDidFinishLaunching(_:) 函数启动时,launchOptions 对象是 nil

我想获取后台更新并发出 HTTP API 请求,根据响应,我将创建本地通知。

这是我的AppDelegate 课程:

import UIKit
import UserNotifications
import CoreLocation

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate 

    var window: UIWindow?
    var locationManager: LocationManager?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool 

        // Checking this because if the app is started for location updates,
        // no need to setup app for UI
        if let _ = launchOptions?[.location] 
            locationManager = LocationManager()
            locationManager?.delegate = self
            locationManager?.getCurrentLocation()
            return true
        

        attemptToRegisterForNotifications(application: application)

        if #available(ios 13, *)   else 
            app.start()
        

        return true
    

    // MARK: UISceneSession Lifecycle
    @available(iOS 13.0, *)
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration 
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    

    @available(iOS 13.0, *)
    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) 
        // Called when the user discards a scene session.
        // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
        // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
    

    func applicationDidBecomeActive(_ application: UIApplication) 
        UNUserNotificationCenter.current().removeAllDeliveredNotifications()
    


extension AppDelegate: LocatableOutputProtocol 
    func didGetCurrentLocation(latitude: Double, longitude: Double) 
        UNUserNotificationCenter.current().getNotificationSettings(completionHandler:  (settings) in
            if settings.authorizationStatus == .authorized 
                let content = UNMutableNotificationContent()
                content.title = "\(Date().timeIntervalSince1970)"

                let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)

                let request = UNNotificationRequest(identifier: "\(Date().timeIntervalSince1970)", content: content, trigger: trigger)

                UNUserNotificationCenter.current().add(request)  _ in

                
            
        )
    

    func failedGetCurrentLocation(error: Error) 
        print(error)
    


extension AppDelegate: UNUserNotificationCenterDelegate 

    private func attemptToRegisterForNotifications(application: UIApplication) 
        UNUserNotificationCenter.current().delegate = self

        let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
        UNUserNotificationCenter.current().requestAuthorization(options: authOptions, completionHandler:  granted, error in
            if let error = error 
                print("failed to get auth", error)
                return
            
            if granted 
                DispatchQueue.main.async 
                    application.registerForRemoteNotifications()
                
             else 
                print("NO AVAIL FOR NOTIFS")
            
        )
    

    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) 
        completionHandler(.alert)
    

我还有一个自定义的LocationManager 类:

import CoreLocation

final class LocationManager: NSObject, Locatable 
    weak var delegate: LocatableOutputProtocol?

    var locationManager: CLLocationManager

    override init() 
        locationManager = CLLocationManager()
        super.init()

        let authStatus = CLLocationManager.authorizationStatus()
        if CLLocationManager.locationServicesEnabled() 
            if (authStatus == .authorizedAlways || authStatus == .authorizedWhenInUse) 
                locationManager.delegate = self
                locationManager.startUpdatingLocation()
                locationManager.startMonitoringSignificantLocationChanges()
                locationManager.allowsBackgroundLocationUpdates = true
                locationManager.desiredAccuracy = kCLLocationAccuracyBest
             else 
                locationManager.requestAlwaysAuthorization()
                print("we dont have permission")
            
         else 

        
    

    func getCurrentLocation() 
        locationManager.startUpdatingLocation()
    


extension LocationManager: CLLocationManagerDelegate 
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) 
        if let coordinates = locations.first?.coordinate 
            locationManager.stopUpdatingLocation()
            self.delegate?.didGetCurrentLocation(latitude: coordinates.latitude, longitude: coordinates.longitude)
        
    

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) 
        self.delegate?.failedGetCurrentLocation(error: error)
    

    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) 
        print("status changed")
        if (status == .authorizedAlways || status == .authorizedWhenInUse) 
            print("we got permission")
         else 
            print("nope")
        
    


我正在尝试通过使用Wait for executable to be launched 在 Xcode 上创建新架构并在模拟器的调试菜单上使用 Freeway Ride 来调试它。也用真机测试过。

我错过了什么?

【问题讨论】:

您是否在后台模式下启用了位置更新?启用后,您的应用程序应在后台获取更新。另外,我在任何地方都没有看到 [locationManager startMonitoringSignificantLocationChanges] 被调用。如果不调用它,您的应用将不会获得重要的位置更新。 @PuneetSharma 它在 LocationManager 类中调用。同样是的,它已启用。 您解决了这个问题吗? @Cyber​​Mew 实际上是的,但由于大流行我已经放弃了项目...... @onurgenes 真不幸……为了后代,您能否分享一下您是如何修复它的以及原因是什么? 【参考方案1】:

@onurgenes,如果你从你的项目中添加一个真实的代码,首先,你如何从这里开始任何位置更新?

    if let _ = launchOptions?[.location] 
        locationManager = LocationManager()
        locationManager?.delegate = self
        locationManager?.getCurrentLocation()
        return true
    

当应用程序第一次启动时launchOptions 将是nil,而您的LocationManager() 甚至没有启动,因此您的任何位置监控和更新都将不起作用(也许您在app.start() 有一些代码但现在它看起来像一个错误)。

第二件事 - 在您的示例中,您正在使用购买的定位监控:

    locationManager.startUpdatingLocation()
    locationManager.startMonitoringSignificantLocationChanges()

所以这里您的位置经理只处理significantLocationChanges()。如果你想同时使用它们 - 你应该切换它(didBecomeActiveNotificationdidEnterBackgroundNotification)或者按照 Apple 的建议创建不同的位置管理器实例。

第三个 - 你的问题。让我们更详细地寻找这部分:

locationManager = LocationManager()
locationManager?.delegate = self
locationManager?.getCurrentLocation()

正如我提到的 - 在LocationManager() 你开始监控位置:

locationManager.startUpdatingLocation()
locationManager.startMonitoringSignificantLocationChanges()

这就是您所需要的 - 重大的位置变化。但是在您使用locationManager.startUpdatingLocation() 调用getCurrentLocation() 之后,您“重写”了您的监控,这就是您没有从中获得任何更新的原因。

另外,请记住:

    重要位置仅在存在 设备位置的显着变化,(实验 建立 500 米或以上) 重要的位置非常不准确(对我来说有时高达 900 米)。很多时候重要的位置使用只是为了唤醒应用程序并重新启动 定位服务。 您的应用从位置更改唤醒后 通知,您将获得少量时间(大约 10 秒),因此如果您需要更多时间将位置发送到服务器 你应该要求更多的时间 beginBackgroundTask(withName:expirationHandler:)

希望我的回答对您有所帮助。快乐编码!

【讨论】:

我已经尝试了所有建议,但这仍然不起作用。如果你有空,我可以邀请你加入我的 GitHub 存储库。 @nurgenes 当然,我在 github 的用户名是 rubikva 已发送邀请。您需要此项目的服务器,但您可以模拟服务器响应。 “按照 Apple 的建议创建不同的位置管理器实例。” 【参考方案2】:

@onurgenes 您需要在您的位置管理器初始化部分使用NotificationCenter,如下所示,

import CoreLocation

    final class LocationManager: NSObject, Locatable 
        weak var delegate: LocatableOutputProtocol?

        var locationManager: CLLocationManager

        override init() 

            NotificationCenter.default.addObserver(self, selector: #selector(applicationDidEnterBackgroundActive(_:)), name: UIApplication.didEnterBackgroundNotification, object: nil)

            NotificationCenter.default.addObserver(self, selector: #selector(applicationWillEnterForegroundActive(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)

            locationManager = CLLocationManager()
            super.init()

            let authStatus = CLLocationManager.authorizationStatus()
            if CLLocationManager.locationServicesEnabled() 
                if (authStatus == .authorizedAlways || authStatus == .authorizedWhenInUse) 
                    locationManager.delegate = self
                    locationManager.startUpdatingLocation()
                    locationManager.startMonitoringSignificantLocationChanges()
                    locationManager.allowsBackgroundLocationUpdates = true
                    locationManager.desiredAccuracy = kCLLocationAccuracyBest
                 else 
                    locationManager.requestAlwaysAuthorization()
                    print("we dont have permission")
                
             else 

            
        

        func getCurrentLocation() 
            locationManager.startUpdatingLocation()
        
    

    extension LocationManager: CLLocationManagerDelegate 
        func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) 
            if let coordinates = locations.first?.coordinate 
                locationManager.stopUpdatingLocation()
                self.delegate?.didGetCurrentLocation(latitude: coordinates.latitude, longitude: coordinates.longitude)
            
        

        func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) 
            self.delegate?.failedGetCurrentLocation(error: error)
            self.locationManager.stopMonitoringSignificantLocationChanges()
        

        func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) 
            print("status changed")
            if (status == .authorizedAlways || status == .authorizedWhenInUse) 
                print("we got permission")
            self.locationManager.startMonitoringSignificantLocationChanges()
             else 
                print("nope")
            
        
    


        @objc private func applicationDidEnterBackgroundActive (_ notification: Notification) 
                self.locationManager.startMonitoringSignificantLocationChanges()
        

        @objc private func applicationWillEnterForegroundActive (_ notification: Notification) 
            self.locationManager.startUpdatingLocation()
        

你需要在你的 AppDelegate 类上使用这个 LocationManager 类来初始化它。我希望它能帮助您实现所需的输出。

【讨论】:

还是没有运气。如果你愿意,我可以邀请你到我的 GitHub 仓库。

以上是关于后台位置更新的主要内容,如果未能解决你的问题,请参考以下文章

后台位置更新

在后台持续更新位置

我是不是需要获得后台位置更新的“始终”授权?

在 iOS 后台更新位置

后台位置的核心数据更新位置导致阻塞 UI

仅有时在 iOS 后台更新位置