Not Running — nó là gì, trạng thái ban đầu của vòng đời

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

Not Running — trạng thái ban đầu của vòng đời ứng dụng di động khi nó chưa được khởi chạy hoặc đã kết thúc công việc. Tìm hiểu cách iOS và Android quản lý trạng thái này, sự kiện nào dẫn đến chuyển đổi từ Not Running và cách xử lý đúng cách việc khởi chạy và kết thúc ứng dụng trong Swift và Kotlin.

Điểm chính

  • Not Running — ứng dụng không được tải vào bộ nhớ và không thực thi mã; đó là điểm vào và ra của vòng đời
  • Khởi chạy — chuyển đổi từ Not Running xảy ra khi chạm vào biểu tượng ứng dụng, qua deep link hoặc thông báo push
  • Kết thúc — người dùng đóng ứng dụng bằng cách vuốt, hệ thống dỡ bỏ nó do thiếu bộ nhớ hoặc xảy ra sự cố
  • Khởi động nguội — ứng dụng khởi động từ đầu, tất cả các đối tượng được tạo lại, trạng thái không được khôi phục từ bộ nhớ đệm
  • Khởi động nóng — ứng dụng ở trạng thái Suspended và quay lại Active mà không cần khởi tạo đầy đủ

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

Not Running là trạng thái cơ bản của vòng đời ứng dụng di động khi nó không được tải vào RAM của thiết bị và không tiêu thụ tài nguyên hệ thống. Trong iOS và Android, trạng thái này có nghĩa là hoàn toàn không có tiến trình và luồng liên quan đến ứng dụng. Người dùng nhìn thấy biểu tượng ứng dụng trên màn hình chính, nhưng bản thân ứng dụng không hoạt động và không có trong danh sách ứng dụng gần đây.

Khi người dùng chạm vào biểu tượng ứng dụng, hệ thống tạo một tiến trình mới, tải mã thực thi vào bộ nhớ và khởi tạo tất cả cấu trúc dữ liệu cần thiết. Quá trình này được gọi là khởi động nguội (cold start) và tốn nhiều tài nguyên nhất về thời gian tải.

Hệ thống có thể di chuyển ứng dụng sang Not Running từ bất kỳ trạng thái nào khác. Nếu ứng dụng đang ở nền hoặc bị tạm dừng, hệ điều hành có quyền dỡ bỏ nó khi không đủ RAM cho các tác vụ có ưu tiên cao hơn — ví dụ, cho một ứng dụng đang hoạt động ở tiền cảnh.

Nhà phát triển phải cân nhắc rằng ứng dụng có thể bị hệ thống kết thúc bất cứ lúc nào khi đang ở nền. Điều này có nghĩa là tất cả dữ liệu chưa được lưu có thể bị mất. Do đó, việc lưu trạng thái trong các kho lưu trữ key-value (UserDefaults, SharedPreferences) hoặc cơ sở dữ liệu cục bộ trong quá trình chuyển đổi từ Active sang Background là cực kỳ quan trọng.

Cách hệ thống xác định ứng dụng nào sẽ bị dỡ bỏ

iOS sử dụng các mức ưu tiên dựa trên trạng thái hiện tại của ứng dụng: Active có ưu tiên cao nhất, tiếp theo là Inactive, Background, Suspended và cuối cùng là Not Running — ưu tiên thấp nhất. Android sử dụng hệ phân cấp tiến trình tương tự: tiến trình nền trước có ưu tiên OOM_ADJ = 0, tiến trình Visible = 100, tiến trình Service = 200, tiến trình Background = 300, tiến trình Empty = 400. Giá trị càng cao, tiến trình càng có khả năng bị kết thúc khi bộ nhớ thấp.

Nền tảngTrạng tháiƯu tiên dỡ bỏMô tả
iOSNot RunningCao nhấtỨng dụng không được tải — không tiêu thụ tài nguyên hệ thống
iOSSuspendedCaoỨng dụng trong bộ nhớ nhưng không thực thi mã — mục tiêu dỡ bỏ đầu tiên
iOSBackgroundTrung bìnhỨng dụng đang thực thi tác vụ nền — bị dỡ bỏ sau thời gian chờ
iOSActiveThấpỨng dụng đang hoạt động — chỉ bị dỡ bỏ khi áp lực bộ nhớ nghiêm trọng
AndroidEmpty ProcessCao nhấtTiến trình không có thành phần hoạt động — bị loại bỏ đầu tiên
AndroidBackground ProcessCaoTiến trình nền không có Activity hiển thị
AndroidForeground ServiceThấpDịch vụ có thông báo — hiếm khi bị kết thúc
AndroidForeground ProcessTối thiểuActivity đang hoạt động — bị kết thúc cuối cùng

