终止/暂停重大变更位置 API 的行为?

下面是 CLLocationManager文档中描述 开始监控重要位置的变化应用程序行为的部分:

如果您启动此服务,并且您的 申请书 终止,系统自动 将应用程序重新启动到 如果有新的事件发生,则作为后台处理 在这种情况下,选项词典 传给了 Application: did FinishLaunchingWithOptions: 应用程序委托的方法 里面有钥匙 UIApplicationLaunchOptionsLocationKey 表明你的申请是 由于位置事件而启动。 重新发射后,你必须 配置位置管理器对象 并调用此方法继续 接收位置事件。当你 重新启动位置服务,当前 事件被传递给您的代表 除此之外, 你的位置经理的财产 对象中填充了最多的 最近的位置对象甚至在你之前 开始定位服务。

所以我的理解是,如果您的应用程序终止(并且我假设如果您不从 终止调用 停止监视重要位置的变化) ,您将被一个 UIApplicationLaunchOptionsLocationKey参数唤醒到 Application: did FinishLaunchingWithOptions。此时,您创建 CLLocationManager,调用 开始监控重要位置的变化,并为 时间有限进行后台位置处理。所以我对这一点没意见。

前面的段落只谈到了应用程序终止时会发生什么,并没有提到应用程序挂起时你会做什么。完成启动选项的文件显示:

应用程序跟踪位置 被清除了, 现在已经重新启动。在这个 在大小写的情况下,字典包含一个键 表示该申请是 因为一个新地点而重新启动 事件。

建议您只有在您的应用程序启动时(因为位置更改)才会收到此呼叫,此时您已被终止。

然而,位置感知编程指南中关于 重大改变服务的段落有以下内容:

如果您让此服务继续运行,并且 你的申请 暂停或终止服务 自动唤醒您的 应用程序时,新的位置数据 到达。在起床时间,你的 应用程序放在后台 并给予少量的时间 处理位置数据。因为 你的申请就在后台, 它应该做最少的工作,并避免 任何任务(例如查询 网络) 在分配的时间之前返回 过期。如果没有,你的 申请可能被终止。

这表明,如果你的应用程序被暂停,你会被位置数据唤醒,但是没有提到你是如何被唤醒的:

在写这篇文章的过程中,我想我可能已经回答了我自己的问题,但是如果有一个更有知识的人来证实我对这个问题的理解,那就太好了。

33414 次浏览

My understanding is as follows (I'm in the process of writing an application that relies on this API, but haven't completed this component enough to start testing):

  1. Your application is run for the first time, you register to startMonitoringSignificantLocationChanges, and provide a callback function. While your application is running, it will call that callback whenever it receives a significant change.
  2. If your application is put to the background, UIApplication will receive applicationWillResignActive, followed by applicationDidEnterBackground.
  3. If your application is killed while it is suspended, you will not be notified; however, if your application is killed while it is running (foreground or background to my knowledge), you will get a moment with applicationWillTerminate. You cannot request extra background time from this function.
  4. Despite being killed in the background, the OS will relaunch your application. If your application is simply launched by the OS for a change, you will get a call to application didFinishLaunchingWithOptions:
if ([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey])

will help you determine if you've come back from a background location change.

  1. If, instead, you were currently running in the background, and your app is manually relaunched by the user, you will receive an applicationWillEnterForeground followed by applicationDidBecomeActive.

  2. Regardless of how it happened, when your application is relaunched (unless it was still running in the background as a result of a background task and said task had started monitoring changes), you need to explicitly tell it to startMonitoringSignificantLocationChanges again because the callback is no longer attached after "freeze drying." And yes, you just need to implement code in didUpdateToLocation once you've re-attached a location handler of some kind once coming back from the suspended state.

This is what I'm going on with my code development right now. As I mentioned before, I'm not quite ready to test this on a device so I can't tell if I've interpreted everything correctly, so commenters, please feel free to correct me (though I've done substantial reading on the topic).

Oh, and if by some stroke of bad luck, you release an app that does what I want mine to do, I might cry :)

Good luck!

Since I asked this question, I have done a fair bit of testing (mostly on the train between home and work) and have confirmed that the behaviour for suspended apps is as I suspected at the end of the question.

That is, your suspended app is woken up, you don't receive any callbacks on your app delegate, instead you receive your location updates through your existing CLLocationManagerDelegate. You can detect that you are running in the background by checking the applicationState, and do limited work for the case where you are woken from a suspended state to do location processing.

[UIApplication sharedApplication].applicationState == UIApplicationStateBackground

I came to this conclusion with a location test harness that you are welcome to download and try out. It is a pretty simple app that allows you to turn on significant change and GPS change APIs through the UI and log all the responses that you get back.

N.B. Point six in the previous answer is not correct. Freeze dried suspended apps do receive CLLocationManagerDelegate callbacks when they are woken up from a suspended state.

If the application is evoked from suspended state as a result of location change application will launch in background state.

All the objects will be live and you will recieve location update in the existing delegate.

So I just have an important note specific to when app was terminated:

For some APIs that can launch the app in the background and later need to be receiving a callback that handles that launching, would likely need a delegate object to be set.

So if you get an app launch (i.e. from terminated state, not suspended state) because of location tracking then, your locationManager delegate callbacks won't be called unless you set the delegate in the didFinishLaunching. To be more precise, you won't get any delegate callbacks until you set your delegate.

  1. So if you're doing follow in your ABC viewcontroller after your app launch
let manager = CLLocationManager()
manager.delegate = self
  1. Then app got terminated
  2. Then app was location due to a location change in the background
  3. YOU WON'T GET a callback until you foreground the app and open your ABC viewcontroller

It's incorrect to think that the app magically knows what the delegate object is. It doesn't persist that information.

Solution:

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

let myLocationManager = CLLocationManager()
myLocationManager.delegate = self
}

I figured this out for a totally unrelated API:

optional func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void)

That is, if you don't set your delegate before AppLaunch, then you miss user interaction with the notifications that happen immediately after an app termination. To be more precise you miss any callbacks until you set the userNotification's delegate.