改善和解决应用后台频繁被杀的问题

6,606 阅读5分钟

升级 iOS 13.2 后应用在后台频繁被杀给大量用户带来困扰。网络上wakeup 分析日志流传甚广,也有人断定因为应用不遵守 iOS 后台唤醒规则,所以被杀。

确定的是 wakeup 调用不是 App 被杀掉的直接原因,不能简单归因后台频繁被杀是开发者的问题。现在 Apple 提供了 MetricKit API 可以从系统层面获取应用后台被杀的原因,并给出了一些改善建议。

系统给出的应用程序后台终止的主要原因 :

  • 崩溃
  • CPU资源限制
  • Watchdog
  • 内存资源限制
  • 内存压力退出
  • 后台任务超时

新的MetricKit API 提供后台被杀的原因

MXBackgroundExitData通过提供每次应用程序被终止时的退出计数,了解为什么应用程序会被杀死。

Crashes

崩溃是最直接的终止类型。可能发生的原因有以下3种

  • SIGSEGV
  • SIGILL
  • 断言和异常退出

这些事件将在crashlog上生成,并自动向我们报告。除了Xcode organizer之外,MetricKit还为每个设备增加了更多的API,即 "MXCrashDiagnostic"。

MXCrashDiagnostic将提供以下信息

  • 堆栈跟踪
  • 信号
  • 异常代码
  • 终止原因

Watchdog

另一类被杀是由于Watchdog事件而发生的,该事件发生在一些关键的过程中的超时。

  • 关键过程中的超时:在应用程序的关键过程中,如启动、进入后台或再次进入前台时,出现长时间的挂起。它的时间限制在20秒左右。
  • 在模拟器和调试器中禁用。
  • 修正Watchdog事件将有助于消除死锁、死循环和主线程上频繁的同步工作。
  • "MXCrashDiagnostic"中提供报告。

CPU资源限制

CPU资源限制是指后台CPU持续负载较高。 在Xcode 12中增加了一些解决方案。

  • 通过xcode organizer和MXCPUExceptionDiagnostic实现电量异常报告。
  • 调用堆栈指出代码中的频繁调用。
  • 考虑将工作转入BGProcessingTask

内存占用超标

应用程序占用太多内存。一些解决方案 :

  • 前景和背景的限制相同
  • 使用 Instruments 和Memory Debugger
  • 请注意对旧设备的限制

Jetsam (内存压力退出)

注意:这不是你的应用程序的错误,它是最常见的退出原因。发生这种情况是因为系统为活动的应用程序腾出内存。

如何降低内存压力退出率?

  • 争取在后台使用少于50MB的空间。

建议在后台做以下操作。

  • 将状态保存到磁盘
  • 清空image view
  • 删除缓存

还有关于如何从内存压力退出中恢复的建议。

  • 在进入后台时保存状态,如视图控制器堆栈、text fields中的草稿输入、媒体播放位置以及更多取决于你的业务场景。
  • 使用UIKit状态恢复
  • 想办法让用户意识不到应用程序被终止了。

后台任务超时

当进入后台时,我们可以使用 "beginBackgroundTask "和 "endBackgroundTask "来执行后台任务(系统会给你30秒的时间来完成任务)

UIApplication.beginBackgroundTask(expertationHandler:)
UIApplication.endBackgroundTask(_:)
  • 问题是当我们不调用 "endBackgroundTask"时,会因为无法明确结束任务而导致程序被杀死。
  • 通过 "MXBackgroundExitData "暴露的计数。

给BackgroundTask命名来排查没有结束后台任务的问题。

UIApplication.beginBackgroundTask(withName:experienceHandler:)

为什么?

  • 在debugger中不会出现终止的情况。
  • 执行控制台消息并审核匹配的后台和结束任务过程的调用

另一个解决办法是使用expirationHandler

  • 实施一个expirationHandler作为保障措施,不要完全依赖它。
  • 在处理程序内调用endBackgroundTask
  • 不要在处理程序内开始新的业务
  • BackgroundTask开始时和期满处理程序中增加数据采集。
let handle = MXMetricManager.makeLogHandle(category: "DatabaseExpHandler")
mxSignpost(.event, log: handle, name: "Entered")
cancelOperations()
closeDatabase()
mxSignpost(.event, log: handle, name: "Exited")
UIApplication.shared.endBackgroundTask(backgroundTaskIdentifier)

让我们检查 "MXMetricPayload",看看标记数量,并检查标记数量是否有不平衡(非成对出现)。

另一种改善应用程序终止调试的解决方案是在做一些后台工作之前检查backgroundTimeRemaining

  • 只有在时间充裕的情况下才开始工作 *在剩余时间小于5秒时开始任务是不安全的。

示例代码 :

let minimumTimeRemaining = min(5, estimateProcessingTime(inputData))
if UIApplication.shared.backgroundTimeRemaining > minimumTimeRemaining {
    // 剩余时间足够,调用开始后台任务
    return UIApplication.shared.beginBackgroundTask { ... }
}else {
    // 时间不够,将这项工作推迟到以后进行。
    registerProcessingTask(inputData)
    return .invalid
}

下一步需要避免内存泄漏UIBackgroundTaskIdentifier。使用局部变量而不是实例变量来保存UIBackgroundTaskIdentifier,这样可以防止内存泄漏,因为它将在不同的内存上分配。

示例代码 :

@IBAction func beginDataExport(sender: UIButton) {
    var taskId.UIBbackgroundTaskIdentifier = .invalid: UIBbackgroundTaskIdentifier = .invalid
    taskId = UIApplication.shared.beginBackgroundTask {...}。
    //归档后结束后台任务,这需要几秒钟的时间。
    ArchiveUtility.exportUserData(completion: ()->()) {
        UIApplication.shared.endBackgroundTask(taskId)
    }
}

总结减少后台exit的解决方案

  • 识别并解决exit问题
  • 减少内存使用量
  • 实施UI状态恢复

参考资料:

wwdc2020/10078 为什么我的应用程序被杀死? developer.apple.com/videos/play…

wwdc2020/10081 MetricKit的新功能: developer.apple.com/videos/play…

MetricKit API 后台应用程序退出计数文档: developer.apple.com/documentati…

MetricKit 及它的使用方式,也提供了一个收集 MetricKit 数据的自建 Web 服务方案: nshipster.com/metrickit/

UI状态恢复文档: developer.apple.com/documentati…

本文由你的关注/点赞/评论赞助发表