Khởi động nguội và khởi động nóng của ứng dụng

Khởi động nguội (cold start) xảy ra khi ứng dụng chuyển từ Not Running trực tiếp sang Active. Hệ thống tạo một tiến trình mới, tải các lớp, khởi tạo các trường tĩnh, tạo luồng chính và khởi động khung UI. Trên iOS, điều này có nghĩa là gọi application(_:didFinishLaunchingWithOptions:), trên Android — gọi Application.onCreate() và Activity.onCreate(). Thời gian khởi động nguội có thể dao động từ 200 ms đến vài giây tùy thuộc vào độ phức tạp của ứng dụng.

Khởi động nóng (warm start hoặc hot start) — ứng dụng ở trạng thái Suspended và tiếp tục mà không cần tải lại hoàn toàn. Hệ thống khôi phục ngăn xếp UI cuối cùng từ bộ nhớ và người dùng tiếp tục làm việc từ cùng một vị trí. Khởi động nóng nhanh hơn đáng kể so với khởi động nguội vì hầu hết mã đã được tải vào bộ nhớ. Trên iOS, khởi động nóng không gọi application(_:didFinishLaunchingWithOptions:), chỉ gọi applicationWillEnterForeground và applicationDidBecomeActive.

Sự khác biệt giữa khởi động nguội và khởi động nóng rất quan trọng đối với trải nghiệm người dùng. Trong quá trình khởi động nguội, nhà phát triển phải đảm bảo rằng việc khởi chạy diễn ra nhanh nhất có thể — khởi tạo chậm các mô-đun, tải chậm các tài nguyên nặng, giảm thiểu công việc trên luồng chính khi khởi động. Google khuyến nghị khởi động nguội không quá 500 ms, Apple — không quá 400 ms cho iOS.

kotlin
// Đo thời gian khởi động nguội trong Android
class App : Application() {
    private var startTime: Long = 0L

    override fun onCreate() {
        super.onCreate()
        startTime = System.currentTimeMillis()
    }

    fun getStartupTime(): Long {
        return System.currentTimeMillis() - startTime
    }
}

// Khởi chạy Activity với khởi tạo chậm
class MainActivity : AppCompatActivity() {
    private val viewModel: MainViewModel by lazy {
        ViewModelProvider(this).get(MainViewModel::class.java)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Chỉ mức tối thiểu cần thiết cho khung hình đầu tiên
        setupNavigation()
    }

    override fun onPostCreate(savedInstanceState: Bundle?) {
        super.onPostCreate(savedInstanceState)
        // Khởi tạo nặng sau khi hiển thị
        initializeHeavyModules()
    }
}

Ví dụ cho thấy việc đo thời gian khởi động nguội trong Android. Application.onCreate() được gọi khi chuyển từ Not Running sang Active. Dấu thời gian được ghi lại khi bắt đầu tiến trình. Activity sử dụng khởi tạo chậm thông qua ủy quyền lazy để tránh chặn khung hình đầu tiên. onPostCreate là nơi tối ưu để khởi tạo các mô-đun nặng vì UI đã được hiển thị.

Not Running trong iOS: Swift và AppDelegate

Trong iOS, Not Running được quản lý thông qua giao thức UIApplicationDelegate. Các phương thức chính: application(_:didFinishLaunchingWithOptions:) được gọi sau khi khởi động nguội, applicationWillTerminate(_:) được gọi trước khi người dùng kết thúc ứng dụng. Tuy nhiên, hệ thống có thể kết thúc ứng dụng mà không gọi applicationWillTerminate — ví dụ, trong quá trình kết thúc khẩn cấp hoặc áp lực bộ nhớ. iOS không đảm bảo rằng phương thức này sẽ được gọi, vì vậy dữ liệu nên được lưu trong applicationDidEnterBackground.

Các kịch bản chuyển đổi sang Not Running trên iOS

