Suspended — nó là gì, đông băng ứng dụng trong nền iOS

Tác giả: IT Sectr Đã đăng: 2026-03-03 Thời gian đọc: 11 phút

Suspended — trạng thái tạm dừng của vòng đời ứng dụng iOS, trong đó ứng dụng bị đông băng trong bộ nhớ nhưng không thực thi mã. Chúng tôi cho thấy Suspended hoạt động như thế nào, những rủi ro khi đông băng ứng dụng trong nền, cách iOS quản lý việc dỡ bỏ các ứng dụng Suspended và cách triển khai state restoration để khôi phục liền mạch sau khi quay lại từ Suspended.

Những điểm chính

  • Suspended — ứng dụng bị đông băng trong bộ nhớ, mã không được thực thi, ngăn xếp UI được bảo toàn
  • iOS Suspended — trạng thái duy nhất không có trong Android; tiến trình tồn tại nhưng không hoạt động
  • Dỡ bỏ — khi thiếu bộ nhớ, các ứng dụng Suspended bị xóa trước, dữ liệu trong bộ nhớ bị mất
  • State Restoration — cơ chế iOS để lưu và khôi phục ngăn xếp UI sau khi dỡ bỏ
  • didEnterBackground — phương thức cuối cùng được đảm bảo gọi trước Suspended

Suspended — trạng thái này là gì

Suspended là trạng thái của vòng đời ứng dụng iOS, trong đó ứng dụng nằm trong RAM của thiết bị nhưng không thực thi bất kỳ mã nào. Đây là trạng thái cuối cùng trước khi kết thúc hoàn toàn: ứng dụng chuyển từ Background sang Suspended sau khi hoàn thành tất cả các tác vụ nền hoặc sau khi hết thời gian chờ. Trong Suspended, ứng dụng bị đông băng hoàn toàn — tất cả các luồng bị tạm dừng, bộ đếm thời gian không hoạt động và không có hoạt động mạng.

Suspended là một tính năng độc đáo của iOS, không có trong vòng đời tiêu chuẩn của Android. Nguyên nhân nằm ở kiến trúc quản lý tiến trình khác nhau. iOS lưu giữ hình ảnh của ứng dụng trong bộ nhớ (tương tự chế độ ngủ đông trên máy tính để bàn) để khi người dùng quay lại, giao diện có thể được khôi phục ngay lập tức mà không cần khởi động nguội. Android không có Suspended — một tiến trình hoặc tồn tại và có thể thực thi mã (Background) hoặc đã kết thúc (Not Running), mặc dù Android có thể tạm dừng thực thi luồng qua LMK.

Đối với người dùng, Suspended trông giống như khôi phục tức thì: họ chuyển đổi giữa các ứng dụng qua App Switcher và mỗi ứng dụng mở đúng nơi họ để lại. Điều này tạo ảo giác rằng tất cả các ứng dụng đang chạy đồng thời. Trên thực tế, hầu hết chúng bị đông băng trong Suspended. Khởi động nóng từ Suspended nhanh hơn nhiều lần so với khởi động nguội từ Not Running, vì mã đã được tải vào bộ nhớ.

Hệ thống quản lý Suspended như thế nào

iOS theo dõi trạng thái của tất cả các ứng dụng và quyết định dỡ bỏ các ứng dụng Suspended dựa trên bộ nhớ khả dụng. Khi bộ nhớ thấp, hệ thống bắt đầu dỡ bỏ các ứng dụng Suspended, bắt đầu từ những ứng dụng ở trạng thái này lâu nhất. Nếu bộ nhớ vẫn không đủ, hệ thống chuyển các ứng dụng từ Background và Inactive sang Suspended với việc dỡ bỏ sau đó. Quá trình này hoàn toàn minh bạch cho người dùng — họ chỉ thấy biểu tượng ứng dụng trong App Switcher, khi chạm vào sẽ bắt đầu khởi động nguội.

