Si llevas un tiempo desarrollando en Android, tarde o temprano te das cuenta de que entender el ciclo de vida de una Activity no es opcional: es cuestión de supervivencia. Llamadas de teléfono que interrumpen tu app, cambios de orientación, modo multiventana, falta de memoria… si no dominas estos escenarios, tu aplicación empieza a comportarse de forma rara, se cierran pantallas, se pierde el estado del usuario y la experiencia se va al traste.
Esta guía de supervivencia del programador Android está pensada justo para eso: que tengas una visión clara y práctica de qué hace Android con tus Activities, qué métodos se ejecutan, en qué orden, qué deberías hacer (y qué no) en cada uno, cómo encaja todo esto con Jetpack Compose y qué estrategias hay para que tu app recuerde el estado incluso cuando el sistema decide matar tu proceso sin avisar.
¿Por qué el ciclo de vida de una Activity lo condiciona todo?
Android está diseñado para dispositivos móviles con recursos limitados: pantallas pequeñas, batería, memoria y CPU más ajustadas que en un PC. Además, la mayoría de estos dispositivos son, ante todo, teléfonos: pueden recibir llamadas, notificaciones y otros eventos que interrumpen tus apps en cualquier momento, sin pedir permiso.
A diferencia de un programa clásico con un método main() que controlas de principio a fin, en Android la plataforma manda. El usuario solo abre aplicaciones; casi nunca las “cierra” explícitamente. Es la propia plataforma la que decide cuándo matar procesos para liberar recursos y, cuando el usuario vuelve, espera que la app retome su estado anterior como si nada hubiera pasado.
Esto significa que cada Activity pasa por diferentes estados de visibilidad y ejecución y, en función de esos estados, el sistema puede destruir tu proceso en cualquier momento. Si entiendes bien el ciclo de vida, puedes reaccionar a esos cambios, liberar recursos cuando toca y restaurar la interfaz cuando el usuario regrese sin que note nada raro.
Activity, pantalla y pila de actividades
En Android, cada pantalla completa de una app suele corresponder a una instancia de Activity. A diferencia de los sistemas de ventanas solapadas de escritorio, en móvil lo normal es ocupar toda la pantalla, aunque puede haber diálogos o notificaciones que tapen solo parte de la interfaz.
Desde el punto de vista del usuario, la navegación entre pantallas funciona como una pila (stack) de Activities. La última que se muestra es la que está en primer plano y, al pulsar el botón Atrás, se “desapila” y se vuelve a la anterior. Esta pila no tiene por qué ser solo de tu app: Android permite que una Activity de otra aplicación se coloque en tu misma pila (por ejemplo, abrir la app de mapas o de correo).
Para lanzar nuevas pantallas o recuperar otras que no son visibles, Android utiliza Intents. Un Intent puede pedir abrir una Activity concreta de tu app, o describir una acción genérica (enviar un email, hacer una foto…) para que otra app se encargue. Es la forma estándar de decirle al sistema: “quiero ir de esta pantalla a otra”.
Estados básicos de una Activity y prioridades del sistema
Cada Activity puede encontrarse en diferentes estados de ejecución, y estos estados determinan cuán probable es que Android mate su proceso cuando necesita memoria:
- Activa (Reanudada / Resumed): la Activity está en primer plano, visible y con foco; el usuario interactúa con ella.
- Pausada (Paused): la Activity ha perdido el foco, pero todavía se ve parcialmente. Suele pasar cuando aparece un diálogo semitransparente o, en modo multiventana, cuando otra app tiene el foco.
- Detenida (Stopped): la Activity es completamente invisible porque otra pantalla la cubre por completo o la app ha ido al fondo.
- Finalizada (Destroyed): la Activity se destruye y ya no forma parte del stack visible para el usuario.
En la práctica, el sistema decide qué procesos matar en función del estado final de sus Activities:
- Si tienes una Activity Reanudada en primer plano, el proceso tiene la prioridad más alta y la probabilidad más baja de ser terminado.
- Si las Activities están Iniciadas o Pausadas pero visibles, el proceso tiene baja probabilidad de ser finalizado.
- Si todas las Activities están Detenidas (en segundo plano), la probabilidad de que el proceso sea eliminado aumenta.
- Si el proceso está vacío (sin componentes activos), es el candidato principal a ser eliminado.
Algo importante: el sistema nunca “mata” una Activity suelta para liberar memoria; lo que hace es terminar el proceso completo donde está esa Activity junto con el resto de componentes. Por eso es tan crítico guardar y restaurar el estado de la interfaz de usuario de forma correcta.
El paradigma del ciclo de vida: las 6 devoluciones clave
La clase Activity pone a tu disposición un conjunto básico de callbacks de ciclo de vida que el sistema invoca automáticamente cuando cambia el estado de la pantalla. Son los siguientes:
- onCreate
- onStart
- onResume
- onPause
- onStop
- onDestroy
Además, existe onRestart para el caso en que se retoma una Activity que estaba detenida. En conjunto, estos métodos cubren el ciclo completo de existencia de una Activity, desde que se crea por primera vez hasta que se destruye definitivamente.
Podemos ver tres “subciclos” importantes dentro del ciclo de vida general:
- Ciclo completo de vida: desde la primera llamada a onCreate hasta la última a onDestroy.
- Ciclo de visibilidad: desde onStart (la Activity empieza a ser visible) hasta onStop (ya no se ve nada de ella).
- Ciclo de primer plano: desde onResume (la Activity está activa y con foco) hasta onPause (pierde el foco).
Aunque tu Activity no tiene por qué implementar todos los métodos, es esencial que entiendas el papel de cada uno y elijas bien en cuál haces cada tipo de trabajo (inicialización, suscripciones, liberación de recursos, guardar estado, etc.).
onCreate: el arranque de tu Activity
onCreate es el callback que se dispara cuando el sistema crea la Activity por primera vez. En este momento, la Activity entra en el estado Created. Es el lugar ideal para ejecutar la lógica que solo debe ocurrir una vez en toda la vida de esa instancia.
En onCreate suele ser habitual inflar el layout o establecer el contenido Compose, vincular la Activity con su ViewModel, inicializar listas, configurar adaptadores, crear variables de ámbito de clase y leer el parámetro savedInstanceState (un Bundle que contiene el estado previamente guardado, si lo hay).
Si tus componentes dependientes de la Activity están adaptados al ciclo de vida (por ejemplo, implementan interfaces de Lifecycle o usan anotaciones antiguas como @OnLifecycleEvent), recibirán un evento ON_CREATE. Es su oportunidad para hacer cualquier configuración inicial necesaria.
Tras ejecutar onCreate, la Activity no se queda “parada” en ese estado: Android llama rápidamente a onStart y onResume para llevarla al primer plano, de modo que el usuario pueda empezar a interactuar con ella.
onStart: la Activity se vuelve visible
Cuando la Activity entra en el estado Started, el sistema invoca onStart. En este punto la pantalla ya es visible para el usuario, aunque todavía no tenga foco. Es un buen lugar para arrancar elementos que mantienen la interfaz, como animaciones ligeras o la preparación de contenidos visuales.
Al pasar a este estado, los componentes que dependen del ciclo de vida reciben el evento ON_START. Puedes aprovecharlo para iniciar recursos que deban funcionar mientras la pantalla sea visible, aunque no necesariamente interactiva.
Al igual que ocurre con onCreate, la ejecución de onStart es breve y la Activity salta enseguida al estado Resumed. Una vez termina onStart, Android invoca onResume y la Activity pasa al primer plano con foco para el usuario.
onResume: la Activity entra en primer plano
onResume se llama cuando la Activity entra en el estado Resumed (reanudada). Es el momento en que la pantalla está en primer plano, con foco de entrada, y la app puede recibir toques, gestos y demás interacciones del usuario.
La Activity permanece en este estado hasta que ocurre algún evento que le quita el foco: una llamada, una notificación de otra app que abre una nueva pantalla, el apagado de la pantalla del dispositivo o la transición a otra Activity.
En este punto, cualquier componente observado por el ciclo de vida recibe el evento ON_RESUME y puede activar lógica que solo debe ejecutarse mientras la pantalla esté completamente activa. Un ejemplo típico sería iniciar la vista previa de la cámara o comenzar a reproducir un vídeo.
Cuando se produce una interrupción, la Activity entra en estado Paused y el sistema llama a onPause. Si más adelante el usuario vuelve a la pantalla, la Activity vuelve al estado Resumed y se invoca de nuevo onResume, por lo que conviene usarlo para reactivar todo lo que se haya liberado en onPause.
Aquí conviene tener en cuenta el modo multiventana: una Activity puede estar totalmente visible incluso estando en estado Paused si otra ventana tiene el foco. Si necesitas que un recurso intensivo (como la cámara) solo funcione cuando tu Activity sea la que está activa en primer plano, inicialízalo tras ON_RESUME y libéralo en ON_PAUSE. Si quieres que siga activo mientras la pantalla siga visible (aunque esté pausada), puedes hacerlo tras ON_START y liberarlo en ON_STOP, sabiendo que esto puede impedir que otra app acceda al mismo recurso en paralelo.
onPause: el primer aviso de que el usuario se va
El método onPause se ejecuta cuando el sistema te lanza la primera pista de que el usuario está dejando tu Activity, aunque esto no implica necesariamente que vaya a destruirse. La Activity ya no está en primer plano, pero aún puede ser visible parcialmente (especialmente en modo multiventana o con diálogos abiertos).
Algunas situaciones típicas en las que una Activity pasa a estado Paused son: un diálogo semitransparente que aparece encima, el foco que pasa a otra app en multiventana o la recepción de una llamada.
Cuando la Activity entra en Paused, los componentes enlazados a su ciclo de vida reciben el evento ON_PAUSE. Es un buen momento para detener tareas que no tienen sentido sin foco de usuario, como pausar animaciones pesadas, parar una vista previa de cámara o ajustar el consumo de recursos.
En onPause tiene sentido liberar recursos que afectan directamente a la batería y no son necesarios mientras la Activity no tenga foco, como listeners de sensores, GPS o determinadas tareas periódicas. Sin embargo, la ejecución de este método es corta y no garantiza tiempo suficiente para operaciones pesadas, por lo que no se recomienda usarlo para guardar en base de datos, hacer llamadas de red o transacciones costosas.
Si necesitas hacer trabajo de cierre más intenso, resérvalo para onStop. Ten también presente que en modo multiventana una Activity en estado Paused puede ser completamente visible, así que quizá te interese mover parte de la lógica de liberación a onStop para ofrecer una experiencia más coherente visualmente.
onStop: la Activity deja de ser visible
onStop se ejecuta cuando el usuario ya no puede ver la Activity en pantalla. Esto suele ocurrir cuando otra Activity ocupa por completo la pantalla, o cuando tu Activity termina (porque se llama a finish o el usuario sale con Atrás).
Al entrar en estado Stopped, los componentes que dependen del ciclo de vida reciben el evento ON_STOP. Aquí es donde deberían pararse por completo las funcionalidades que no hacen falta si la pantalla no se ve en absoluto.
En onStop es recomendable liberar recursos relacionados con la interfaz y operaciones visibles que ya no aportan nada con la app en segundo plano: pausar animaciones, reducir la frecuencia de actualizaciones de ubicación o detener suscripciones caras. También es un buen punto para ejecutar tareas de finalización más pesadas dentro de un hilo en segundo plano, como guardar borradores o persistir información clave en la base de datos.
Aunque la Activity esté detenida, el objeto Activity sigue residente en memoria con todas sus propiedades, pero ya no está conectado al WindowManager. Si el usuario regresa y el sistema no ha matado el proceso, la Activity recuerda su estado y puede volver a mostrar los datos tal y como estaban.
Desde este estado, la Activity puede volver a primera línea (se llamará a onRestart, luego a onStart y onResume) o terminar de forma definitiva y pasar a onDestroy.
onRestart: volver de un estado detenido
El método onRestart se llama cuando una Activity que estaba detenida (Stopped) vuelve a mostrarse al usuario. Actúa como puente entre onStop y la nueva secuencia onStart → onResume.
Es poco frecuente que necesites lógica compleja en onRestart, pero puede ser útil para saber que la Activity ha pasado un tiempo fuera de pantalla y quieres ejecutar cierta acción de “vuelta”, como actualizar datos si han pasado minutos u horas desde la última vez que el usuario la vio.
onDestroy: el cierre definitivo de la Activity
onDestroy se ejecuta justo antes de que la Activity sea destruida completamente. Hay dos grandes motivos para que esto ocurra: que la Activity termine porque el usuario la cierra explícitamente (por ejemplo, pulsando Atrás o llamando a finish), o que el sistema la destruya de forma temporal debido a un cambio de configuración (rotación, cambio a modo multiventana, cambio de idioma, etc.).
Cuando la Activity pasa al estado Destroyed, cualquier componente asociado a su ciclo de vida recibe el evento ON_DESTROY y puede liberar los últimos recursos todavía pendientes. onDestroy es tu última oportunidad para limpiar aquello que no se haya liberado en onStop, como hilos que aún estén corriendo o manejadores que podrían provocar fugas de memoria.
En lugar de intentar deducir dentro de la Activity por qué se está destruyendo, es recomendable delegar los datos de la interfaz en un ViewModel. Si la Activity se recrea por un cambio de configuración, el ViewModel se conserva y se asigna a la nueva instancia automáticamente. Solo cuando la Activity no vaya a crearse de nuevo se ejecuta onCleared en el ViewModel, donde puedes limpiar el estado de larga duración.
La forma habitual de distinguir cuándo la Activity está terminando de verdad es usando isFinishing(). Recuerda que cuando la destrucción es por cambio de configuración, el sistema creará inmediatamente una nueva Activity e invocará de nuevo onCreate, reconstruyendo la interfaz con la nueva configuración.
Compose y el ciclo de vida: efectos y observación de estado
En aplicaciones modernas con Jetpack Compose no es buena idea meter lógica de negocio directamente en onStart o onResume de la Activity. En vez de eso, se recomienda usar APIs preparadas para trabajar con Lifecycle que se integran con el árbol de composición.
Por ejemplo, puedes consumir flujos del ViewModel con collectAsStateWithLifecycle. Esta API comienza a recopilar datos automáticamente cuando la IU entra en estado Started y detiene la recolección cuando pasa a segundo plano, evitando trabajo innecesario y consumo de recursos cuando la pantalla no está activa.
Tras transformar esos flujos en estado Compose, es posible usar Lifecycle-aware effects (como LaunchedEffect combinado con eventos de lifecycle) para ejecutar código cuando se producen determinados eventos de ciclo de vida, sin depender directamente de los callbacks de la Activity.
Al usar este enfoque, la interfaz reacciona al estado del ciclo de vida de forma natural: solo ejecuta lógica de negocio cuando el usuario está interactuando realmente con el componente visible, y se desconecta de servicios y flujos cuando ya no es necesario. Esto reduce fugas de memoria, errores sutiles y hace tu código más reutilizable.
Estado de proceso, expulsión de memoria y expectativas del usuario
Android puede finalizar procesos cuando necesita liberar RAM. Como hemos visto, la prioridad para matar depende del estado de las Activities, pero desde el punto de vista del usuario hay expectativas claras: espera que la app mantenga su estado al girar el dispositivo, al pasar a otra app un momento y volver, o al entrar y salir del modo multiventana.
El problema es que, por defecto, un cambio de configuración provoca que se destruya y se vuelva a crear la Activity, dejando atrás cualquier estado que estuviera solo en memoria. De forma similar, si el sistema mata el proceso mientras la app está en segundo plano y luego el usuario vuelve, la Activity original ya no existe, aunque el sistema “recuerde” que estuvo ahí y trate de reconstruirla.
Para satisfacer estas expectativas, es necesario combinar varias herramientas: ViewModel para la lógica compleja y estado de pantalla, APIs como rememberSaveable en Compose para estado de IU ligero y, cuando sea necesario, almacenamiento local persistente para cantidades mayores de datos.
Estado de instancia y rememberSaveable
Existe una diferencia importante entre cuando la Activity finaliza por comportamiento normal (el usuario pulsa Atrás o se llama a finish) y cuando el sistema la destruye por presión de memoria o cambios de configuración.
En el primer caso, la instancia concreta de Activity desaparece para siempre: el sistema no necesita recordar nada porque el usuario ha expresado claramente que quería salir. Pero si la destrucción es por restricciones del sistema, Android mantiene internamente un paquete de datos llamados estado de instancia, que es básicamente una colección de pares clave-valor serializados.
Este estado de instancia sirve para reconstruir información básica de la interfaz: texto escrito en cajas de texto, posiciones de scroll, selecciones simples, etc. En Compose, rememberSaveable se apoya en este mecanismo para guardar y restaurar estado transitorio de la IU sin que tengas que escribir código manual en la Activity.
Cuando usas rememberSaveable para declarar un estado, Compose se encarga de serializarlo en el Bundle de instancia y restaurarlo automáticamente cuando la Activity se recrea. Esto funciona tanto en cambios de configuración como en cierres de proceso iniciados por el sistema.
Sin embargo, el mecanismo de estado de instancia no es adecuado para grandes cantidades de datos: requiere serialización en el hilo principal y consume memoria del proceso del sistema. Para información más compleja (datos de usuario, respuestas de red, estructuras grandes) necesitas combinar ViewModel, almacenamiento local (Room, DataStore, etc.) y hoisting de estado en Compose.
Guardado de estado ligero con rememberSaveable
Cuando el sistema empieza a detener tu Activity, se va preparando para guardar el estado de instancia. Con Compose, te conectas a este mecanismo utilizando rememberSaveable directamente dentro de tus funciones @Composable.
Con rememberSaveable puedes conservar, por ejemplo, el texto introducido por el usuario, el progreso en una pequeña tarea, la página actual de un carrusel, etc. Compose empaqueta ese estado en el Bundle y lo restaura sin que tengas que implementar onSaveInstanceState ni revisar si el Bundle es nulo a nivel de Activity.
Debes tener presente que este mecanismo no se activa cuando el usuario cierra la Activity de forma explícita ni cuando se llama a finish; su propósito es soportar destrucciones provocadas por el sistema, no decisiones directas del usuario.
Para datos realmente persistentes, como preferencias de usuario o información de dominio, lo más sensato es guardar en almacenamiento duradero (base de datos, DataStore, ficheros) durante la vida normal de la Activity. Si no hay una ocasión mejor, onStop es un buen sitio para delegar la persistencia al ViewModel en un hilo de fondo.
Restauración automática del estado de la IU
Cuando la Activity se vuelve a crear tras haber sido destruida por el sistema, la restauración del estado en Compose es automática si utilizas rememberSaveable. No necesitas lógica adicional en la propia Activity, ni inspeccionar Bundles, ni reinyectar datos manualmente.
El mismo código que inicializa y guarda el estado a través de rememberSaveable se encarga también de restablecerlo cuando la Activity renace. Esto mantiene el código UI más limpio, predecible y fácil de razonar, ya que la funcionalidad de guardado y restauración está encapsulada en un único punto.
Actividades y navegación moderna
Aunque Android ofrece desde siempre la posibilidad de iniciar Activities para cada pantalla, las apps modernas suelen adoptar una arquitectura de actividad única. Es decir, una sola Activity que funciona como contenedor y que utiliza el componente Navigation de Jetpack para controlar la navegación entre diferentes pantallas composables.
Este enfoque simplifica el manejo del ciclo de vida, ya que reduces el número de Activities que gestionar y concentras la lógica en composables y ViewModels. Aun así, hay ocasiones en las que necesitas lanzar otra Activity, ya sea de tu app o externa.
Iniciar una Activity desde otra
Si quieres lanzar una Activity nueva sin necesidad de recuperar un resultado, la forma clásica es usar startActivity con un Intent adecuado. Por ejemplo, para ir desde tu pantalla actual a una SignInActivity de tu propia app, creas un Intent explícito con la clase objetivo y lo pasas al contexto.
Cuando necesitas obtener un resultado de esa Activity (por ejemplo, pedir una foto a la app de cámara y recibir la imagen), hoy en día se recomienda utilizar las APIs modernas de Activity Result en lugar del antiguo startActivityForResult, que está obsoleto. Estas APIs encajan mejor con el ciclo de vida y evitan fugas de callbacks.
Iniciar actividades externas
Tu Activity también puede lanzar actividades de otras aplicaciones para apoyarse en funcionalidades ya existentes: abrir el navegador, enviar un email, compartir contenido o sacar una foto.
En estos casos, utilizas Intents implícitos que describen una acción (ACTION_SEND, ACTION_VIEW, etc.) y, opcionalmente, datos extra como direcciones de correo o URIs. El sistema se encarga de buscar una app instalada que pueda manejar esa acción y mostrar, si es necesario, un selector al usuario.
Si además de lanzar la Activity externa necesitas un resultado, de nuevo las Activity Result APIs son la opción recomendada, ya que gestionan mejor las recreaciones de Activity debidas a cambios de configuración.
Coordinación entre Activities: orden de los callbacks
Cuando una Activity A inicia una Activity B, es importante entender que sus ciclos de vida se solapan. La primera no se detiene por completo antes de que la segunda empiece; ambos procesos ocurren en paralelo para minimizar tiempos de espera del usuario.
El orden típico de operaciones cuando A lanza B dentro del mismo proceso es:
- Se ejecuta el método onPause de la Activity A.
- Se ejecutan en secuencia onCreate, onStart y onResume de la Activity B, que pasa a tener la atención del usuario.
- Si A ya no es visible, el sistema llama a onStop en A.
Conocer esta secuencia te permite mover datos, liberar recursos y coordinar acciones de una Activity a otra sin sorpresas. Por ejemplo, puedes asegurarte de pausar música, detener la cámara o guardar borradores en el momento adecuado, antes de que la nueva pantalla aparezca.
En definitiva, dominar el ciclo de vida de una Activity y las herramientas modernas como ViewModel, Navigation y Compose te permite construir aplicaciones más estables, eficientes y agradables. Una vez interiorices qué hace Android en cada fase y ubiques bien tus inicializaciones, tus liberaciones y tus guardados de estado, dejarás de pelearte con cierres inesperados y pantallas que “olvidan” al usuario, y podrás centrarte en lo realmente importante: que tu app haga lo que promete y lo haga bien.