Ciclo de vida de la actividad

Cuando un usuario navega por tu app, sale de ella y vuelve a entrar, las instancias de Activity de tu app pasan por diferentes estados de su ciclo de vida. La clase Activity proporciona una serie de devoluciones de llamada que le informan a la actividad cuándo cambia un estado, o bien que el sistema está creando, deteniendo o reanudando una actividad, o bien cuando destruía el proceso en el que se encuentra.

Dentro de los métodos de devolución de llamada de ciclo de vida, puedes declarar el comportamiento que tendrá tu actividad cuando el usuario la abandone y la reanude. Por ejemplo, si creas un reproductor de video en streaming, puedes pausar el video y cancelar la conexión de red cuando el usuario cambie a otra app. Cuando el usuario regrese, podrás volver a conectarte a la red y permitir que el usuario reanude el video desde el mismo punto.

Cada devolución de llamada te permite realizar un trabajo específico que es apropiado para un cambio de estado determinado. Hacer el trabajo preciso en el momento adecuado y administrar las transiciones correctamente hace que tu app sea más sólida y eficiente. Por ejemplo, una buena implementación de las devoluciones de llamada de ciclo de vida puede ayudar a tu app a evitar lo siguiente:

  • No falle si el usuario recibe una llamada telefónica o cambia a otra app mientras usa la tuya.
  • No consuma recursos valiosos del sistema cuando el usuario no la use de forma activa.
  • No pierda el progreso del usuario si este abandona tu app y regresa a ella posteriormente.
  • No falle ni pierda el progreso del usuario cuando se gire la pantalla entre la orientación horizontal y la vertical.

En este documento, se explica en detalle el ciclo de vida de las actividades. Al principio del documento, se describe el paradigma del ciclo de vida. A continuación, se explica cada una de las devoluciones de llamada: qué sucede internamente mientras se ejecutan y qué debes implementar durante ellas.

Luego, se presenta de forma breve la relación entre el estado de una actividad y la vulnerabilidad de un proceso que el sistema está por finalizar. Por último, se analizan varios temas relacionados con las transiciones entre los estados de una actividad.

Para obtener información sobre cómo controlar los ciclos de vida, incluida orientación sobre las prácticas recomendadas, consulta Cómo manejar ciclos de vida con componentes optimizados para ciclos de vida y Cómo guardar estados de la IU. Para aprender a diseñar una app sólida y de calidad mediante actividades junto con componentes de arquitectura, consulta la Guía de arquitectura de apps.

Conceptos de los ciclos de vida de las actividades

Para navegar por las transiciones entre etapas del ciclo de vida de una actividad, la clase Activity proporciona un conjunto principal de seis devoluciones de llamada: onCreate(), onStart(), onResume(), onPause(), onStop() y onDestroy(). El sistema invoca cada una de estas devoluciones de llamada cuando la actividad entra en un nuevo estado.

En la figura 1, se muestra una representación visual de este paradigma.

Figura 1: Ilustración simplificada del ciclo de vida de una actividad.

Cuando el usuario comienza a abandonar la actividad, el sistema llama a métodos para desmantelarla. En algunos casos, la actividad solo se desmantela parcialmente y aún reside en la memoria, como cuando el usuario cambia a otra app. En estos casos, la actividad aún puede volver a primer plano.

Si el usuario regresa a la actividad, esta se reanuda desde donde la dejó. Con algunas excepciones, se restringe el inicio de actividades cuando las apps se ejecutan en segundo plano.

La probabilidad de que el sistema finalice un proceso determinado, junto con las actividades que contiene, depende del estado de la actividad en ese momento. Para obtener más información sobre la relación entre el estado y la vulnerabilidad a la expulsión, consulta la sección sobre el estado de la actividad y la expulsión de la memoria.

Según la complejidad de tu actividad, es probable que no necesites implementar todos los métodos del ciclo de vida. Sin embargo, es importante que comprendas cada uno de ellos y que implementes aquellos que hacen que tu app se comporte como esperan los usuarios.

Devoluciones de llamada del ciclo de vida

En esta sección, se proporciona información conceptual y de implementación sobre los métodos de devolución de llamada utilizados durante el ciclo de vida de la actividad.

Algunas acciones pertenecen a los métodos del ciclo de vida de la actividad. Sin embargo, coloca código que implemente las acciones de un componente dependiente en el componente, en lugar del método del ciclo de vida de la actividad. Para lograrlo, debes hacer que el componente dependiente priorice el ciclo de vida. Para aprender a hacer que los componentes de tus dependencias prioricen los ciclos de vida, consulta Cómo manejar ciclos de vida con componentes optimizados para ciclos de vida.

onCreate()

Debes implementar esta devolución de llamada, que se activa cuando el sistema crea la actividad por primera vez. Cuando se crea la actividad, esta entra en el estado Created. En el método onCreate(), realiza la lógica de inicio básica de la aplicación que ocurre una sola vez en toda la vida de la actividad.

