Not Running — the initial lifecycle state of a mobile application when it has not been launched yet or has already terminated. Learn how iOS and Android manage this state, what events lead to a transition from Not Running, and how to properly handle application launch and termination in Swift and Kotlin.
Key Takeaways
Not Running is the basic lifecycle state of a mobile application in which it is not loaded into the device’s RAM and does not consume system resources. In iOS and Android, this state means the complete absence of processes and threads associated with the application. The user sees the app icon on the home screen, but the application itself is not active and is not in the recent apps list.
When the user taps the app icon, the system creates a new process, loads the executable code into memory, and initializes all necessary data structures. This process is called a cold start and is the most resource-intensive in terms of load time.
The system can move the application to Not Running from any other state. If the application is in the background or suspended, the operating system has the right to unload it when there is insufficient RAM for higher-priority tasks — for example, for an active foreground application.
The developer must consider that the application can be terminated by the system at any moment when it is in the background. This means that all unsaved data may be lost. Therefore, it is critically important to save state in key-value stores (UserDefaults, SharedPreferences) or a local database during transitions from Active to Background.
iOS uses priorities based on the current application state: Active has the highest priority, followed by Inactive, Background, Suspended, and finally Not Running — the lowest priority. Android uses a similar process hierarchy: Foreground process has priority OOM_ADJ = 0, Visible process = 100, Service process = 200, Background process = 300, Empty process = 400. The higher the value, the more likely the process will be terminated when memory is low.
| Platform | State | Unload Priority | Description |
|---|---|---|---|
| iOS | Not Running | Highest | Application not loaded — no system resources consumed |
| iOS | Suspended | High | Application in memory but code not executing — first target for unloading |
| iOS | Background | Medium | Application executing a background task — unloaded after timeout |
| iOS | Active | Low | Active application — unloaded only under critical memory pressure |
| Android | Empty Process | Highest | Process with no active components — removed first |
| Android | Background Process | High | Background process without a visible Activity |
| Android | Foreground Service | Low | Service with a notification — rarely terminated |
| Android | Foreground Process | Minimum | Active Activity — terminated last |
Cold start occurs when the application transitions from Not Running directly to Active. The system creates a new process, loads classes, initializes static fields, creates the main thread, and starts the UI framework. On iOS, this means calling application(_:didFinishLaunchingWithOptions:), on Android — calling Application.onCreate() and Activity.onCreate(). Cold start time can range from 200 ms to several seconds depending on the application’s complexity.
Warm start (or hot start) — the application was in the Suspended state and resumes without a full reload. The system restores the last UI stack from memory, and the user continues working from the same point. A warm start is significantly faster than a cold start because most of the code is already loaded into memory. On iOS, a warm start does not call application(_:didFinishLaunchingWithOptions:), only applicationWillEnterForeground and applicationDidBecomeActive.
The difference between cold and warm start is critical for user experience. During a cold start, the developer must ensure that the launch happens as quickly as possible — lazy initialization of modules, deferred loading of heavy resources, minimizing work on the main thread at startup. Google recommends a cold start of no more than 500 ms, Apple — no more than 400 ms for iOS.
// Measuring cold start time in Android
class App : Application() {
private var startTime: Long = 0L
override fun onCreate() {
super.onCreate()
startTime = System.currentTimeMillis()
}
fun getStartupTime(): Long {
return System.currentTimeMillis() - startTime
}
}
// Starting Activity with lazy initialization
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)
// Only the necessary minimum for the first frame
setupNavigation()
}
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
// Heavy initialization after rendering
initializeHeavyModules()
}
}The example shows measuring cold start time in Android. Application.onCreate() is called when transitioning from Not Running to Active. The timestamp is recorded at process start. The Activity uses lazy initialization via a lazy delegate to avoid blocking the first frame. onPostCreate is the optimal place to initialize heavy modules since the UI has already been rendered.
In iOS, Not Running is managed through the UIApplicationDelegate protocol. Key methods: application(_:didFinishLaunchingWithOptions:) is called after a cold start, applicationWillTerminate(_:) is called before the user terminates the application. However, the system may terminate the application without calling applicationWillTerminate — for example, during an emergency termination or memory pressure. iOS does not guarantee that this method will be called, so data should be saved in applicationDidEnterBackground.
The user can manually terminate the application with a swipe in the App Switcher. The system can unload the application from memory while in the background. The application may crash. In all cases, all objects created during launch are destroyed. State that was not saved is lost forever. In iOS 13+, it is recommended to use NSUserActivity or the state restoration mechanism via UIApplication.stateRestorationIdentifier to preserve state.
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// Cold start: application transitioned from Not Running
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Initializing the minimum set of services
setupAnalytics()
configureAppearance()
return true
}
// Application terminates — only manual close
func applicationWillTerminate(
_ application: UIApplication
) {
saveCriticalData()
}
// Saving data before going to the background
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
)
}
}The code demonstrates correct Not Running handling on iOS. applicationWillTerminate is only called when the user manually terminates the app. Saving critical data is duplicated in applicationDidEnterBackground since this method is guaranteed to be called before going to the background. State restoration allows saving the UI stack for later recovery during a cold start.
In Android, Not Running means the application process does not exist. The Linux system underlying Android manages processes through the Zygote mechanism. When an application launches, Zygote forks a new process, loads Dalvik/ART, and calls Application.onCreate(). Android does not have a direct equivalent of applicationWillTerminate — the system can terminate the process at any time without warning.
When an Activity is first called, the system creates the process, Application, and Activity through the chain onCreate → onStart → onResume. If the user presses Back, the Activity is destroyed (onDestroy), and the process may be terminated by the system. A key difference from iOS: in Android, the process can continue to exist even without active Activities — for example, if a Foreground Service is running or there is an active BroadcastReceiver.
// Handling Not Running via SavedStateHandle in 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 — the first callback after Not Running
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
initCrashReporter()
initDependencyInjection()
}
}SavedStateHandle is a component of Android Architecture Components that automatically saves state during a transition to Not Running and restores it on a cold start. A ViewModel created via ViewModelProvider survives screen rotation and Activity destruction. When the process terminates, data from SavedStateHandle is serialized into a Bundle and saved in the saved instance state.
Not Running occurs for several reasons. The user manually closes the application. The system unloads the application due to low memory. The application crashes with an exception. On Android, the system may terminate the process during a mass app update or device reboot. iOS may terminate the application when a background task times out (usually 30 seconds).
| Reason | iOS | Android | Can Be Prevented |
|---|---|---|---|
| Manual close by user | Swipe in App Switcher | Swipe from Recents | No — user action |
| Low memory | Memory warning triggered | onTrimMemory / LMK | Partially — memory optimization |
| Application crash | NSException / signal | UncaughtException / ANR | Yes — error handling and crash reporting |
| Background task timeout | 30 sec for Background task | 10 min for JobScheduler | Yes — proper task scheduling |
| OS reboot | applicationWillTerminate called | Broadcast ACTION_SHUTDOWN | No — system event |
| App update | Does not occur (iOS Sandbox) | Process terminated on APK update | No — system update |
For iOS, use console logging in applicationWillTerminate and applicationDidFinishLaunching. Add a flag in UserDefaults on each launch — if the flag is missing on the next start, the application was terminated improperly. On Android, use ActivityManager.isBackgroundRestricted() to check whether the application can run background tasks. Also, monitor onTrimMemory(TRIM_MEMORY_COMPLETE) — this is a signal that the process will be terminated.
First rule — never assume that applicationWillTerminate or onDestroy will be called. Save critically important data on every transition from Active to Background. Use key-value stores for simple settings and SQLite/Room for structured data.
Second rule — measure cold start time and optimize it. Lazy initialization, minimizing work on the main thread, preloading resources, using the SplashScreen API — all of these improve perceived launch time. Google recommends a cold start of less than 200 ms for excellent UX.
Third rule — implement State Restoration. On iOS, use UIApplication.stateRestorationIdentifier and NSUserActivity. On Android, use SavedStateHandle in ViewModel combined with onSaveInstanceState. This will allow the user to continue working from the same point after an app restart.
Fourth rule — handle launchOptions and Intent with which the application was started after Not Running. Deep links, push notifications, universal links — all of these are passed through launch parameters. The developer must correctly extract this data and navigate the user to the appropriate screen.
// Handling deep link after cold start
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Checking if a notification arrived
if let notification = launchOptions?[.remoteNotification] as? [String: Any] {
handleNotification(notification)
}
// Checking 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)
}The code shows handling of launch parameters on iOS cold start. launchOptions contains the data with which the system launched the application. Notifications, deep links, and universal links are passed through this dictionary. The developer must correctly handle all possible launch scenarios to ensure a seamless user experience.
Frequently Asked Questions
Data that was saved to persistent storage (UserDefaults, Core Data, SharedPreferences, Room) is preserved. Data in RAM — variables, cache, ViewModel state without SavedStateHandle — is irretrievably lost. Therefore, it is critically important to save application state on every transition to the Background.
During a cold start, application(_:didFinishLaunchingWithOptions:) is called. During a hot start (return from Suspended), this method is not called — only applicationWillEnterForeground and applicationDidBecomeActive are triggered. If you need to perform an action only on a cold start, set a flag in didFinishLaunchingWithOptions.
Yes. A Foreground Service with a persistent notification prevents the system from terminating the process, even if all Activities are destroyed. A Background Service (startService without foreground) can be stopped by the system at any time. A running Service means the process exists, and this is no longer Not Running.
On the iOS simulator, terminate the app through the App Switcher (Cmd+Shift+H twice, swipe up). On the Android emulator, use adb shell am force-stop com.example.app or the Stop button in Logcat. After that, launch the application again — this will be a clean cold start from Not Running.
Kill-switch is a server command for emergency application termination. It is used in banking and enterprise applications for remote access blocking. If the application receives a kill command, on the next cold start it blocks the UI and requests reauthentication. On iOS, a kill-switch is implemented via remote notifications with a blocking flag.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also