后台状态后打开应用时黑屏

问题描述

我的任务是调试为什么有些用户在打开应用程序时有时会卡在黑屏上。我是这个特定应用程序的新手,所以我不知道整个流程,但我可以看出该应用程序具有后台功能。某些任务在夜间运行。

当涉及到后台模式时,我很难理解 iOS 应用的完整生命周期。

当应用从终止状态在后台启动时,我假设 didFinishLaunchingWithOptions 仍会被调用。我看到我们在代码中对此进行了一个小检查,在这种情况下,它省略了整个 UI 初始化:

func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    /*{ Initial setup }*/
    if UIApplication.shared.applicationState == .background {
        // App was launched due to Background Fetch event. No need for UI.
        return true
    }
    /*{ Start UI }*/
    return true
}

我怀疑这段代码会导致应用在每次手动打开应用时没有用户界面当它当前或最近由“系统”在后台运行时 (从最初终止的状态)。这样对吗?这意味着存在一些边缘情况,即在没有调用“{ Start UI }”的情况下打开应用程序。

我们还实现了 applicationDidBecomeActive,我认为应该使用它来确保在这种情况下呈现 UI。然而,现在这里只有一些可达性的东西:

func applicationDidBecomeActive(_ application: UIApplication) {
    reachabilityManager?.startObserving()
}

我发现的大多数在线资源都没有具体显示应用程序是如何在后台启动的,例如 this graph 在所有情况下总是转换为 didBecomeActive

所以问题1;在 didFinishLaunchingWithOptions 中评估状态的最佳实践方法是什么?在 .background 的情况下省略 UI 的当前实现是否最佳?如果是这样,我是否应该执行检查以查看 UI 是否正在 didBecomeActive 中运行,如果不是,则启动 UI?

引出问题2;如果我们应该在 didBecomeActive 中加载 UI,我们是否还应该在 didEnterBackground 中卸载或取消分配任何活动的 UI?

额外问题:是否可以使用调试器实际重现此问题?每次我用调试器启动应用程序时,它显然不在后台。如何调试从后台前台的生命周期?

或者我可能完全偏离了目标,为什么有时没有用户界面可能有不同的原因?

解决方法

除了这里的评论还有一种答案:

显然我们不知道是不是这样,但我想说你走在正确的轨道上。根据您的描述,我假设会发生以下情况:

计划任务启动应用程序并且没有构建用户界面。 iOS 没有什么可做的,因此它决定让您的应用程序处于挂起模式,而不是在计划任务完成后完全终止它。我会说这实际上是可以预料的,因为它只会在它真的缺乏资源时才终止它(我假设挂起的应用程序几乎不需要任何东西,内存交换到磁盘,没有 CPU 周期,那么为什么要完全终止它?) .

然后,稍后,用户点按该应用程序并将其置于前台。由于它已暂停且未终止,因此不会再次调用 didFinishLaunchingWithOptions(因为它之前已经启动)并且“哦不”没有构建 UI。


所以要具体回答您的 Q1(警告,自以为是!):

好吧,检查状态的最佳实践在您的实现中显然没问题(if 检查)...但是:基于状态不是一个好的做法。事实上,在我看来,自己从代码中“启动”UI 构建是一个坏主意,总的来说。 Swift(UI) 应用程序和基于 UIKit 和 Storyboard 的应用程序都不这样做是有原因的。我知道有些人喜欢“在代码中做 UI”,但我不同意。设法做到这一点的人通常并不意味着过于字面化,而且有一大群人误解了这一点,结果彼此之间产生了一行又一行的观点。这变得效率低下,所以像你这样的解决方案突然出现,其意图是“好吧,在后台任务中,我们必须要快,所以让我们把它骗出去”,而正确的结论应该是“也许我们做错了什么繁琐的手动 UI 构建”...

那么,这对您有什么帮助(因为我猜您无法完全重做所有 UI 内容来修复此错误)?不幸的是,“这取决于”。我尝试的第一件事实际上只是省略了 UI 的状态检查,即无论应用程序是否从后台启动,都进行构建。希望该过程中的任何内容实际上都不需要应用程序在屏幕上可见即可成功完成该过程。如果那行得通,那又怎样?然后应用程序在执行后台任务时有一个尚未显示的 UI,但那又怎样?无论如何,一旦用户将其置于前台,它将在稍后使用。

如果这不起作用,那么尝试将 UI 构建移动到 didBecomeActive 中并进行一些检查,以确保在应用程序已执行该代码的情况下它不会“重建”。在您的团队中公开质疑整个 UI 方法可能是值得的。

关于问题 2:

我猜这是一件很难的事情...

正如评论中所解释的,调试这个的技巧是为不是由 Xcode 启动的应用程序启动准备调试器,而是计划的 BG 任务。这意味着两件事:

  1. 您必须以某种方式将任务安排到未来或多或少已知的时间点。这很可能需要您添加一些调试代码(除非您想为每晚安排的实际任务熬夜……只是为了错过它)。只需在 didFinishLaunchingWithOptions 中安排一个在一分钟左右的时间内运行。现在,我知道你不能保证它会被执行,但我现在看不到其他方法。我自己还没有尝试过,如果它不起作用,呃...你可能不走运(或者必须等待很长时间才能真正调用任务)。
  2. 启动应用以便安排任务,然后在目标时间过去之前立即再次终止应用
  3. 更改 Xcode 方案。 “运行 - 信息 - 启动”到“等待可执行文件启动”。按下运行按钮。调试器现在等待通过其他方式启动应用程序。除非您自己点按该图标,否则这有望成为计划任务。

顺便说一下,Apple 有一个具体的方法来帮助您处理 debugging BG tasks,但看起来它实际上不会在安排它和启动任务处理程序之间终止您的应用程序(所以 {{1}不会以您需要的方式调用)。不过,您可能想自己对此进行调查(这个答案已经足够长了……抱歉)。


更新,因为我是个白痴:

虽然以上所有内容可能都有用,但我有点忽略了您实际想要测试的内容:基于状态的 UI 设置是否是问题的原因。有一种方法可以通过启动参数“注入”状态来确认这一点。您需要对您的 didFinishLaunchingWithOptions 实现进行最小程度的更改,并稍微修改一下您的应用方案,但它应该可以解决问题:

  1. 为您的应用方案提供自定义启动参数,例如“假BGState”。为此,请编辑方案,转到“运行 - 参数”并在“启动时传递的参数”下添加该字符串。
  2. 在您的 didFinishLaunchingWithOptions 中,将您的 UI 设置代码包装到
didFinishLaunchingWithOptions

通过这种方式,您可以根据启动参数以与 BG 任务相同的方式启动。虽然这完全消除了从操作系统触发 BG 任务的实际启动,但它仍然足以让您弄清楚是这个特定的启动流程导致黑屏。

最后,如果您的应用程序还支持后台提取,您可以使用它来触发应用程序从操作系统启动并通过 Xcode 进入后台模式。该方法与我对问题 2 的回答中的上述方法类似,但您不安排任何任务。相反,您只需按照第 3 步让调试器等待,然后在 Xcode 中,从 Debug 菜单中选择“Simulate Background Fetch”。也许这也对你有帮助,祝你好运!