Por ejemplo, tu implementación de onCreate() podría vincular datos a listas, asociar la actividad con un ViewModel y crear instancias de algunas variables de alcance de clase. Este método recibe el parámetro savedInstanceState, que es un objeto Bundle que contiene el estado guardado previamente de la actividad. Si la actividad nunca existió, el valor del objeto Bundle es nulo.

Si tienes un componente optimizado para ciclos de vida que está conectado al ciclo de vida de tu actividad, recibe el evento ON_CREATE. Se llama al método anotado con @OnLifecycleEvent para que tu componente optimizado para ciclos de vida pueda realizar cualquier código de configuración que necesite para el estado de creación.

En el siguiente ejemplo del método onCreate(), se muestra la configuración básica de la actividad, como declarar la interfaz de usuario (definida en un archivo de diseño XML), definir las variables de miembro y configurar parte de la IU. En este ejemplo, el archivo de diseño XML pasa el ID de recurso del archivo R.layout.main_activity a setContentView().

Kotlin

lateinit var textView: TextView

// Some transient state for the activity instance.
var gameState: String? = null

override fun onCreate(savedInstanceState: Bundle?) {
    // Call the superclass onCreate to complete the creation of
    // the activity, like the view hierarchy.
    super.onCreate(savedInstanceState)

    // Recover the instance state.
    gameState = savedInstanceState?.getString(GAME_STATE_KEY)

    // Set the user interface layout for this activity.
    // The layout is defined in the project res/layout/main_activity.xml file.
    setContentView(R.layout.main_activity)

    // Initialize member TextView so it is available later.
    textView = findViewById(R.id.text_view)
}

// This callback is called only when there is a saved instance previously saved using
// onSaveInstanceState(). Some state is restored in onCreate(). Other state can optionally
// be restored here, possibly usable after onStart() has completed.
// The savedInstanceState Bundle is same as the one used in onCreate().
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    textView.text = savedInstanceState?.getString(TEXT_VIEW_KEY)
}

// Invoked when the activity might be temporarily destroyed; save the instance state here.
override fun onSaveInstanceState(outState: Bundle?) {
    outState?.run {
        putString(GAME_STATE_KEY, gameState)
        putString(TEXT_VIEW_KEY, textView.text.toString())
    }
    // Call superclass to save any view hierarchy.
    super.onSaveInstanceState(outState)
}

Java

TextView textView;

// Some transient state for the activity instance.
String gameState;

@Override
public void onCreate(Bundle savedInstanceState) {
    // Call the superclass onCreate to complete the creation of
    // the activity, like the view hierarchy.
    super.onCreate(savedInstanceState);

    // Recover the instance state.
    if (savedInstanceState != null) {
        gameState = savedInstanceState.getString(GAME_STATE_KEY);
    }

    // Set the user interface layout for this activity.
    // The layout is defined in the project res/layout/main_activity.xml file.
    setContentView(R.layout.main_activity);

    // Initialize member TextView so it is available later.
    textView = (TextView) findViewById(R.id.text_view);
}

// This callback is called only when there is a saved instance previously saved using
// onSaveInstanceState(). Some state is restored in onCreate(). Other state can optionally
// be restored here, possibly usable after onStart() has completed.
// The savedInstanceState Bundle is same as the one used in onCreate().
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    textView.setText(savedInstanceState.getString(TEXT_VIEW_KEY));
}

// Invoked when the activity might be temporarily destroyed; save the instance state here.
@Override
public void onSaveInstanceState(Bundle outState) {
    outState.putString(GAME_STATE_KEY, gameState);
    outState.putString(TEXT_VIEW_KEY, textView.getText());

    // Call superclass to save any view hierarchy.
    super.onSaveInstanceState(outState);
}

Como alternativa a definir el archivo en formato XML y pasarlo a setContentView(), puedes crear nuevos objetos View en el código de tu actividad y compilar una jerarquía de vistas insertando nuevos objetos View en un ViewGroup. Para usar ese diseño, pasa la raíz ViewGroup a setContentView(). Para obtener más información sobre cómo crear una interfaz de usuario, consulta la documentación de interfaz de usuario.

Tu actividad no permanece en el estado Created. Cuando finaliza la ejecución del método onCreate(), la actividad entra en el estado Started y el sistema llama rápidamente a los métodos onStart() y onResume().

onStart()

Cuando la actividad entra en el estado Started, el sistema invoca a onStart(). Esta llamada hace que el usuario pueda ver la actividad mientras la app se prepara para que esta entre en primer plano y se convierta en interactiva. Por ejemplo, en este método se inicializa el código que mantiene la IU.

Cuando la actividad pasa al estado Started, cualquier componente que priorice el ciclo de vida vinculado al de la actividad recibe el evento ON_START.

El método onStart() se completa rápidamente y, al igual que con el estado Created, la actividad no permanece en el estado Started. Una vez finalizada esta devolución de llamada, la actividad entra en el estado Resumed, y el sistema invoca el método onResume().

onResume()

Cuando la actividad entra en el estado Resumed, pasa al primer plano y el sistema invoca la devolución de llamada onResume(). Este es el estado en el que la app interactúa con el usuario. La app permanece en este estado hasta que ocurre algo que la quita de foco, como cuando el dispositivo recibe una llamada telefónica, cuando el usuario navega a otra actividad o se apaga la pantalla del dispositivo.

