Not Running — el estado inicial del ciclo de vida de una aplicación móvil cuando aún no se ha iniciado o ya ha finalizado. Descubra cómo iOS y Android gestionan este estado, qué eventos provocan la transición desde Not Running y cómo manejar correctamente el inicio y la finalización de la aplicación en Swift y Kotlin.
Puntos clave
Not Running es el estado básico del ciclo de vida de una aplicación móvil en el que no está cargada en la RAM del dispositivo ni consume recursos del sistema. En iOS y Android, este estado significa la ausencia total de procesos e hilos asociados con la aplicación. El usuario ve el icono de la aplicación en la pantalla de inicio, pero la propia aplicación no está activa ni aparece en la lista de aplicaciones recientes.
Cuando el usuario toca el icono de la aplicación, el sistema crea un nuevo proceso, carga el código ejecutable en memoria e inicializa todas las estructuras de datos necesarias. Este proceso se llama arranque en frío (cold start) y es el que más recursos consume en términos de tiempo de carga.
El sistema puede mover la aplicación a Not Running desde cualquier otro estado. Si la aplicación está en segundo plano o suspendida, el sistema operativo tiene derecho a descargarla cuando no haya suficiente RAM para tareas de mayor prioridad, por ejemplo, para una aplicación activa en primer plano.
El desarrollador debe considerar que la aplicación puede ser finalizada por el sistema en cualquier momento cuando esté en segundo plano. Esto significa que todos los datos no guardados pueden perderse. Por lo tanto, es de vital importancia guardar el estado en almacenes clave-valor (UserDefaults, SharedPreferences) o en una base de datos local durante las transiciones de Active a Background.
iOS utiliza prioridades basadas en el estado actual de la aplicación: Active tiene la prioridad más alta, seguido de Inactive, Background, Suspended y, finalmente, Not Running — la prioridad más baja. Android utiliza una jerarquía de procesos similar: el proceso en primer plano tiene prioridad OOM_ADJ = 0, proceso Visible = 100, proceso Service = 200, proceso Background = 300, proceso Empty = 400. Cuanto mayor es el valor, más probable es que el proceso se finalice cuando falte memoria.
| Plataforma | Estado | Prioridad de descarga | Descripción |
|---|---|---|---|
| iOS | Not Running | Más alta | Aplicación no cargada — no consume recursos del sistema |
| iOS | Suspended | Alta | Aplicación en memoria pero sin ejecutar código — primer objetivo de descarga |
| iOS | Background | Media | Aplicación ejecutando una tarea en segundo plano — se descarga tras tiempo de espera |
| iOS | Active | Baja | Aplicación activa — se descarga solo con presión crítica de memoria |
| Android | Empty Process | Más alta | Proceso sin componentes activos — se elimina primero |
| Android | Background Process | Alta | Proceso en segundo plano sin Activity visible |
| Android | Foreground Service | Baja | Servicio con notificación — raramente finalizado |
| Android | Foreground Process | Mínima | Activity activo — se finaliza al último |
Arranque en frío (cold start) ocurre cuando la aplicación pasa de Not Running directamente a Active. El sistema crea un nuevo proceso, carga las clases, inicializa los campos estáticos, crea el hilo principal e inicia el framework de UI. En iOS, esto significa llamar a application(_:didFinishLaunchingWithOptions:), en Android — llamar a Application.onCreate() y Activity.onCreate(). El tiempo de arranque en frío puede oscilar entre 200 ms y varios segundos dependiendo de la complejidad de la aplicación.
Arranque en caliente (warm start o hot start) — la aplicación estaba en estado Suspended y se reanuda sin una recarga completa. El sistema restaura la última pila de UI desde la memoria y el usuario continúa trabajando desde el mismo punto. El arranque en caliente es significativamente más rápido que el arranque en frío porque la mayor parte del código ya está cargada en memoria. En iOS, un arranque en caliente no llama a application(_:didFinishLaunchingWithOptions:), solo a applicationWillEnterForeground y applicationDidBecomeActive.
La diferencia entre el arranque en frío y en caliente es crítica para la experiencia del usuario. Durante un arranque en frío, el desarrollador debe asegurarse de que el inicio ocurra lo más rápido posible — inicialización perezosa de módulos, carga diferida de recursos pesados, minimización del trabajo en el hilo principal al iniciar. Google recomienda un arranque en frío de no más de 500 ms, Apple — no más de 400 ms para iOS.
// Medición del tiempo de arranque en frío en Android
class App : Application() {
private var startTime: Long = 0L
override fun onCreate() {
super.onCreate()
startTime = System.currentTimeMillis()
}
fun getStartupTime(): Long {
return System.currentTimeMillis() - startTime
}
}
// Inicio de Activity con inicialización perezosa
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)
// Solo el mínimo necesario para el primer fotograma
setupNavigation()
}
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
// Inicialización pesada después del renderizado
initializeHeavyModules()
}
}El ejemplo muestra la medición del tiempo de arranque en frío en Android. Application.onCreate() se llama al pasar de Not Running a Active. La marca de tiempo se registra al iniciar el proceso. El Activity utiliza inicialización perezosa mediante un delegado lazy para no bloquear el primer fotograma. onPostCreate es el lugar óptimo para inicializar módulos pesados, ya que la UI ya se ha renderizado.
En iOS, Not Running se gestiona a través del protocolo UIApplicationDelegate. Métodos clave: application(_:didFinishLaunchingWithOptions:) se llama después de un arranque en frío, applicationWillTerminate(_:) se llama antes de que el usuario finalice la aplicación. Sin embargo, el sistema puede finalizar la aplicación sin llamar a applicationWillTerminate — por ejemplo, durante una finalización de emergencia o presión de memoria. iOS no garantiza que este método se llame, por lo que los datos deben guardarse en applicationDidEnterBackground.
El usuario puede finalizar manualmente la aplicación con un deslizamiento en el App Switcher. El sistema puede descargar la aplicación de la memoria mientras está en segundo plano. La aplicación puede bloquearse. En todos los casos, todos los objetos creados durante el inicio se destruyen. El estado que no se guardó se pierde para siempre. En iOS 13+, se recomienda usar NSUserActivity o el mecanismo de restauración de estado a través de UIApplication.stateRestorationIdentifier para preservar el estado.
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// Arranque en frío: la aplicación pasó de Not Running
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Inicialización del conjunto mínimo de servicios
setupAnalytics()
configureAppearance()
return true
}
// La aplicación finaliza — solo cierre manual
func applicationWillTerminate(
_ application: UIApplication
) {
saveCriticalData()
}
// Guardar datos antes de ir a segundo plano
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
)
}
}El código muestra el manejo correcto de Not Running en iOS. applicationWillTerminate solo se llama cuando el usuario finaliza manualmente la aplicación. El guardado de datos críticos se duplica en applicationDidEnterBackground ya que este método está garantizado para llamarse antes de ir a segundo plano. La restauración de estado permite guardar la pila de UI para su posterior recuperación durante un arranque en frío.
En Android, Not Running significa que el proceso de la aplicación no existe. El sistema Linux subyacente de Android gestiona los procesos mediante el mecanismo Zygote. Cuando se inicia una aplicación, Zygote bifurca un nuevo proceso, carga Dalvik/ART y llama a Application.onCreate(). Android no tiene un equivalente directo de applicationWillTerminate — el sistema puede finalizar el proceso en cualquier momento sin previo aviso.
Cuando se llama a un Activity por primera vez, el sistema crea el proceso, Application y Activity a través de la cadena onCreate → onStart → onResume. Si el usuario presiona Atrás, el Activity se destruye (onDestroy) y el sistema puede finalizar el proceso. Una diferencia clave con iOS: en Android, el proceso puede seguir existiendo incluso sin Activities activos — por ejemplo, si se ejecuta un Foreground Service o hay un BroadcastReceiver activo.
// Manejo de Not Running mediante SavedStateHandle en 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 — la primera devolución de llamada después de Not Running
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
initCrashReporter()
initDependencyInjection()
}
}SavedStateHandle es un componente de Android Architecture Components que guarda automáticamente el estado durante una transición a Not Running y lo restaura en un arranque en frío. Un ViewModel creado a través de ViewModelProvider sobrevive a la rotación de pantalla y a la destrucción del Activity. Cuando el proceso finaliza, los datos de SavedStateHandle se serializan en un Bundle y se guardan en el estado de instancia guardado.
Not Running ocurre por varias razones. El usuario cierra manualmente la aplicación. El sistema descarga la aplicación por falta de memoria. La aplicación se bloquea con una excepción. En Android, el sistema puede finalizar el proceso durante una actualización masiva de aplicaciones o un reinicio del dispositivo. iOS puede finalizar la aplicación cuando una tarea en segundo plano agota el tiempo de espera (generalmente 30 segundos).
| Razón | iOS | Android | Se puede prevenir |
|---|---|---|---|
| Cierre manual por el usuario | Deslizar en App Switcher | Deslizar desde Recents | No — acción del usuario |
| Falta de memoria | Activación de aviso de memoria | onTrimMemory / LMK | Parcialmente — optimización de memoria |
| Bloqueo de la aplicación | NSException / señal | UncaughtException / ANR | Sí — manejo de errores y reporte de bloqueos |
| Tiempo de espera de tarea en segundo plano | 30 seg para tarea en segundo plano | 10 min para JobScheduler | Sí — programación correcta de tareas |
| Reinicio del SO | Se llama a applicationWillTerminate | Broadcast ACTION_SHUTDOWN | No — evento del sistema |
| Actualización de la aplicación | No ocurre (iOS Sandbox) | El proceso finaliza al actualizar APK | No — actualización del sistema |
Para iOS, use el registro en consola en applicationWillTerminate y applicationDidFinishLaunching. Agregue una bandera en UserDefaults en cada inicio — si la bandera falta en el próximo inicio, la aplicación se finalizó incorrectamente. En Android, use ActivityManager.isBackgroundRestricted() para verificar si la aplicación puede ejecutar tareas en segundo plano. También, supervise onTrimMemory(TRIM_MEMORY_COMPLETE) — esta es una señal de que el proceso será finalizado.
Primera regla — nunca asuma que se llamará a applicationWillTerminate u onDestroy. Guarde los datos críticamente importantes en cada transición de Active a Background. Use almacenes clave-valor para configuraciones simples y SQLite/Room para datos estructurados.
Segunda regla — mida el tiempo de arranque en frío y optimícelo. Inicialización perezosa, minimización del trabajo en el hilo principal, precarga de recursos, uso de la API SplashScreen — todo esto mejora la percepción del tiempo de inicio. Google recomienda un arranque en frío de menos de 200 ms para una excelente experiencia de usuario.
Tercera regla — implemente la Restauración de Estado. En iOS, use UIApplication.stateRestorationIdentifier y NSUserActivity. En Android, use SavedStateHandle en ViewModel combinado con onSaveInstanceState. Esto permitirá al usuario continuar trabajando desde el mismo punto después de un reinicio de la aplicación.
Cuarta regla — maneje launchOptions e Intent con los que se inició la aplicación después de Not Running. Deep links, notificaciones push, enlaces universales — todos se pasan a través de los parámetros de inicio. El desarrollador debe extraer correctamente estos datos y dirigir al usuario a la pantalla correspondiente.
// Manejo de deep link después de un arranque en frío
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Verificar si llegó una notificación
if let notification = launchOptions?[.remoteNotification] as? [String: Any] {
handleNotification(notification)
}
// Verificar 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)
}El código muestra el manejo de los parámetros de inicio en un arranque en frío de iOS. launchOptions contiene los datos con los que el sistema inició la aplicación. Las notificaciones, deep links y enlaces universales se pasan a través de este diccionario. El desarrollador debe manejar correctamente todos los escenarios de inicio posibles para garantizar una experiencia de usuario fluida.
Preguntas frecuentes
Los datos que se guardaron en el almacenamiento persistente (UserDefaults, Core Data, SharedPreferences, Room) se conservan. Los datos en RAM — variables, caché, estado del ViewModel sin SavedStateHandle — se pierden irreversiblemente. Por lo tanto, es de vital importancia guardar el estado de la aplicación en cada transición a Background.
Durante un arranque en frío, se llama a application(_:didFinishLaunchingWithOptions:). Durante un arranque en caliente (regreso de Suspended), este método no se llama — solo se activan applicationWillEnterForeground y applicationDidBecomeActive. Si necesita realizar una acción solo en un arranque en frío, establezca una bandera en didFinishLaunchingWithOptions.
Sí. Un Foreground Service con una notificación persistente evita que el sistema finalice el proceso, incluso si todos los Activities están destruidos. Un Background Service (startService sin foreground) puede ser detenido por el sistema en cualquier momento. Un Service en ejecución significa que el proceso existe, y esto ya no es Not Running.
En el simulador de iOS, finalice la aplicación a través del App Switcher (Cmd+Shift+H dos veces, deslizar hacia arriba). En el emulador de Android, use adb shell am force-stop com.example.app o el botón Stop en Logcat. Después de eso, inicie la aplicación de nuevo — será un arranque en frío limpio desde Not Running.
Kill-switch es un comando de servidor para la finalización de emergencia de la aplicación. Se utiliza en aplicaciones bancarias y empresariales para el bloqueo remoto de acceso. Si la aplicación recibe un comando kill, en el próximo arranque en frío bloquea la UI y solicita una nueva autenticación. En iOS, un kill-switch se implementa mediante notificaciones remotas con una bandera de bloqueo.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también