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 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.
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ảng | Trạng thái | Ưu tiên dỡ bỏ | Mô tả |
|---|---|---|---|
| iOS | Not Running | Cao nhất | Ứng dụng không được tải — không tiêu thụ tài nguyên hệ thống |
| iOS | Suspended | Cao | Ứng dụng trong bộ nhớ nhưng không thực thi mã — mục tiêu dỡ bỏ đầu tiên |
| iOS | Background | Trung bình | Ứng dụng đang thực thi tác vụ nền — bị dỡ bỏ sau thời gian chờ |
| iOS | Active | Thấp | Ứng dụng đang hoạt động — chỉ bị dỡ bỏ khi áp lực bộ nhớ nghiêm trọng |
| Android | Empty Process | Cao nhất | Tiến trình không có thành phần hoạt động — bị loại bỏ đầu tiên |
| Android | Background Process | Cao | Tiến trình nền không có Activity hiển thị |
| Android | Foreground Service | Thấp | Dịch vụ có thông báo — hiếm khi bị kết thúc |
| Android | Foreground Process | Tối thiểu | Activity đang hoạt động — bị kết thúc cuối cù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.
// Đ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ị.
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.
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.
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.
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.
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.
// 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.
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ý do | iOS | Android | Có thể ngăn chặn |
|---|---|---|---|
| Đóng thủ công bởi người dùng | Vuốt trong App Switcher | Vuốt từ Recents | Khô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 / LMK | Một phần — tối ưu hóa bộ nhớ |
| Sự cố ứng dụng | NSException / tín hiệu | UncaughtException / ANR | Có — xử lý lỗi và báo cáo sự cố |
| Hết thời gian tác vụ nền | 30 giây cho tác vụ nền | 10 phút cho JobScheduler | Có — lên lịch tác vụ đúng cách |
| Khởi động lại hệ điều hành | applicationWillTerminate được gọi | Broadcast ACTION_SHUTDOWN | Không — sự kiện hệ thống |
| Cập nhật ứng dụng | Không xảy ra (iOS Sandbox) | Tiến trình kết thúc khi cập nhật APK | Không — cập nhật hệ thống |
Đố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.
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.
// 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
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.
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.
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.
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 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
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.
Đọc thêm