Cuando la actividad pasa al estado Resumed, cualquier componente que priorice el ciclo de vida vinculado al de la actividad recibe el evento ON_RESUME. Aquí es donde los componentes del ciclo de vida pueden habilitar cualquier funcionalidad que necesite ejecutarse mientras el componente esté visible y en primer plano, como, por ejemplo, iniciar una vista previa de la cámara.

Cuando se produce un evento disruptivo, la actividad entra en el estado Pause y el sistema invoca la devolución de llamada onPause().

Si la actividad regresa al estado Resumed desde Pausado, el sistema volverá a llamar al método onResume(). Por este motivo, implementa onResume() para inicializar los componentes que lances durante onPause() y realizar cualquier otra inicialización que deba ocurrir cada vez que la actividad entre en el estado Resumed.

A continuación, se muestra un ejemplo de un componente optimizado para ciclos de vida que accede a la cámara cuando el componente recibe el evento ON_RESUME:

Kotlin

class CameraComponent : LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun initializeCamera() {
        if (camera == null) {
            getCamera()
        }
    }
    ...
}

Java

public class CameraComponent implements LifecycleObserver {

    ...

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    public void initializeCamera() {
        if (camera == null) {
            getCamera();
        }
    }
    ...
}

El código anterior inicializa la cámara una vez que LifecycleObserver recibe el evento ON_RESUME. Sin embargo, en el modo multiventana, tu actividad podría ser completamente visible incluso cuando se encuentre en el estado Pausado. Por ejemplo, cuando la app está en el modo multiventana y el usuario presiona la ventana que no contiene tu actividad, esta pasará al estado En pausa.

Si deseas que la cámara esté activa solo cuando se reanude la app (visible y activa en primer plano), inicializa la cámara después del evento ON_RESUME que se mostró antes. Si deseas mantener la cámara activa mientras la actividad está en pausa, pero visible, como en el modo multiventana, inicializa la cámara después del evento ON_START.

Sin embargo, tener la cámara activa mientras la actividad está en el estado Pausado puede denegar el acceso a la cámara a otra app reanudada en el modo multiventana. En ocasiones, es necesario mantener la cámara activa mientras la actividad está en el estado Pausado, pero, si lo haces, podría degradarse la experiencia general del usuario.

Por este motivo, piensa detenidamente en qué parte del ciclo de vida es más apropiado tomar el control de los recursos compartidos del sistema en el contexto del modo multiventana. Para obtener más información sobre la compatibilidad con el modo multiventana, consulta Compatibilidad con el modo multiventana.

Independientemente del evento en el que decidas realizar una operación de inicialización, asegúrate de utilizar el evento de ciclo de vida correspondiente para liberar el recurso. Si inicializas un elemento después del evento ON_START, libéralo o finalízalo después del evento ON_STOP. Si inicializas un evento después de ON_RESUME, libéralo después del evento ON_PAUSE.

En el fragmento de código anterior, se coloca el código de inicialización de la cámara en un componente optimizado para ciclos de vida. En su lugar, puedes colocar este código directamente en las devoluciones de llamada del ciclo de vida de la actividad, como onStart() y onStop(), pero no se recomienda. Agregar esta lógica a un componente independiente que prioriza el ciclo de vida te permite reutilizar el componente en varias actividades sin tener que duplicar el código. Si deseas obtener más información sobre cómo crear un componente optimizado para ciclos de vida, consulta Cómo controlar ciclos de vida con componentes optimizados para ciclos de vida.

onPause()

El sistema llama a este método como el primer indicador de que el usuario está abandonando tu actividad, aunque no siempre significa que esta se esté destruyendo. Indica que la actividad ya no está en primer plano, pero seguirá visible si el usuario está en el modo multiventana. Existen varios motivos por los que una actividad puede entrar en este estado:

  • Un evento que interrumpe la ejecución de la app, como se describe en la sección sobre la devolución de llamada onResume(), pausa la actividad actual. Este es el caso más común.
  • En el modo multiventana, solo una app tiene foco en todo momento, y el sistema pausa todas las demás.
  • Abrir una actividad semitransparente nueva, como un diálogo, pausa la actividad que cubre. Mientras la actividad sea parcialmente visible, pero no esté en foco, se mantendrá pausada.

Cuando una actividad pasa al estado Pausado, cualquier componente que priorice el ciclo de vida vinculado al de la actividad recibe el evento ON_PAUSE. Aquí es donde los componentes del ciclo de vida pueden detener cualquier funcionalidad que no necesite ejecutarse mientras el componente no esté en primer plano, como detener una vista previa de la cámara.

Usa el método onPause() para pausar o ajustar las operaciones que no pueden continuar o que pueden continuar con moderación, mientras Activity está en estado Pausado y que esperas reanudar en breve.

También puedes usar el método onPause() para liberar recursos del sistema, controladores de sensores (como el GPS) o cualquier recurso que afecte la duración de la batería mientras la actividad está en pausa y el usuario no los necesita.