Người dùng có thể kết thúc thủ công ứng dụng bằng cách vuốt trong App Switcher. Hệ thống có thể dỡ bỏ ứng dụng khỏi bộ nhớ khi đang ở nền. Ứng dụng có thể bị sự cố. Trong mọi trường hợp, tất cả các đối tượng được tạo trong quá trình khởi chạy đều bị phá hủy. Trạng thái không được lưu sẽ bị mất vĩnh viễn. Trong iOS 13+, bạn nên sử dụng NSUserActivity hoặc cơ chế khôi phục trạng thái thông qua UIApplication.stateRestorationIdentifier để bảo toàn trạng thái.

swift
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    // Khởi động nguội: ứng dụng đã chuyển từ Not Running
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        // Khởi tạo tập hợp dịch vụ tối thiểu
        setupAnalytics()
        configureAppearance()
        return true
    }

    // Ứng dụng kết thúc — chỉ đóng thủ công
    func applicationWillTerminate(
        _ application: UIApplication
    ) {
        saveCriticalData()
    }

    // Lưu dữ liệu trước khi vào nền
    func applicationDidEnterBackground(
        _ application: UIApplication
    ) {
        saveApplicationState()
    }

    private func saveCriticalData() {
        UserDefaults.standard.synchronize()
    }

    private func saveApplicationState() {
        let state = ["lastScreen": "main", "timestamp": Date()]
        try? NSKeyedArchiver.archivedData(
            withRootObject: state,
            requiringSecureCoding: true
        )
    }
}

Mã nguồn cho thấy cách xử lý Not Running đúng cách trên iOS. applicationWillTerminate chỉ được gọi khi người dùng kết thúc thủ công ứng dụng. Việc lưu dữ liệu quan trọng được nhân đôi trong applicationDidEnterBackground vì phương thức này được đảm bảo sẽ được gọi trước khi vào nền. Khôi phục trạng thái cho phép lưu ngăn xếp UI để khôi phục sau trong quá trình khởi động nguội.

Not Running trong Android: Kotlin và tiến trình

Trong Android, Not Running có nghĩa là tiến trình ứng dụng không tồn tại. Hệ thống Linux nền tảng của Android quản lý các tiến trình thông qua cơ chế Zygote. Khi một ứng dụng được khởi chạy, Zygote tạo ra một tiến trình mới, tải Dalvik/ART và gọi Application.onCreate(). Android không có tương đương trực tiếp của applicationWillTerminate — hệ thống có thể kết thúc tiến trình bất cứ lúc nào mà không báo trước.

Vòng đời tiến trình Android

Khi một Activity được gọi lần đầu tiên, hệ thống tạo tiến trình, Application và Activity thông qua chuỗi onCreate → onStart → onResume. Nếu người dùng nhấn Quay lại, Activity bị phá hủy (onDestroy) và tiến trình có thể bị hệ thống kết thúc. Một sự khác biệt chính so với iOS: trong Android, tiến trình có thể tiếp tục tồn tại ngay cả khi không có Activity nào hoạt động — ví dụ, nếu một Foreground Service đang chạy hoặc có một BroadcastReceiver đang hoạt động.

kotlin
// Xử lý Not Running qua SavedStateHandle trong ViewModel
class MainViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    companion object {
        private const val KEY_LAST_SCREEN = "last_screen"
        private const val KEY_USER_DATA = "user_data"
    }

    fun saveCurrentState(screen: String, data: String) {
        savedStateHandle[KEY_LAST_SCREEN] = screen
        savedStateHandle[KEY_USER_DATA] = data
    }

    fun restoreState(): AppState? {
        val screen = savedStateHandle.get<String>(KEY_LAST_SCREEN)
        val data = savedStateHandle.get<String>(KEY_USER_DATA)
        return if (screen != null && data != null) {
            AppState(screen, data)
        } else null
    }
}

// Application — callback đầu tiên sau Not Running
class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        initCrashReporter()
        initDependencyInjection()
    }
}

SavedStateHandle là một thành phần của Android Architecture Components tự động lưu trạng thái trong quá trình chuyển đổi sang Not Running và khôi phục trạng thái đó khi khởi động nguội. Một ViewModel được tạo thông qua ViewModelProvider sống sót qua việc xoay màn hình và hủy Activity. Khi tiến trình kết thúc, dữ liệu từ SavedStateHandle được tuần tự hóa thành Bundle và được lưu trong trạng thái thể hiện đã lưu.