Đặc tínhSuspended (iOS)Background (iOS)Background (Android)
Thực thi mãKhôngCó (giới hạn)Có (giới hạn)
Trong bộ nhớ
Tiêu thụ CPU0%ThấpThấp
Khởi động nóngCó — khôi phục tức thìCó — qua InactiveKhông — tiến trình có thể bị kết thúc
Thời gian chờKhông — có thể ở trong bộ nhớ hàng giờ~30 giây (sau beginBackgroundTask)Phụ thuộc vào phiên bản API
Dỡ bỏ bởi hệ thốngKhi thiếu bộ nhớKhi thiếu bộ nhớ nghiêm trọngLMK (Low Memory Killer)
Quay lại làm việcTừ App Switcher — ngay lập tứcTừ App Switcher — qua InactiveKhởi động nguội
State RestorationĐược khuyến nghịKhông cần thiếtSavedStateHandle

Suspended trong iOS: cơ chế đông băng

Trong iOS, Suspended đạt được tự động sau khi hoàn thành tất cả các tác vụ nền. Hệ thống gọi applicationDidEnterBackground, dành thời gian để thực thi beginBackgroundTask (khoảng 30 giây), sau đó buộc tạm dừng tất cả các luồng và chuyển ứng dụng sang Suspended. Các đối tượng trong bộ nhớ được bảo toàn, nhưng không có mã nào được thực thi — ứng dụng bị đông băng trong trạng thái hiện tại của nó.

Một điểm cực kỳ quan trọng: applicationDidEnterBackground là phương thức cuối cùng được đảm bảo gọi trước Suspended. Sau đó, ứng dụng không nhận được bất kỳ thông báo nào về việc dỡ bỏ bộ nhớ. Nếu người dùng hoặc hệ thống kết thúc ứng dụng khi nó đang trong Suspended, cả applicationWillTerminate và applicationDidEnterBackground đều không được gọi lại. Do đó, tất cả việc lưu dữ liệu phải diễn ra trong applicationDidEnterBackground, không phải trong applicationWillTerminate.

swift
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    // Lời gọi được đảm bảo cuối cùng trước Suspended
    func applicationDidEnterBackground(_ application: UIApplication) {
        // Lưu mọi thứ cần sống sót qua việc dỡ bỏ bộ nhớ
        savePersistentState()
        saveNavigationStack()

        // Yêu cầu thêm thời gian nếu cần
        let task = application.beginBackgroundTask {
            application.endBackgroundTask(task)
        }
    }

    // Quay lại từ Suspended — khởi động nóng
    func applicationWillEnterForeground(_ application: UIApplication) {
        // Ứng dụng đã ở trong Suspended, tiếp tục công việc
        print("Quay lại từ Suspended hoặc Background")
    }

    // Khôi phục hoàn toàn sau khi dỡ bỏ bộ nhớ
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        // Nếu đây là khởi động nguội sau khi dỡ bỏ khỏi Suspended —
        // khôi phục state restoration
        return true
    }

    private func savePersistentState() {
        UserDefaults.standard.set(Date(), forKey: "lastActiveDate")
    }

    private func saveNavigationStack() {
        guard let rootVC = window?.rootViewController else { return }
        // Lưu ngăn xếp điều hướng hiện tại
        if let navController = rootVC as? UINavigationController {
            let vcClasses = navController.viewControllers.map { type(of: $0) }
            UserDefaults.standard.set(vcClasses.map { NSStringFromClass($0) }, forKey: "navStack")
        }
    }
}

Mã cho thấy việc xử lý cực kỳ quan trọng của Suspended trên iOS. applicationDidEnterBackground là lời gọi được đảm bảo cuối cùng. Tất cả việc lưu dữ liệu nên diễn ra ở đây: trạng thái người dùng, ngăn xếp điều hướng, bản nháp, bộ đếm thời gian. applicationWillEnterForeground được gọi khi quay lại từ Suspended hoặc Background. didFinishLaunchingWithOptions — chỉ khi khởi động nguội, khi ứng dụng đã bị dỡ bỏ khỏi bộ nhớ sau Suspended.

Suspended trong Android — có tương tự không

