【问题标题】:Black screen when opening app after background state在后台状态后打开应用程序时黑屏
【发布时间】:2021-06-13 16:39:59
【问题描述】:

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

当谈到后台模式时,我很难理解 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
}

我怀疑此代码会导致应用在手动打开应用时当前或最近由“系统”在后台运行时显示 UI (从最初终止的状态)。它是否正确?这意味着在某些极端情况下,人们会在没有调用“{ 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?

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

或者我可能在这里完全偏离目标,为什么有时某些用户没有 UI 可能有不同的原因?

【问题讨论】:

  • 至于如何在不启动前台进行调试,请查看Xcode Run option: Wait for the executable to belaunched。
  • @paiv 我需要的是一种从终止状态调试计划后台任务的方法。例如,根本没有真正启动应用程序。我认为您的解决方案只是等到我点击图标后才开始执行任何操作,而不是自动打开它,是吗?
  • 我认为@paiv 的意思是:使用一些在不久的将来安排任务的调试代码正常启动应用程序。然后终止它。然后在 Xcode 中按照他说的做(即选择“等待启动可执行文件”,然后按运行按钮)。这将使调试器准备好在应用程序由于计划任务启动后自行附加。在那之前你只需要等待(因此我建议暂时安排一些调试代码,比如一分钟左右)。

标签: ios swift background-fetch ios-lifecycle


【解决方案1】:

这里除了cmets还有一种回答:

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

计划任务启动应用程序,但未构建任何 UI。 iOS 没有太多工作要做,因此它决定让您的应用程序处于暂停模式,而不是在计划任务完成后完全终止它。这实际上是可以预料的,因为它只会在它真的缺乏资源的情况下终止它(我假设一个暂停的应用程序几乎不需要任何东西,内存被交换到磁盘,没有 CPU 周期,那么为什么要完全终止它呢?) .

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


所以要具体回答您的 Q1(警告,固执己见!):

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

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

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

关于问题2:

我猜这是一件困难而脆弱的事情......

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

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

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


更新,因为我是个白痴:

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

  1. 为您的应用方案提供自定义启动参数,例如“假BG状态”。为此,请编辑方案,转到“Run - Arguments”并在“Arguments Passed On Launch”下添加该字符串。
  2. 在您的 didFinishLaunchingWithOptions 中,将您的 UI 设置代码包装到
    if ProcessInfo.processInfo.arguments.contains("fakeBGState") {
        // ...
    }

这样,您可以按照与启动参数相同的方式启动 BG 任务。虽然这完全消除了操作系统为 BG 任务触发的实际启动,但仍然足以让您弄清楚这个特定的启动流程会导致黑屏。

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

【讨论】:

  • 谢谢。我尝试按照建议进行调度,但它从未启动应用程序。即使在一夜之间进行记录。操作系统调度程序是一个不会被驯服的神话野兽。不过我发现了一些奇怪的东西。如果我创建一个全新的项目(Swift/UIKit/Storyboard)并简单地添加applicationState == .background 签入didFinishLaunching,每次都是如此,即使在手动启动时也是如此。我将删除整个检查并让 UI 在后台初始化并监视它。最坏的情况是我们的背景内容不起作用,但这比黑屏要好。
  • 有点离题,我不同意你反对“在代码中做 UI”的观点。 SwiftUI 和 Storyboard 项目默认“不这样做”的“原因”是因为它增加了复杂性作为灵活性的副产品。这并不意味着它是没有根据的或不必要的。在某些情况下,遵循默认的“Storyboard-lifetime”只是一种低效且非常糟糕的解决方案。就我个人而言,我倾向于远离 Storyboard,因为需要额外的工作和时间来处理简单的事情,比如版本控制、打开时的加载时间、错误的连接等等。对于每个人来说。
猜你喜欢
  • 2012-11-19
  • 2021-07-17
  • 1970-01-01
  • 2019-07-22
  • 1970-01-01
  • 2014-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多