Sin embargo, como se mencionó en la sección sobre onResume(), es posible que una actividad con el estado Pausado sea completamente visible si la app está en el modo multiventana. Considera usar onStop() en lugar de onPause() para liberar o ajustar por completo los recursos y operaciones relacionados con la IU a fin de admitir mejor el modo multiventana.

El siguiente ejemplo de un LifecycleObserver que reacciona al evento ON_PAUSE es la contraparte del ejemplo de evento ON_RESUME anterior, que libera la cámara que se inicializa después de que se recibe el evento ON_RESUME:

Kotlin

class CameraComponent : LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun releaseCamera() {
        camera?.release()
        camera = null
    }
    ...
}

Java

public class JavaCameraComponent implements LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    public void releaseCamera() {
        if (camera != null) {
            camera.release();
            camera = null;
        }
    }
    ...
}

En este ejemplo, se coloca el código de liberación de la cámara después de que LifecycleObserver recibe el evento ON_PAUSE.

La ejecución de onPause() es muy breve y no necesariamente ofrece suficiente tiempo para realizar operaciones de guardado. Por este motivo, no uses onPause() para guardar datos de la aplicación o del usuario, realizar llamadas de red o ejecutar transacciones de bases de datos. Es posible que este trabajo no se complete antes de que se complete el método.

En su lugar, realiza operaciones de apagado de cargas pesadas durante onStop(). Si deseas obtener más información sobre las operaciones adecuadas para realizar durante onStop(), consulta la siguiente sección. Para obtener más información sobre cómo guardar datos, consulta la sección sobre cómo guardar y restablecer el estado.

La finalización del método onPause() no significa que la actividad abandone el estado Paused. Más bien, la actividad permanecerá en ese estado hasta que se reanude o se vuelva completamente invisible para el usuario. Si se reanuda la actividad, el sistema volverá a invocar la devolución de llamada onResume().

Si la actividad regresa del estado Detenida al estado Reanudar, el sistema mantendrá la instancia Activity en la memoria y la volverá a llamar cuando invoque onResume(). En este caso, no necesitas volver a inicializar los componentes creados durante los métodos de devolución de llamada que llevan al estado Resumed. Si la actividad se vuelve completamente invisible, el sistema llamará a onStop().

onStop()

Cuando el usuario ya no puede ver tu actividad, entra en el estado Detenida, y el sistema invoca la devolución de llamada onStop(). Esto puede ocurrir cuando una actividad recién lanzada cubre toda la pantalla. El sistema también llama a onStop() cuando la actividad termina de ejecutarse y está a punto de finalizar.

Cuando la actividad pasa al estado Stopped, cualquier componente que priorice el ciclo de vida vinculado al de la actividad recibe el evento ON_STOP. Aquí es donde los componentes del ciclo de vida pueden detener cualquier funcionalidad que no necesite ejecutarse mientras el componente no sea visible en la pantalla.

En el método onStop(), libera o ajusta los recursos que no se necesitan mientras la app no es visible para el usuario. Por ejemplo, tu app podría pausar animaciones o cambiar de actualizaciones de ubicación detalladas a más generales. El uso de onStop() en lugar de onPause() implica que el trabajo relacionado con la IU continúa, incluso cuando el usuario ve tu actividad en el modo multiventana.

Además, usa onStop() para realizar operaciones de cierre con un uso relativamente intensivo de la CPU. Por ejemplo, si no encuentras un momento más adecuado para guardar información en una base de datos, puedes hacerlo durante onStop(). Por ejemplo, a continuación, se muestra una implementación de onStop() que guarda los contenidos del borrador de una nota en el almacenamiento persistente:

Kotlin

override fun onStop() {
    // Call the superclass method first.
    super.onStop()

    // Save the note's current draft, because the activity is stopping
    // and we want to be sure the current note progress isn't lost.
    val values = ContentValues().apply {
        put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText())
        put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle())
    }

    // Do this update in background on an AsyncQueryHandler or equivalent.
    asyncQueryHandler.startUpdate(
            token,     // int token to correlate calls
            null,      // cookie, not used here
            uri,       // The URI for the note to update.
            values,    // The map of column names and new values to apply to them.
            null,      // No SELECT criteria are used.
            null       // No WHERE columns are used.
    )
}

Java

@Override
protected void onStop() {
    // Call the superclass method first.
    super.onStop();

    // Save the note's current draft, because the activity is stopping
    // and we want to be sure the current note progress isn't lost.
    ContentValues values = new ContentValues();
    values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText());
    values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle());

    // Do this update in background on an AsyncQueryHandler or equivalent.
    asyncQueryHandler.startUpdate (
            mToken,  // int token to correlate calls
            null,    // cookie, not used here
            uri,    // The URI for the note to update.
            values,  // The map of column names and new values to apply to them.
            null,    // No SELECT criteria are used.
            null     // No WHERE columns are used.
    );
}

En la muestra de código anterior, se usa SQLite directamente. Sin embargo, recomendamos usar Room, una biblioteca de persistencia que proporciona una capa de abstracción sobre SQLite. Para obtener más información sobre los beneficios de usar Room y cómo implementarla en tu app, consulta la guía Biblioteca de persistencias Room.