Trong Android, không có tương tự trực tiếp của Suspended iOS. Android không đông băng các ứng dụng trong bộ nhớ trong khi vẫn bảo toàn ngữ cảnh thực thi. Thay vào đó, Android giữ tiến trình trong nền hoặc kết thúc nó. Tuy nhiên, trên Android 11+ (API 30), một cơ chế gọi là App Freezer đã được giới thiệu, tạm dừng thực thi các tiến trình nền bằng tín hiệu SIGSTOP. Điều này tương tự về mặt chức năng với Suspended, nhưng có những khác biệt quan trọng.

App Freezer là một phần của hệ thống quản lý bộ nhớ Android. Khi một ứng dụng ở trong nền lâu mà không có thông báo hoạt động, hệ thống gửi SIGSTOP, tạm dừng tất cả các luồng. Khi ứng dụng quay lại nền trước, SIGCONT được gửi và việc thực thi tiếp tục. Sự khác biệt chính với iOS: App Freezer không đảm bảo bảo toàn trạng thái — dữ liệu trong bộ nhớ có thể bị mất nếu tiến trình bị kết thúc trong quá trình đông băng.

Trên Android, nên sử dụng SavedStateHandle trong ViewModel để tự động bảo toàn trạng thái trong quá trình kết thúc tiến trình. SavedStateHandle lưu dữ liệu trong Bundle qua onSaveInstanceState, sống sót qua cả App Freezer và Process Death. Không giống iOS, nơi việc dỡ bỏ khỏi Suspended là một tình huống ngoại lệ, trên Android Process Death là hành vi bình thường luôn phải được mong đợi.

kotlin
// SavedStateHandle — cứu tinh khỏi Process Death trên Android
class CheckoutViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    // Trạng thái sống sót qua tiến trình ngay cả sau App Freezer
    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
    }
}

// Lưu trong onStop phòng trường hợp App Freezer
class MainActivity : AppCompatActivity() {

    override fun onStop() {
        super.onStop()
        // Lưu dữ liệu cần sống sót qua đông băng
        saveDraftData()
        // Giải phóng tài nguyên không cần thiết trong trạng thái đông băng
        releaseHeavyResources()
        // Cảnh báo rằng ứng dụng sẽ bị đông băng
        // (Ghi log để gỡ lỗi)
        Log.d("Lifecycle", "Activity stopped — có thể App Freeze")
    }
}

Mã cho thấy cách tiếp cận để xử lý tương tự Suspended trên Android. SavedStateHandle trong ViewModel tự động lưu và khôi phục dữ liệu trong quá trình Process Death. onStop là sự kiện được đảm bảo cuối cùng trước khi có thể xảy ra App Freezer hoặc kết thúc tiến trình. Trạng thái biểu mẫu thanh toán, danh sách mặt hàng trong giỏ hàng — tất cả dữ liệu này sống sót qua quá trình đông băng nhờ SavedStateHandle. Đối với các tài nguyên nặng (bitmap, con trỏ DB), onStop là nơi để giải phóng bộ nhớ.

State Restoration: khôi phục sau Suspended

State Restoration là cơ chế tích hợp sẵn của iOS để lưu và khôi phục trạng thái UI sau khi ứng dụng bị dỡ bỏ khỏi bộ nhớ. Nếu ứng dụng ở trong Suspended và hệ thống đã dỡ bỏ nó, ở lần khởi động nguội tiếp theo, state restoration khôi phục ngăn xếp điều hướng, vị trí cuộn, trạng thái biểu mẫu và các thành phần UI khác. Người dùng quay lại cùng một màn hình nơi họ đã dừng lại.

State Restoration hoạt động thông qua các giao thức UIViewControllerRestorationUIStateRestoring. Nhà phát triển gán restorationIdentifier cho mỗi ViewController và View mà họ muốn khôi phục. Khi vào nền, iOS mã hóa trạng thái của các đối tượng này. Khi quay lại sau khi dỡ bỏ, iOS tạo các đối tượng mới và giải mã trạng thái đã lưu. Nếu không có state restoration, người dùng sẽ thấy màn hình trống sau khi khởi động nguội thay vì nơi họ đã dừng lại.

swift
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
            // Khôi phục vị trí sau khi tải dữ liệu
        }
    }
}