Lý do chuyển đổi sang Not Running

Not Running xảy ra vì một số lý do. Người dùng đóng thủ công ứng dụng. Hệ thống dỡ bỏ ứng dụng do thiếu bộ nhớ. Ứng dụng bị sự cố với một ngoại lệ. Trên Android, hệ thống có thể kết thúc tiến trình trong quá trình cập nhật hàng loạt ứng dụng hoặc khởi động lại thiết bị. iOS có thể kết thúc ứng dụng khi một tác vụ nền hết thời gian chờ (thường là 30 giây).

Lý doiOSAndroidCó thể ngăn chặn
Đóng thủ công bởi người dùngVuốt trong App SwitcherVuốt từ RecentsKhông — hành động của người dùng
Thiếu bộ nhớKích hoạt cảnh báo bộ nhớonTrimMemory / LMKMột phần — tối ưu hóa bộ nhớ
Sự cố ứng dụngNSException / tín hiệuUncaughtException / ANRCó — xử lý lỗi và báo cáo sự cố
Hết thời gian tác vụ nền30 giây cho tác vụ nền10 phút cho JobSchedulerCó — lên lịch tác vụ đúng cách
Khởi động lại hệ điều hànhapplicationWillTerminate được gọiBroadcast ACTION_SHUTDOWNKhông — sự kiện hệ thống
Cập nhật ứng dụngKhông xảy ra (iOS Sandbox)Tiến trình kết thúc khi cập nhật APKKhông — cập nhật hệ thống

Cách chẩn đoán chuyển đổi sang Not Running

Đối với iOS, hãy sử dụng nhật ký bảng điều khiển trong applicationWillTerminate và applicationDidFinishLaunching. Thêm một cờ trong UserDefaults ở mỗi lần khởi chạy — nếu cờ bị thiếu ở lần khởi động tiếp theo, ứng dụng đã bị kết thúc không đúng cách. Trên Android, hãy sử dụng ActivityManager.isBackgroundRestricted() để kiểm tra xem ứng dụng có thể chạy các tác vụ nền hay không. Ngoài ra, hãy theo dõi onTrimMemory(TRIM_MEMORY_COMPLETE) — đây là tín hiệu cho thấy tiến trình sẽ bị kết thúc.

Các phương pháp tốt nhất để làm việc với Not Running

Quy tắc đầu tiên — không bao giờ cho rằng applicationWillTerminate hoặc onDestroy sẽ được gọi. Lưu dữ liệu quan trọng ở mỗi lần chuyển đổi từ Active sang Background. Sử dụng kho lưu trữ key-value cho các cài đặt đơn giản và SQLite/Room cho dữ liệu có cấu trúc.

Quy tắc thứ hai — đo thời gian khởi động nguội và tối ưu hóa nó. Khởi tạo chậm, giảm thiểu công việc trên luồng chính, tải trước tài nguyên, sử dụng API SplashScreen — tất cả điều này cải thiện nhận thức về thời gian khởi chạy. Google khuyến nghị khởi động nguội dưới 200 ms để có trải nghiệm người dùng tuyệt vời.

Quy tắc thứ ba — triển khai Khôi phục Trạng thái. Trên iOS, sử dụng UIApplication.stateRestorationIdentifier và NSUserActivity. Trên Android, sử dụng SavedStateHandle trong ViewModel kết hợp với onSaveInstanceState. Điều này sẽ cho phép người dùng tiếp tục làm việc từ cùng một vị trí sau khi khởi động lại ứng dụng.

Quy tắc thứ tư — xử lý launchOptions và Intent mà ứng dụng được khởi chạy sau Not Running. Các deep link, thông báo push, liên kết phổ quát — tất cả đều được truyền qua các tham số khởi chạy. Nhà phát triển phải trích xuất dữ liệu này một cách chính xác và điều hướng người dùng đến màn hình thích hợp.

swift
// Xử lý deep link sau khởi động nguội
func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
    // Kiểm tra xem thông báo đã đến chưa
    if let notification = launchOptions?[.remoteNotification] as? [String: Any] {
        handleNotification(notification)
    }
    // Kiểm tra deep link
    if let url = launchOptions?[.url] as? URL {
        handleDeepLink(url)
    }
    return true
}