Cuando tu actividad entra en el estado Stopped, se mantiene el objeto Activity en la memoria: mantiene toda la información de estado y de miembro, pero no está conectado al administrador de ventanas. Cuando se reanuda la actividad, recuerda esta información.

No es necesario que vuelvas a inicializar los componentes creados durante los métodos de devolución de llamada que llevan al estado Resumed. El sistema también realiza un seguimiento del estado actual de cada objeto View en el diseño. Por lo tanto, si el usuario ingresa texto en un widget de EditText, se conserva ese contenido para que no tengas que guardarlo ni restablecerlo.

Nota: Una vez que se detiene la actividad, el sistema puede finalizar el proceso que la contiene si necesita recuperar memoria. Incluso si el sistema destruye el proceso mientras la actividad está detenida, el sistema seguirá reteniendo el estado de los objetos View, como el texto de un widget EditText, en un Bundle (un BLOB de pares clave-valor) y los restablecerá si el usuario regresa a la actividad. Para obtener más información sobre cómo restablecer una actividad a la que regresa un usuario, consulta la sección sobre cómo guardar y restablecer el estado.

Desde el estado Stopped, la actividad regresa a interactuar con el usuario o se termina de ejecutar y desaparece. Si la actividad regresa, el sistema invoca a onRestart(). Si se terminó de ejecutar Activity, el sistema llamará a onDestroy().

onDestroy()

Se llama a onDestroy() antes de que finalice la actividad. El sistema invoca esta devolución de llamada por uno de dos motivos:

  1. La actividad está finalizando, debido a que el usuario la descarta por completo o a que se llama a finish() en la actividad.
  2. El sistema está finalizando temporalmente la actividad debido a un cambio de configuración, como la rotación del dispositivo o el ingreso al modo multiventana.

Cuando la actividad pasa al estado destruido, cualquier componente que priorice el ciclo de vida vinculado al de la actividad recibe el evento ON_DESTROY. Aquí es donde los componentes del ciclo de vida pueden limpiar todo lo que necesiten antes de que se destruya el Activity.

En lugar de colocar lógica en tu Activity para determinar por qué se destruye, usa un objeto ViewModel para contener los datos de vista relevantes para tu Activity. Si se vuelve a crear Activity debido a un cambio de configuración, ViewModel no tiene que realizar ninguna acción, ya que se conserva y se entrega a la siguiente instancia de Activity.

Si no se vuelve a crear Activity, ViewModel llama al método onCleared(), en el que puede limpiar cualquier dato que necesite antes de destruirse. Puedes diferenciar estos dos casos con el método isFinishing().

Si la actividad está terminando, onDestroy() es la devolución de llamada de ciclo de vida final que recibe la actividad. Si se llama a onDestroy() como resultado de un cambio de configuración, el sistema crea inmediatamente una instancia de actividad nueva y, luego, llama a onCreate() en esa instancia nueva, en la configuración nueva.

La devolución de llamada onDestroy() libera todos los recursos que las devoluciones de llamada anteriores no liberaron, como onStop().

Estado de actividad y expulsión de memoria

El sistema cierra los procesos cuando necesita liberar RAM. La probabilidad de que el sistema finalice un proceso determinado depende del estado del proceso en ese momento. El estado del proceso, a su vez, depende del estado de la actividad que se ejecuta en el proceso. En la tabla 1, se muestran las correlaciones entre el estado del proceso, el estado de la actividad y la probabilidad de que el sistema finalice el proceso. Esta tabla solo se aplica si un proceso no ejecuta otros tipos de componentes de la aplicación.

Probabilidad de que finalice Estado del proceso Estado final de la actividad
Muy bajo Primer plano (en foco o por estar en él) Reanudado
Bajo Visible (sin foco) Iniciado/pausado
Alto Fondo (invisible) Detenido
Más alto Vacío Finalizado

Tabla 1: Relación entre el ciclo de vida del proceso y el estado de la actividad

El sistema nunca finaliza una actividad de forma directa para liberar memoria. En cambio, finaliza el proceso en el que se ejecuta la actividad y destruye no solo la actividad, sino también todo lo que se ejecuta en el proceso. Si quieres obtener información para preservar y restablecer el estado de la IU de tu actividad cuando finaliza el proceso iniciado por el sistema, consulta la sección sobre cómo guardar y restablecer el estado.

El usuario también puede finalizar un proceso utilizando el Administrador de aplicaciones, en Configuración, para finalizar la app correspondiente.

Para obtener más información sobre los procesos, consulta Descripción general de los procesos y subprocesos.

Cómo guardar y restablecer el estado transitorio de la IU

El usuario espera que se conserve el estado de la IU de una actividad durante un cambio de configuración, como la rotación o el cambio al modo multiventana. Sin embargo, el sistema finaliza la actividad de forma predeterminada cuando se produce un cambio de configuración de este tipo, lo que elimina cualquier estado de la IU almacenado en la instancia de actividad.