// AppDelegate — kích hoạt State Restoration
func application(
    _ application: UIApplication,
    shouldSaveSecureApplicationState coder: NSCoder
) -> Bool {
    return true
}

func application(
    _ application: UIApplication,
    shouldRestoreSecureApplicationState coder: NSCoder
) -> Bool {
    return true
}

Mã cho thấy việc triển khai State Restoration trên iOS. restorationIdentifierrestorationClass là cần thiết cho mỗi ViewController có thể khôi phục. encodeRestorableState/decodeRestorableState lưu và tải dữ liệu qua NSCoder. Trong AppDelegate, shouldSaveSecureApplicationState và shouldRestoreSecureApplicationState bật tính năng lưu trạng thái đã mã hóa. Trên iOS 12+, nên sử dụng mã hóa an toàn (NSSecureCoding) để bảo vệ dữ liệu.

Các phương pháp tốt nhất khi làm việc với Suspended

Quy tắc đầu tiên — không bao giờ cho rằng ứng dụng sẽ quay lại từ Suspended. Hệ thống có thể dỡ bỏ ứng dụng bất cứ lúc nào. Tất cả dữ liệu cực kỳ quan trọng phải được lưu vào bộ nhớ lâu dài trước khi chuyển sang Suspended — tức là trong applicationDidEnterBackground hoặc onStop. UserDefaults, Core Data, File Manager là các tùy chọn lưu trữ phù hợp. Bộ nhớ (biến, thuộc tính) là bộ nhớ không đáng tin cậy cho dữ liệu cần sống sót qua Suspended.

Quy tắc thứ hai — giải phóng tài nguyên trước Suspended. Đóng các bộ mô tả tệp, giải phóng bộ nhớ GPU (Metal, Core Graphics), đóng các kết nối mạng. Mặc dù ứng dụng không tiêu thụ CPU trong Suspended, các tài nguyên đang chiếm dụng sẽ bị chặn đối với các ứng dụng khác. Trên iOS, không thể giữ socket mở trong Suspended — khi quay lại từ Suspended, chúng có thể không hoạt động, gây ra lỗi.

Quy tắc thứ ba — không đặt logic phụ thuộc thời gian vào việc chờ đợi quay lại từ Suspended. Bộ đếm thời gian, callback và hoạt động mạng ngừng lại trong Suspended. Nếu ứng dụng ở trong Suspended vài giờ, khi quay lại, bộ đếm thời gian có thể kích hoạt không chính xác. Kiểm tra tính hợp lệ của dữ liệu khi quay lại — bộ nhớ đệm có thể lỗi thời và mã thông báo có thể hết hạn.

Quy tắc thứ tư — sử dụng State Restoration cho tất cả các màn hình, đặc biệt là biểu mẫu nhập liệu, danh sách có thể cuộn và màn hình chi tiết. Nếu không có state restoration, sau khi quay lại từ Suspended đã bị dỡ bỏ, người dùng sẽ thấy màn hình ban đầu của ứng dụng thay vì nơi họ đã dừng lại. Điều này làm giảm trải nghiệm người dùng và buộc người dùng phải lặp lại các thao tác.

swift
import UIKit

// Kiểm tra: ứng dụng có bị dỡ bỏ khỏi bộ nhớ không?
func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
    // Kiểm tra xem có trạng thái đã lưu không
    if UserDefaults.standard.object(forKey: "navStack") != nil {
        // Ứng dụng đã bị dỡ bỏ khỏi Suspended
        // Cần khôi phục trạng thái
        restoreNavigationStack()
    } else {
        // Khởi động nguội sạch từ 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)
        }
    }
}

Mã cho thấy phương pháp xác định liệu ứng dụng có bị dỡ bỏ khỏi Suspended hay không. Kiểm tra UserDefaults về sự hiện diện của ngăn xếp điều hướng đã lưu cho phép phân biệt khởi động nguội sau khi dỡ bỏ với khởi động nguội sạch. Trong trường hợp đầu tiên, ngăn xếp điều hướng được khôi phục; trong trường hợp thứ hai, màn hình giới thiệu hoặc màn hình chính được hiển thị. Cách tiếp cận này bổ sung cho State Restoration tích hợp sẵn cho các trường hợp NSCoder không đủ.