private func handleDeepLink(_ url: URL) {
    guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
          let screenId = components.queryItems?.first(where: { $0.name == "screen" })?.value
    else { return }
    openScreen(screenId)
}

Mã nguồn cho thấy cách xử lý các tham số khởi chạy trong khởi động nguội iOS. launchOptions chứa dữ liệu mà hệ thống đã khởi chạy ứng dụng. Các thông báo, deep link và liên kết phổ quát được truyền qua từ điển này. Nhà phát triển phải xử lý chính xác tất cả các kịch bản khởi chạy có thể xảy ra để đảm bảo trải nghiệm người dùng liền mạch.

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

Điều gì xảy ra với dữ liệu khi chuyển đổi sang Not Running?

Dữ liệu được lưu trong bộ nhớ liên tục (UserDefaults, Core Data, SharedPreferences, Room) được bảo toàn. Dữ liệu trong RAM — biến, bộ nhớ đệm, trạng thái ViewModel không có SavedStateHandle — bị mất không thể khôi phục. Do đó, việc lưu trạng thái ứng dụng ở mỗi lần chuyển đổi sang Background là cực kỳ quan trọng.

Làm thế nào để phân biệt khởi động nguội và khởi động nóng trên iOS?

Trong quá trình khởi động nguội, application(_:didFinishLaunchingWithOptions:) được gọi. Trong quá trình khởi động nóng (quay lại từ Suspended), phương thức này không được gọi — chỉ có applicationWillEnterForeground và applicationDidBecomeActive được kích hoạt. Nếu bạn cần thực hiện một hành động chỉ khi khởi động nguội, hãy đặt một cờ trong didFinishLaunchingWithOptions.

Một ứng dụng Android có thể ở Not Running với một Service đang hoạt động không?

Có. Một Foreground Service với thông báo liên tục ngăn hệ thống kết thúc tiến trình, ngay cả khi tất cả các Activities đều bị phá hủy. Một Background Service (startService không có foreground) có thể bị hệ thống dừng bất cứ lúc nào. Một Service đang chạy có nghĩa là tiến trình đang tồn tại và đây không còn là Not Running nữa.

Làm thế nào để mô phỏng Not Running trên trình mô phỏng?

Trên trình mô phỏng iOS, hãy kết thúc ứng dụng qua App Switcher (Cmd+Shift+H hai lần, vuốt lên). Trên trình giả lập Android, hãy sử dụng adb shell am force-stop com.example.app hoặc nút Stop trong Logcat. Sau đó, khởi chạy lại ứng dụng — đây sẽ là một lần khởi động nguội sạch từ Not Running.

Kill-switch trong bối cảnh Not Running là gì?

Kill-switch là một lệnh máy chủ để kết thúc khẩn cấp ứng dụng. Nó được sử dụng trong các ứng dụng ngân hàng và doanh nghiệp để chặn truy cập từ xa. Nếu ứng dụng nhận được lệnh kill, trong lần khởi động nguội tiếp theo, nó sẽ chặn UI và yêu cầu xác thực lại. Trên iOS, kill-switch được triển khai thông qua thông báo từ xa với cờ chặn.

Tóm tắt

  • Not Running — trạng thái ban đầu và cuối cùng của vòng đời, ứng dụng không được tải vào bộ nhớ và không thực thi mã
  • Khởi động nguội — khởi động lại hoàn toàn ứng dụng từ Not Running, yêu cầu khởi tạo tất cả các thành phần từ đầu
  • Khởi động nóng — quay lại từ Suspended, không gọi didFinishLaunchingWithOptions hoặc Application.onCreate
  • Lưu dữ liệu — cực kỳ quan trọng khi chuyển đổi sang Background, vì Not Running có thể xảy ra bất cứ lúc nào
  • iOS — applicationWillTerminate không được đảm bảo, trạng thái được lưu qua UserDefaults hoặc khôi phục trạng thái
  • Android — tiến trình có thể bị kết thúc bất cứ lúc nào, SavedStateHandle trong ViewModel tự động lưu trạng thái
  • Tối ưu hóa khởi động — khởi tạo chậm, công việc tối thiểu trên luồng chính, API SplashScreen cho khung hình đầu tiên nhanh

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