Del mismo modo, un usuario espera que el estado de la IU siga siendo el mismo si cambia temporalmente de tu app a una diferente y, luego, regresa a tu app. Sin embargo, el sistema puede destruir el proceso de tu aplicación mientras el usuario esté ausente y tu actividad esté detenida.

Cuando las restricciones del sistema destruyen la actividad, conserva el estado transitorio de la IU con una combinación de ViewModel, onSaveInstanceState() o almacenamiento local. Para obtener más información sobre las expectativas de los usuarios en comparación con el comportamiento del sistema y la mejor manera de preservar los datos complejos del estado de la IU durante la actividad iniciada por el sistema y el cierre del proceso, consulta Cómo guardar estados de la IU.

En esta sección, se describe el estado de la instancia y cómo implementar el método onSaveInstance(), que es una devolución de llamada a la actividad en sí. Si los datos de tu IU son livianos, puedes usar solo onSaveInstance() para conservar el estado de la IU durante los cambios de configuración y el cierre del proceso iniciado por el sistema. Sin embargo, debido a que onSaveInstance() genera costos de serialización/deserialización, en la mayoría de los casos, se usan ViewModel y onSaveInstance(), como se describe en Cómo guardar estados de la IU.

Nota: Para obtener más información sobre los cambios de configuración, cómo restringir la recreación de Activity si es necesario y cómo reaccionar a esos cambios de configuración desde el sistema de View y Jetpack Compose, consulta la página Cómo controlar los cambios de configuración.

Estado de la instancia

Existen algunas situaciones en las que finaliza tu actividad debido al comportamiento normal de la app, por ejemplo, cuando el usuario presiona el botón Atrás o tu actividad indica su propia finalización llamando al método finish().

Cuando finaliza tu actividad porque el usuario presiona Atrás o la actividad se finaliza a sí misma, el concepto de esa instancia Activity, tanto del sistema como del usuario, desaparece para siempre. En estos casos, las expectativas del usuario coinciden con el comportamiento del sistema y no tienes trabajo adicional que hacer.

Sin embargo, si el sistema destruye la actividad debido a restricciones (como un cambio de configuración o presión de memoria), entonces, aunque haya desaparecido la instancia real de Activity, el sistema recuerda que existía. Si el usuario intenta volver a la actividad, el sistema crea una nueva instancia de esa actividad utilizando un conjunto de datos guardados que describen el estado de la actividad cuando finalizó.

Los datos guardados que el sistema utiliza para restablecer el estado anterior se denominan estado de la instancia. Es una colección de pares clave-valor almacenados en un objeto Bundle. De forma predeterminada, el sistema usa el estado de instancia Bundle para guardar información sobre cada objeto View del diseño de tu actividad, como el valor de texto ingresado en un widget EditText.

De este modo, si finaliza y se vuelve a crear la instancia de tu actividad, se restablece el estado del diseño a su estado previo sin necesidad de que escribas el código. Sin embargo, es posible que tu actividad tenga más información de estado que desees restablecer, como variables de miembro que siguen el progreso del usuario en la actividad.

Nota: Para que el sistema Android restablezca el estado de las vistas de tu actividad, cada vista debe tener un ID único proporcionado por el atributo android:id.

Un objeto Bundle no es apropiado para preservar más que una cantidad trivial de datos, ya que requiere serialización en el subproceso principal y consume memoria del proceso del sistema. Si quieres preservar más de una cantidad muy pequeña de datos, adopta un enfoque combinado para preservar los datos mediante el almacenamiento local persistente, el método onSaveInstanceState() y la clase ViewModel, como se describe en Cómo guardar estados de la IU.

Cómo guardar un estado de IU simple y ligero usando onSaveInstanceState()

A medida que comienza a detenerse tu actividad, el sistema llama al método onSaveInstanceState() para que tu actividad pueda guardar la información del estado en un paquete de estado de instancia. La implementación predeterminada de ese método guarda información transitoria acerca del estado de la jerarquía de vistas de la actividad, como el texto de un widget EditText o la posición de desplazamiento de un widget ListView.

Para guardar información adicional sobre el estado de la instancia de tu actividad, anula onSaveInstanceState() y agrega pares clave-valor al objeto Bundle que se guarda en caso de que tu actividad finalice de forma inesperada. Cuando anulas onSaveInstanceState(), debes llamar a la implementación de la superclase si quieres que la implementación predeterminada guarde el estado de la jerarquía de vistas. Esto se muestra en el siguiente ejemplo:

Kotlin

override fun onSaveInstanceState(outState: Bundle?) {
    // Save the user's current game state.
    outState?.run {
        putInt(STATE_SCORE, currentScore)
        putInt(STATE_LEVEL, currentLevel)
    }

    // Always call the superclass so it can save the view hierarchy state.
    super.onSaveInstanceState(outState)
}

companion object {
    val STATE_SCORE = "playerScore"
    val STATE_LEVEL = "playerLevel"
}

Java

static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
// ...


@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the user's current game state.
    savedInstanceState.putInt(STATE_SCORE, currentScore);
    savedInstanceState.putInt(STATE_LEVEL, currentLevel);

    // Always call the superclass so it can save the view hierarchy state.
    super.onSaveInstanceState(savedInstanceState);
}

Nota: No se llama a onSaveInstanceState() cuando el usuario cierra explícitamente la actividad o en otros casos cuando se llama a finish().

Para guardar datos persistentes, como las preferencias del usuario o datos de una base de datos, aprovecha las oportunidades apropiadas cuando tu actividad esté en primer plano. Si no se presenta tal oportunidad, guarda datos persistentes durante el método onStop().

Cómo restablecer el estado de la IU de la actividad utilizando el estado de la instancia guardada

Cuando se vuelve a crear tu actividad tras haber finalizado, puedes recuperar la instancia del estado guardado desde el Bundle que el sistema pasa a tu actividad. Los métodos de devolución de llamada onCreate() y onRestoreInstanceState() reciben el mismo Bundle que contiene la información del estado de la instancia.

Como se llama al método onCreate() tanto si el sistema crea una instancia nueva de tu actividad como si vuelve a crear una anterior, debes verificar si el estado Bundle es nulo antes de intentar leerlo. Si es nulo, el sistema creará una instancia nueva de la actividad en lugar de restablecer una previa que ya haya finalizado.

En el siguiente fragmento de código, se muestra cómo puedes restablecer algunos datos de estado en onCreate():

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState) // Always call the superclass first

    // Check whether we're recreating a previously destroyed instance.
    if (savedInstanceState != null) {
        with(savedInstanceState) {
            // Restore value of members from saved state.
            currentScore = getInt(STATE_SCORE)
            currentLevel = getInt(STATE_LEVEL)
        }
    } else {
        // Probably initialize members with default values for a new instance.
    }
    // ...
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); // Always call the superclass first

    // Check whether we're recreating a previously destroyed instance.
    if (savedInstanceState != null) {
        // Restore value of members from saved state.
        currentScore = savedInstanceState.getInt(STATE_SCORE);
        currentLevel = savedInstanceState.getInt(STATE_LEVEL);
    } else {
        // Probably initialize members with default values for a new instance.
    }
    // ...
}

En lugar de restablecer el estado durante onCreate(), puedes implementar onRestoreInstanceState(), al que el sistema llama después del método onStart(). El sistema llama a onRestoreInstanceState() solo si hay un estado guardado para restablecer, por lo que no necesitas verificar si Bundle es nulo.

Kotlin

override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    // Always call the superclass so it can restore the view hierarchy.
    super.onRestoreInstanceState(savedInstanceState)

    // Restore state members from saved instance.
    savedInstanceState?.run {
        currentScore = getInt(STATE_SCORE)
        currentLevel = getInt(STATE_LEVEL)
    }
}

Java

public void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy.
    super.onRestoreInstanceState(savedInstanceState);

    // Restore state members from saved instance.
    currentScore = savedInstanceState.getInt(STATE_SCORE);
    currentLevel = savedInstanceState.getInt(STATE_LEVEL);
}

Precaución: Siempre llama a la implementación de superclase de onRestoreInstanceState() para que la implementación predeterminada pueda restablecer el estado de la jerarquía de vistas.

Navegación entre actividades

Es probable que una app entre y salga de una actividad, quizás muchas veces, durante su ciclo de vida, como cuando el usuario presiona el botón Atrás del dispositivo o la actividad inicia una actividad diferente.

En esta sección, se abordan temas que necesitas saber para implementar transiciones de actividad que se realizaron correctamente. Estos temas incluyen iniciar una actividad desde otra, guardar el estado de la actividad y restablecer su estado.

Cómo iniciar una actividad desde otra

Es posible que una actividad necesite iniciar otra actividad en algún momento. Esta necesidad surge, por ejemplo, cuando una app necesita pasar de la pantalla actual a una nueva.

En función de si tu actividad desea recuperar el resultado de la nueva actividad que está a punto de comenzar, puedes iniciarla utilizando los métodos startActivity() o startActivityForResult(). En cualquier caso, debes pasar un objeto Intent.

El objeto Intent especifica la actividad exacta que quieres iniciar o describe el tipo de acción que quieres realizar. El sistema selecciona la actividad adecuada para ti, que incluso puede ser de otra aplicación. Un objeto Intent también puede contener pequeñas cantidades de datos que utilizará la actividad que se inicie. Para obtener más información sobre la clase Intent, consulta Intents y filtros de intents.

startActivity()

Si la actividad recién iniciada no necesita mostrar un resultado, la actividad actual puede iniciarla llamando al método startActivity().

Cuando trabajes en tu propia aplicación, con frecuencia necesitarás iniciar una actividad conocida. Por ejemplo, el siguiente fragmento de código muestra cómo lanzar una actividad llamada SignInActivity.

Kotlin

val intent = Intent(this, SignInActivity::class.java)
startActivity(intent)

Java

Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);

Tu aplicación también podría querer realizar alguna acción, como enviar un correo electrónico, mandar un mensaje de texto o actualizar su estado con datos de tu actividad. En ese caso, es posible que tu aplicación no tenga actividades propias para realizar esas acciones, por lo que, en su lugar, puedes aprovechar las actividades que proporcionan otras aplicaciones del dispositivo y que pueden realizar las acciones por ti.

Aquí es donde los intents son realmente valiosos. Puedes crear un intent que describa una acción que desees realizar y el sistema iniciará la actividad adecuada desde otra aplicación. Si hay varias actividades que pueden controlar el intent, el usuario podrá seleccionar la que quiera usar. Por ejemplo, si quieres permitir que el usuario envíe un mensaje de correo electrónico, puedes crear el siguiente intent:

Kotlin

val intent = Intent(Intent.ACTION_SEND).apply {
    putExtra(Intent.EXTRA_EMAIL, recipientArray)
}
startActivity(intent)

Java

Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);

El EXTRA_EMAIL adicional agregado al intent es un array de cadenas de direcciones de correo electrónico a las que se enviará el correo electrónico. Cuando una aplicación de correo electrónico responde a este intent, lee el array de cadenas proporcionado en el campo adicional y coloca las direcciones en el campo "Para" del formulario de composición de correo electrónico. En esta situación, se inicia la actividad de la aplicación de correo electrónico y, cuando el usuario termina, se reanuda tu actividad.

startActivityForResult()

En ocasiones, se desea obtener el resultado de una actividad cuando esta termina. Por ejemplo, puedes iniciar una actividad que permita al usuario elegir a una persona de una lista de contactos. Cuando finaliza, muestra a la persona seleccionada. Para ello, llama al método startActivityForResult(Intent, int), donde el parámetro entero identifica la llamada.

Este identificador sirve para distinguir varias llamadas a startActivityForResult(Intent, int) de la misma actividad. No es un identificador global y no corre el riesgo de entrar en conflicto con otras apps o actividades. El resultado se obtiene a través de tu método onActivityResult(int, int, Intent).

Cuando se lleva a cabo una actividad secundaria, puedes llamar a setResult(int) para mostrarle los datos a la actividad superior. La actividad secundaria debe proporcionar un código de resultado, que puede ser los resultados estándar RESULT_CANCELED o RESULT_OK, o cualquier valor personalizado que comience con RESULT_FIRST_USER.

Además, la actividad secundaria puede mostrar de manera opcional un objeto Intent que contenga cualquier dato adicional que desee. La actividad superior usa el método onActivityResult(int, int, Intent), junto con el identificador de número entero que la actividad superior proporcionó originalmente, para recibir la información.

Si una actividad secundaria falla por cualquier motivo, por ejemplo, debido a una falla, la actividad superior recibe un resultado con el código RESULT_CANCELED.

Kotlin

class MyActivity : Activity() {
    // ...

    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
            // When the user center presses, let them pick a contact.
            startActivityForResult(
                    Intent(Intent.ACTION_PICK,Uri.parse("content://contacts")),
                    PICK_CONTACT_REQUEST)
            return true
        }
        return false
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
        when (requestCode) {
            PICK_CONTACT_REQUEST ->
                if (resultCode == RESULT_OK) {
                    // A contact was picked. Display it to the user.
                    startActivity(Intent(Intent.ACTION_VIEW, intent?.data))
                }
        }
    }

    companion object {
        internal val PICK_CONTACT_REQUEST = 0
    }
}

Java

public class MyActivity extends Activity {
     // ...

     static final int PICK_CONTACT_REQUEST = 0;

     public boolean onKeyDown(int keyCode, KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
             // When the user center presses, let them pick a contact.
             startActivityForResult(
                 new Intent(Intent.ACTION_PICK,
                 new Uri("content://contacts")),
                 PICK_CONTACT_REQUEST);
            return true;
         }
         return false;
     }

     protected void onActivityResult(int requestCode, int resultCode,
             Intent data) {
         if (requestCode == PICK_CONTACT_REQUEST) {
             if (resultCode == RESULT_OK) {
                 // A contact was picked. Display it to the user.
                 startActivity(new Intent(Intent.ACTION_VIEW, data));
             }
         }
     }
 }

Cómo coordinar actividades

Cuando una actividad inicia otra, ambas experimentan transiciones en su ciclo de vida. La primera actividad deja de funcionar y entra en el estado Paused o Stopped, mientras se crea la otra actividad. Si esas actividades comparten datos guardados en el disco o en alguna otra parte, es importante que entiendas que no se detiene la primera actividad por completo antes de que se cree la segunda. Más bien, el proceso de iniciar la segunda se superpone con el proceso de detener la primera.

El orden de las devoluciones de llamada de ciclo de vida está bien definido, en especial cuando las dos actividades están en el mismo proceso (en otras palabras, la misma app) y una inicia la otra. Aquí te mostramos el orden de las operaciones que ocurren cuando la actividad A inicia la actividad B:

  1. Se ejecuta el método onPause() de la actividad A.
  2. Los métodos onCreate(), onStart() y onResume() de la actividad B se ejecutan en secuencia. La actividad B ahora tiene la atención del usuario.
  3. Si la actividad A ya no está visible en la pantalla, se ejecuta su método onStop().

Esta secuencia de devoluciones de llamada de ciclo de vida te permite administrar la transición de información de una actividad a otra.