Câu hỏi thường gặp

Ứng dụng có thể ở trong Suspended bao lâu?

Không giới hạn — từ vài giây đến vài ngày. iOS không có thời gian chờ cho Suspended. Ứng dụng sẽ ở trong bộ nhớ cho đến khi hệ thống quyết định dỡ bỏ nó do thiếu tài nguyên. Trong thực tế, các ứng dụng ở trong Suspended từ 15 phút đến vài giờ, tùy thuộc vào RAM của thiết bị và số lượng ứng dụng đang hoạt động.

applicationWillTerminate có được gọi khi dỡ bỏ khỏi Suspended không?

Không. applicationWillTerminate không được gọi khi ứng dụng bị dỡ bỏ khỏi Suspended. Hệ thống chỉ đơn giản giải phóng bộ nhớ mà không thông báo cho ứng dụng. Đây là một lý do nữa tại sao tất cả việc lưu dữ liệu phải diễn ra trong applicationDidEnterBackground. applicationWillTerminate chỉ được gọi khi người dùng kết thúc thủ công ứng dụng bằng cách vuốt nó ra khỏi App Switcher.

Android có tương tự Suspended không?

Không có tương tự trực tiếp. Trên Android 11+, App Freezer đã được giới thiệu, tạm dừng các tiến trình nền qua SIGSTOP — điều này tương tự về mặt chức năng với Suspended. Tuy nhiên, các ứng dụng Android nên được thiết kế với giả định Process Death có thể xảy ra bất cứ lúc nào. Sử dụng SavedStateHandle trong ViewModel và onSaveInstanceState để lưu trạng thái sống sót qua cả App Freezer và Process Death.

Làm thế nào để kiểm tra xem ứng dụng có ở trong Suspended không?

Trong iOS, không có API trực tiếp để kiểm tra. Một phương pháp gián tiếp: kiểm tra UserDefaults về sự hiện diện của trạng thái đã lưu trong didFinishLaunchingWithOptions. Nếu trạng thái tồn tại — ứng dụng đã bị dỡ bỏ khỏi Suspended và đang khởi động nguội. Nếu không có trạng thái — khởi động nguội sạch. Trong SwiftUI, có thể lưu một cờ trong scenePhase.background và kiểm tra nó ở lần khởi chạy tiếp theo.

Ảnh chụp màn hình (snapshot) trong ngữ cảnh Suspended là gì?

Khi chuyển sang Suspended, iOS chụp ảnh chụp màn hình — ảnh chụp giao diện ứng dụng hiện tại. Ảnh chụp này được hiển thị trong App Switcher và khi quay lại ứng dụng (dưới dạng hoạt ảnh “rã đông”). Nếu ứng dụng chứa dữ liệu bảo mật, ảnh chụp có thể lộ ra. Để bảo vệ, hãy sử dụng UIApplication.shouldSnapshotSecureApp (iOS 16+) hoặc áp dụng lớp phủ làm mờ trong applicationDidEnterBackground.

Tổng kết

  • Suspended — ứng dụng bị đông băng trong bộ nhớ iOS, mã không được thực thi, nhưng ngăn xếp UI được bảo toàn để khôi phục tức thì
  • Tính độc đáo của iOS — Suspended không có trong Android; Android sử dụng App Freezer (SIGSTOP) như một tương tự một phần
  • Dỡ bỏ — hệ thống dỡ bỏ các ứng dụng Suspended ưu tiên hàng đầu khi thiếu bộ nhớ, không có thông báo
  • Lưu trữ — applicationDidEnterBackground là phương thức được đảm bảo cuối cùng; tất cả dữ liệu nên được lưu ở đây
  • State Restoration — cơ chế dựa trên NSCoder để tự động khôi phục UI sau khi dỡ bỏ khỏi Suspended
  • Thay thế trên Android — SavedStateHandle + onSaveInstanceState để sống sót qua Process Death
  • Ảnh chụp màn hình — iOS chụp ảnh màn hình trong Suspended; dữ liệu bảo mật cần được ẩn qua lớp phủ làm mờ hoặc ảnh chụp an toàn

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm