Suspended — iOS 应用程序生命周期中的暂停状态,应用程序被冻结在内存中,但不执行任何代码。我们将展示 Suspended 如何工作,后台应用冻结带来哪些风险,iOS 如何管理已挂起应用程序的卸载,以及如何实现状态恢复以在从 Suspended 返回后实现无缝恢复。
要点
Suspended 是 iOS 应用程序生命周期中的一个状态,应用程序位于设备的 RAM 中,但不执行任何代码。这是完全终止前的最终状态:应用程序在完成所有后台任务或超时后从 Background 进入 Suspended。在 Suspended 状态下,应用程序被完全冻结 — 所有线程暂停,定时器不工作,没有网络活动。
Suspended 是 iOS 的独特特性,在标准的 Android 生命周期中不存在。原因在于不同的进程管理架构。iOS 将应用程序镜像保存在内存中(类似于桌面系统的休眠),以便用户返回时无需冷启动即可立即恢复界面。Android 没有 Suspended — 进程要么存在并可以执行代码(Background),要么已终止(Not Running),尽管 Android 可以通过 LMK 暂停线程的执行。
对用户而言,Suspended 看起来像是即时恢复:用户通过 App Switcher 在应用间切换,每个应用都从离开的位置打开。这造成所有应用同时运行的假象。实际上,大多数应用都被冻结在 Suspended 状态。从 Suspended 热启动比从 Not Running 冷启动快得多,因为代码已经加载到内存中。
iOS 监控所有应用的状态,并根据可用内存决定卸载 Suspended 应用。当内存不足时,系统开始卸载 Suspended 应用,从处于该状态最久的应用开始。如果内存仍然不足,系统将应用从 Background 和 Inactive 转移到 Suspended 状态,然后卸载。这个过程对用户完全透明 — 用户只会看到 App Switcher 中的应用图标,点击时会触发冷启动。
| 特性 | Suspended (iOS) | Background (iOS) | Background (Android) |
|---|---|---|---|
| 代码执行 | 否 | 是(有限) | 是(有限) |
| 在内存中 | 是 | 是 | 是 |
| CPU 消耗 | 0% | 低 | 低 |
| 热启动 | 是 — 即时恢复 | 是 — 通过 Inactive | 否 — 进程可能已被终止 |
| 超时 | 无 — 可在内存中停留数小时 | 约 30 秒(beginBackgroundTask 后) | 取决于 API 版本 |
| 系统卸载 | 内存不足时 | 内存严重不足时 | LMK(低内存杀手) |
| 恢复运行 | 从 App Switcher — 即时 | 从 App Switcher — 通过 Inactive | 冷启动 |
| 状态恢复 | 推荐 | 不需要 | SavedStateHandle |
在 iOS 中,Suspended 在所有后台任务完成后自动进入。系统调用 applicationDidEnterBackground,给予执行 beginBackgroundTask 的时间(约 30 秒),然后强制暂停所有线程并将应用程序转入 Suspended 状态。此时内存中的对象被保留,但不执行任何代码 — 应用程序保持在当前状态被冻结。
关键点:applicationDidEnterBackground 是进入 Suspended 前保证被调用的最后一个方法。之后应用程序不会收到任何关于内存卸载的通知。如果用户或系统终止处于 Suspended 状态的应用程序,既不会调用 applicationWillTerminate,也不会再次调用 applicationDidEnterBackground。因此所有数据保存必须在 applicationDidEnterBackground 中完成,而不是在 applicationWillTerminate 中。
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// 进入 Suspended 前的最后保证调用
func applicationDidEnterBackground(_ application: UIApplication) {
// 保存所有需要在内存卸载后保留的数据
savePersistentState()
saveNavigationStack()
// 如果需要,请求额外时间
let task = application.beginBackgroundTask {
application.endBackgroundTask(task)
}
}
// 从 Suspended 返回 — 热启动
func applicationWillEnterForeground(_ application: UIApplication) {
// 应用程序已在 Suspended 状态,恢复运行
print("从 Suspended 或 Background 返回")
}
// 内存卸载后的完全恢复
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 如果这是从 Suspended 卸载后的冷启动 —
// 恢复状态恢复
return true
}
private func savePersistentState() {
UserDefaults.standard.set(Date(), forKey: "lastActiveDate")
}
private func saveNavigationStack() {
guard let rootVC = window?.rootViewController else { return }
// 保存当前导航堆栈
if let navController = rootVC as? UINavigationController {
let vcClasses = navController.viewControllers.map { type(of: $0) }
UserDefaults.standard.set(vcClasses.map { NSStringFromClass($0) }, forKey: "navStack")
}
}
}代码中展示了 iOS 上处理 Suspended 的关键逻辑。applicationDidEnterBackground 是最后保证的调用。所有数据保存必须在此处完成:用户状态、导航堆栈、草稿、定时器。applicationWillEnterForeground 在从 Suspended 或 Background 返回时调用。didFinishLaunchingWithOptions — 仅在冷启动时调用,即应用程序在 Suspended 后从内存中卸载时。
Android 没有直接对应的 iOS Suspended 状态。Android 不会冻结内存中的应用程序并保留执行上下文。相反,Android 要么将进程保留在后台(Background),要么终止它(Not Running)。但在 Android 11+(API 30)上引入了 App Freezer 机制,通过 SIGSTOP 信号暂停后台进程的执行。这是 Suspended 的功能性类似状态,但存在重要区别。
App Freezer 是 Android 内存管理系统的一部分。当应用程序长时间处于后台且没有活跃通知时,系统向其发送 SIGSTOP,暂停所有线程。当应用程序回到前台时,发送 SIGCONT,执行恢复。与 iOS 的关键区别:App Freezer 不保证状态保存 — 如果进程在冻结期间被终止,内存中的数据可能丢失。
在 Android 上,推荐使用 SavedStateHandle 在 ViewModel 中自动保存任何进程终止时的状态。SavedStateHandle 通过 onSaveInstanceState 将数据保存在 Bundle 中,该 Bundle 既能度过 App Freezer 也能度过 Process Death。与 iOS 不同,在 iOS 中从 Suspended 卸载是例外情况,而在 Android 中 Process Death 是正常行为,应始终被预期。
// SavedStateHandle — Android 上避免进程终止的救星
class CheckoutViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// 即使在应用冻结后也能保留的状态
var currentStep: MutableLiveData<Int> =
savedStateHandle.getLiveData("checkout_step", 1)
var cartItems: MutableLiveData<List<CartItem>> =
savedStateHandle.getLiveData("cart_items", emptyList())
fun proceedToNextStep() {
currentStep.value = (currentStep.value ?: 0) + 1
}
fun addToCart(item: CartItem) {
val updatedList = (cartItems.value ?: emptyList()) + item
cartItems.value = updatedList
savedStateHandle["cart_items"] = updatedList
}
}
// 在 onStop 中保存以应对应用冻结
class MainActivity : AppCompatActivity() {
override fun onStop() {
super.onStop()
// 保存需要在冻结后保留的数据
saveDraftData()
// 释放在冻结状态下不需要的资源
releaseHeavyResources()
// 警告应用程序将被冻结
// (调试日志记录)
Log.d("生命周期", "Activity 已停止 — 可能的应用冻结")
}
}代码中展示了处理 Android 上 Suspended 类似状态的方法。ViewModel 中的 SavedStateHandle 在 Process Death 时自动保存和恢复数据。onStop 是进入可能的 App Freezer 或进程终止前最后保证的事件。结账表单状态、购物车商品列表 — 所有这些数据都通过 SavedStateHandle 度过冻结。对于重资源(位图、数据库游标),onStop 是释放内存的地方。
State Restoration 是 iOS 内置的机制,用于在应用程序从内存中卸载后保存和恢复 UI 状态。如果应用程序处于 Suspended 状态并被系统卸载,下次冷启动时状态恢复会恢复导航堆栈、滚动位置、表单状态和其他 UI 元素。用户将回到离开时的同一界面。
State Restoration 通过 UIViewControllerRestoration 和 UIStateRestoring 协议工作。开发者为每个想要恢复的 ViewController 和 View 分配 restorationIdentifier。在进入 Background 时,iOS 对这些对象的状态进行编码。在卸载后返回时,iOS 创建新对象并解码保存的状态。没有状态恢复,用户在冷启动后会看到空白界面,而不是离开时的位置。
import UIKit
class DetailViewController: UIViewController {
var itemID: String = ""
var scrollPosition: CGPoint = .zero
override func viewDidLoad() {
super.viewDidLoad()
restorationIdentifier = "DetailViewController"
restorationClass = type(of: self)
}
override func encodeRestorableState(with coder: NSCoder) {
super.encodeRestorableState(with: coder)
coder.encode(itemID, forKey: "itemID")
coder.encode(scrollPosition, forKey: "scrollPosition")
}
override func decodeRestorableState(with coder: NSCoder) {
super.decodeRestorableState(with: coder)
if let savedID = coder.decodeObject(forKey: "itemID") as? String {
itemID = savedID
loadItem()
}
if let savedPosition = coder.decodeCGPoint(forKey: "scrollPosition") {
scrollPosition = savedPosition
// 数据加载后恢复位置
}
}
}
// AppDelegate — 激活状态恢复
func application(
_ application: UIApplication,
shouldSaveSecureApplicationState coder: NSCoder
) -> Bool {
return true
}
func application(
_ application: UIApplication,
shouldRestoreSecureApplicationState coder: NSCoder
) -> Bool {
return true
}代码中展示了 iOS 上 State Restoration 的实现。每个可恢复的 ViewController 都需要 restorationIdentifier 和 restorationClass。encodeRestorableState/decodeRestorableState 通过 NSCoder 保存和加载数据。在 AppDelegate 中,shouldSaveSecureApplicationState 和 shouldRestoreSecureApplicationState 启用加密状态保存。从 iOS 12+ 开始,推荐使用安全编码(NSSecureCoding)来保护数据。
第一条规则 — 永远不要指望应用程序会从 Suspended 返回。系统可以在任何时候卸载应用程序。所有关键数据必须在进入 Suspended 之前保存到持久化存储中 — 即在 applicationDidEnterBackground 或 onStop 中。UserDefaults、Core Data、File Manager 是合适的存储方式。内存(变量、属性)对于需要在 Suspended 后保留的数据来说是不可靠的存储方式。
第二条规则 — 在进入 Suspended 前释放资源。关闭文件描述符,释放 GPU 内存(Metal、Core Graphics),关闭网络连接。虽然应用程序在 Suspended 状态下不消耗 CPU,但占用的资源会被阻塞,无法被其他应用程序使用。在 iOS 的 Suspended 状态下不能保持打开的套接字 — 从 Suspended 返回后它们可能无法正常工作,导致错误。
第三条规则 — 不要将依赖时间的逻辑放在等待从 Suspended 返回的位置。定时器、回调和网络活动在 Suspended 状态下会停止。如果应用程序在 Suspended 状态下停留数小时,返回时定时器可能触发不正确。返回时检查数据的时效性 — 缓存可能已过期,授权令牌可能已失效。
第四条规则 — 对所有界面使用 State Restoration,特别是输入表单、可滚动列表和详情界面。没有状态恢复,用户从已卸载的 Suspended 返回后将看到应用程序的初始界面,而不是离开时的位置。这会降低用户体验,迫使用户重复操作。
import UIKit
// 检查:应用程序是否已从内存中卸载?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 检查是否有保存的状态
if UserDefaults.standard.object(forKey: "navStack") != nil {
// 应用程序已从 Suspended 卸载
// 需要恢复状态
restoreNavigationStack()
} else {
// 从 Not Running 的纯冷启动
showOnboardingIfNeeded()
}
return true
}
private func restoreNavigationStack() {
guard let savedStack = UserDefaults.standard.array(forKey: "navStack") as? [String],
let navController = window?.rootViewController as? UINavigationController
else { return }
for vcClassName in savedStack {
if let vcClass = NSClassFromString(vcClassName) as? UIViewController.Type {
let vc = vcClass.init()
navController.pushViewController(vc, animated: false)
}
}
}代码中展示了判断应用程序是否已从 Suspended 卸载的实践。检查 UserDefaults 中是否存在保存的导航堆栈,可以区分卸载后的冷启动和纯冷启动。在第一种情况下,恢复导航堆栈;在第二种情况下,显示引导页或主界面。这种方法在 NSCoder 不足以处理的情况时补充了内置的 State Restoration。
常见问题
没有限制 — 从几秒到几天。iOS 对 Suspended 没有超时限制。应用程序将保持在内存中,直到系统因资源不足决定卸载它。实际上,应用程序在 Suspended 状态下停留 15 分钟到几个小时,具体取决于设备的 RAM 容量和活跃应用的数量。
不会。applicationWillTerminate 不会被调用当应用程序从 Suspended 卸载时。系统只是释放内存,不通知应用程序。这是为什么所有数据保存必须在 applicationDidEnterBackground 中完成的另一个原因。applicationWillTerminate 仅在用户通过从 App Switcher 滑动手动终止应用程序时调用。
没有直接对应的状态。在 Android 11+ 上引入了 App Freezer,通过 SIGSTOP 暂停后台进程 — 这在功能上类似于 Suspended。但 Android 应用程序必须设计为随时可能发生 进程终止。使用 ViewModel 中的 SavedStateHandle 和 onSaveInstanceState 保存状态,以度过 App Freezer 和进程终止。
在 iOS 中没有直接的 API 可以检查。间接方法:在 didFinishLaunchingWithOptions 中检查 UserDefaults 中是否存在保存的状态。如果状态存在 — 应用程序已从 Suspended 卸载并冷启动。如果状态不存在 — 纯冷启动。在 SwiftUI 中,可以在 scenePhase.background 中保存一个标志,并在下次启动时检查。
进入 Suspended 时,iOS 会拍摄 快照 — 应用程序当前 UI 的截图。此快照显示在 App Switcher 中,并在返回应用程序时(作为「解冻」动画)显示。如果应用程序包含敏感数据,快照可能会泄露它们。为了保护,请使用 UIApplication.shouldSnapshotSecureApp(iOS 16+)或在 applicationDidEnterBackground 中叠加模糊层。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。