Etapas del ciclo de vida de la actividad

Introducción

En este codelab, obtendrás más información sobre una parte fundamental de Android: la actividad. El ciclo de vida de la actividad es el conjunto de estados en los que puede estar una actividad durante su ciclo de vida. El ciclo de vida abarca desde el momento en que se crea inicialmente la actividad hasta que esta se destruye y el sistema recupera los recursos de esa actividad. A medida que un usuario navega entre las actividades de tu app (y también cuando ingresa a la app y sale de ella), cada una de esas actividades experimenta una transición entre diferentes estados de su ciclo de vida.

Como desarrollador de Android, debes comprender el ciclo de vida de la actividad. Si las actividades no responden correctamente a los cambios de estado del ciclo de vida, es posible que tu app genere errores extraños o comportamientos confusos para los usuarios, o que use demasiados recursos del sistema Android. Comprender el ciclo de vida de Android y responder correctamente a los cambios de estado del ciclo de vida es fundamental para ser un buen ciudadano de Android.

Conocimientos que ya deberías tener

  • Qué es una actividad y cómo crear una en tu app
  • Qué hace el método onCreate() de la actividad y qué tipo de operaciones se realizan en ese método

Qué aprenderás

  • La forma de imprimir información de registro en Logcat
  • Los conceptos básicos del ciclo de vida de Activity y las devoluciones de llamada que se invocan cuando la actividad pasa de un estado a otro
  • La manera de anular métodos de devolución de llamada de ciclo de vida para realizar operaciones en diferentes momentos del ciclo de vida de la actividad

Actividades

  • Modifica una app inicial llamada DessertClicker a fin de agregar información de registro que se muestra en Logcat.
  • Anula los métodos de devolución de llamada de ciclo de vida y registra los cambios de estado en el estado de la actividad.
  • Ejecuta la app y observa la información de registro que aparece cuando se inicia, se detiene y se reanuda la actividad.
  • Implementa el método onSaveInstanceState() para conservar los datos de la app que podrían perderse si cambiara la configuración del dispositivo. Agrega un código para restablecer esos datos cuando se vuelva a iniciar la app.

En este codelab, trabajarás con una app inicial llamada DessertClicker. En esta app, cada vez que el usuario presiona un postre en la pantalla, la app "compra" el postre para el usuario. La app actualiza los valores en el diseño para la cantidad de postres que se compraron y el importe total que el usuario gastó.

8216c20f5571fc04.png

Esta app contiene varios errores relacionados con el ciclo de vida de Android. Por ejemplo, en determinadas circunstancias, la app restablece los valores de los postres a 0. Comprender el ciclo de vida de Android te ayudará a comprender por qué ocurren estos problemas y cómo corregirlos.

Obtén la app inicial

Descarga el código de inicio de DessertClicker y ábrelo en Android Studio.

Si usas el código de inicio de GitHub, ten en cuenta que el nombre de la carpeta es android-basics-kotlin-dessert-clicker-app-starter. Selecciona esta carpeta cuando abras el proyecto en Android Studio.

A fin de obtener el código necesario para este codelab y abrirlo en Android Studio, haz lo siguiente:

Obtén el código

  1. Haz clic en la URL proporcionada. Se abrirá la página de GitHub del proyecto en un navegador.
  2. En esa página, haz clic en el botón Code, que abre un cuadro de diálogo.

Eme2bJP46u-pMpnXVfm-bS2N2dlyq6c0jn1DtQYqBaml7TUhzXDWpYoDI0lGKi4xndE_uJw8sKfwfOZ1fC503xCVZrbh10JKJ4iEHdLDwFfdvnOheNxkokITW1LW6UZTncVJJUZ5Fw

  1. En el cuadro de diálogo, haz clic en el botón Download ZIP para guardar el proyecto en tu computadora. Espera a que se complete la descarga.
  2. Ubica el archivo en tu computadora (probablemente en la carpeta Descargas).
  3. Haz doble clic en el archivo ZIP para descomprimirlo. Se creará una carpeta nueva con los archivos del proyecto.

Abre el proyecto en Android Studio

  1. Inicia Android Studio.
  2. En la ventana Welcome to Android Studio, haz clic en Open an existing Android Studio project.

Tdjf5eS2nCikM9KdHgFaZNSbIUCzKXP6WfEaKVE2Oz1XIGZhgTJYlaNtXTHPFU1xC9pPiaD-XOPdIxVxwZAK8onA7eJyCXz2Km24B_8rpEVI_Po5qlcMNN8s4Tkt6kHEXdLQTDW7mg

Nota: Si Android Studio ya está abierto, selecciona la opción de menú File > New > Import Project.

PaMkVnfCxQqSNB1LxPpC6C6cuVCAc8jWNZCqy5tDVA6IO3NE2fqrfJ6p6ggGpk7jd27ybXaWU7rGNOFi6CvtMyHtWdhNzdAHmndzvEdwshF_SG24Le01z7925JsFa47qa-Q19t3RxQ

  1. En el cuadro de diálogo Import Project, navega hasta donde se encuentra la carpeta de proyecto descomprimido (probablemente en Descargas).
  2. Haz doble clic en la carpeta del proyecto.
  3. Espera a que Android Studio abra el proyecto.
  4. Haz clic en el botón Run j7ptomO2PEQNe8jFt4nKCOw_Oc_Aucgf4l_La8fGLCMLy0t9RN9SkmBFGOFjkEzlX4ce2w2NWq4J30sDaxEe4MaSNuJPpMgHxnsRYoBtIV3-GUpYYcIvRJ2HrqR27XGuTS4F7lKCzg para compilar y ejecutar la app. Asegúrate de que funcione como se espera.
  5. Explora los archivos del proyecto en la ventana de herramientas Project para ver cómo se implementó la app.

Cada actividad tiene lo que se conoce como un ciclo de vida. Es una alusión a los ciclos de vida de las plantas y los animales, como el ciclo de vida de esta mariposa. Los diferentes estados de la mariposa muestran su crecimiento desde que nace hasta que se convierte en un adulto pleno y, luego, hasta que muere.

c685f48ff799f0c9.png

De manera similar, el ciclo de vida de la actividad se compone de los diferentes estados que puede atravesar una actividad, por ejemplo, cuando se inicializa por primera vez hasta que al final se destruye y el sistema recupera su memoria. Cuando el usuario inicia tu app y navega dentro y fuera de ella, y entre las actividades, la actividad cambia de estado. En el siguiente diagrama, se muestran todos los estados del ciclo de vida de las actividades. Como los nombres lo indican, estos estados representan el estado de la actividad.

c803811f4cb4034b.png

A menudo, quieres cambiar algún comportamiento o ejecutar algún código cuando cambia el estado del ciclo de vida de la actividad. Por lo tanto, la clase Activity en sí misma y cualquier subclase de Activity, como AppCompatActivity, implementan un conjunto de métodos de devolución de llamada de ciclo de vida. Android invoca estas devoluciones de llamada cuando la actividad pasa de un estado a otro, y puedes anular esos métodos en tus propias actividades a fin de realizar tareas en respuesta a esos cambios de estado del ciclo de vida. En el siguiente diagrama, se muestran los estados del ciclo de vida junto con las devoluciones de llamada anulables disponibles.

f6b25a71cec4e401.png

Es importante saber cuándo se invocan estas devoluciones de llamada y qué hacer en cada método de devolución de llamada. Sin embargo, ambos diagramas son complejos y pueden ser confusos. En este codelab, en lugar de solo leer lo que significa cada estado y devolución de llamada, investigarás y desarrollarás tus ideas sobre lo que está sucediendo.

Paso 1: Observa el método onCreate() y agrega registros

Para determinar lo que sucede con el ciclo de vida de Android, es útil saber cuándo se llama a los diferentes métodos de ciclo de vida. Esto te ayudará a identificar problemas en DessertClicker.

Una manera sencilla de hacerlo es usar la funcionalidad de registro de Android. El registro te permite escribir mensajes cortos en una consola mientras la app se ejecuta, y puedes usarlo para que te muestre cuándo se activan las diferentes devoluciones de llamada.

  1. Ejecuta la app DessertClicker y presiona varias veces la imagen del postre. Observa cómo cambia el valor de Postres vendidos y el importe total en dólares.
  2. Abre MainActivity.kt y examina el método onCreate() para esta actividad:
override fun onCreate(savedInstanceState: Bundle?) {
...
}

En el diagrama del ciclo de vida de la actividad, es posible que hayas reconocido el método onCreate(), dado que ya usaste esta devolución de llamada con anterioridad. Este es el único método que deben implementar todas las actividades. El método onCreate() es aquel en el cual debes realizar las inicializaciones únicas para tu actividad. Por ejemplo, en onCreate(), se aumenta el diseño, se definen objetos de escucha de clics o se configura la vinculación de vistas.

9be2255ff49e0af8.png

Se llama al método de ciclo de vida de onCreate() una vez, justo después de que se inicializa la actividad (cuando se crea el nuevo objeto Activity en la memoria). Después de que se ejecuta onCreate(), la actividad se considera creada.

  1. En el método onCreate(), justo después de la llamada a super.onCreate(), agrega la siguiente línea:
Log.d("MainActivity", "onCreate Called")
  1. Si es necesario, importa la clase Log (presiona Alt+Enter, o Option+Enter en una Mac, y selecciona Import). Si habilitaste las importaciones automáticas, esto debe ocurrir automáticamente.
import android.util.Log

La clase Log escribe mensajes en Logcat. Logcat es la consola para registrar mensajes. Aquí aparecen los mensajes de Android sobre tu app, incluidos los mensajes que envías de manera explícita al registro con el método Log.d() u otros métodos de clase Log.

Este comando tiene tres partes:

  • La prioridad del mensaje de registro, es decir, cuán importante es el mensaje. En este caso, el método Log.d() escribe un mensaje de depuración. Otros métodos de la clase Log son Log.i() para mensajes informativos, Log.e() para errores, Log.w() para advertencias y Log.v() para mensajes detallados.
  • La etiqueta del registro (el primer parámetro), en este caso, "MainActivity". La etiqueta es una string que te permite encontrar con mayor facilidad los mensajes de registro en Logcat. La etiqueta suele ser el nombre de la clase.
  • El mensaje de registro real (el segundo parámetro), que es una string corta. En este caso, es "onCreate called".

Una constante de tiempo de compilación es un valor que no cambia. Usa const antes de declarar una variable a fin de marcarla como una constante de tiempo de compilación.

  1. Compila y ejecuta la app DessertClicker. No verás ninguna diferencia de comportamiento en la app cuando presiones el postre. En Android Studio, en la parte inferior de la pantalla, haz clic en la pestaña Logcat.

ff9c50376701877f.png

  1. En la ventana Logcat, escribe D/MainActivity en el campo de búsqueda.

bb0b78600cd47789.png

Logcat puede contener muchos mensajes, y la mayoría no te resultará útil. Puedes filtrar las entradas de Logcat de muchas maneras, pero es más sencillo realizar una búsqueda. Dado que usaste MainActivity como la etiqueta de registro en tu código, puedes usar esa etiqueta a fin de filtrar el registro. Agregar D/ al principio significa que se trata de un mensaje de depuración creado por Log.d().

El mensaje de registro incluye la fecha y la hora, el nombre del paquete (com.example.android.dessertclicker), la etiqueta de registro (con D/ al inicio) y el mensaje en sí. Como este mensaje aparece en el registro, sabes que se ejecutó onCreate().

Paso 2: Implementa el método onStart()

Se llama al método de ciclo de vida de onStart() justo después de onCreate(). Una vez que se ejecute onStart(), tu actividad se visualizará en la pantalla. A diferencia de lo que ocurre con onCreate(), que se llama solo una vez para inicializar tu actividad, se puede llamar a onStart() muchas veces durante el ciclo de vida de tu actividad.

385df4ce82ae2de9.png

Ten en cuenta que onStart() está vinculado con el método de ciclo de vida onStop() correspondiente. Si el usuario inicia tu app y luego regresa a la pantalla principal del dispositivo, la actividad se detendrá y ya no estará visible en la pantalla.

  1. En Android Studio, con MainActivity.kt abierto y el cursor dentro de la clase MainActivity, selecciona Code > Override Methods o presiona Control+o (Command+o en Mac). Aparecerá un diálogo con una lista enorme de todos los métodos que puedes anular en esta clase. e1f2460242b2ae.png
  2. Comienza a ingresar onStart para buscar el método adecuado. Para desplazarte al siguiente elemento coincidente, usa la flecha hacia abajo. Selecciona onStart() de la lista y haz clic en OK para insertar el código estándar de anulación. El código se ve así:
override fun onStart() {
   super.onStart()
}
  1. Agrega la siguiente constante en el nivel superior de MainActivity.kt, que está por encima de la declaración de la clase, class MainActivity.
const val TAG = "MainActivity"
  1. Dentro del método onStart(), agrega un mensaje de registro:
override fun onStart() {
   super.onStart()
   Log.d(TAG, "onStart Called")
}
  1. Compila y ejecuta la app DessertClicker, y abre el panel de Logcat. Escribe D/MainActivity en el campo de búsqueda para filtrar el registro. Ten en cuenta que los métodos onCreate() y onStart() se llamaron uno tras otro y que tu actividad se visualiza en pantalla.
  2. Presiona el botón de inicio del dispositivo y luego usa la pantalla Recientes para volver a la actividad. Ten en cuenta que la actividad se reanudará donde la dejaste, con los mismos valores, y que onStart() se registrará por segunda vez en Logcat. Observa también que no se suele volver a llamar al método onCreate().
16:19:59.125 31107-31107/com.example.android.dessertclicker D/MainActivity: onCreate Called
16:19:59.372 31107-31107/com.example.android.dessertclicker D/MainActivity: onStart Called
16:20:11.319 31107-31107/com.example.android.dessertclicker D/MainActivity: onStart Called

Paso 3: Agrega más instrucciones de registro

En este paso, implementarás el registro de todos los demás métodos del ciclo de vida.

  1. Anula el resto de los métodos del ciclo de vida en tu MainActivity y agrega instrucciones de registro para cada uno. Este es el código:
override fun onResume() {
   super.onResume()
   Log.d(TAG, "onResume Called")
}

override fun onPause() {
   super.onPause()
   Log.d(TAG, "onPause Called")
}

override fun onStop() {
   super.onStop()
   Log.d(TAG, "onStop Called")
}

override fun onDestroy() {
   super.onDestroy()
   Log.d(TAG, "onDestroy Called")
}

override fun onRestart() {
   super.onRestart()
   Log.d(TAG, "onRestart Called")
}
  1. Vuelve a compilar y ejecutar DessertClicker, y revisa Logcat. Esta vez, observa que, además de onCreate() y onStart(), hay un mensaje de registro para la devolución de llamada de ciclo de vida onResume().
2020-10-16 10:27:33.244 22064-22064/com.example.android.dessertclicker D/MainActivity: onCreate Called
2020-10-16 10:27:33.453 22064-22064/com.example.android.dessertclicker D/MainActivity: onStart Called
2020-10-16 10:27:33.454 22064-22064/com.example.android.dessertclicker D/MainActivity: onResume Called

Cuando una actividad se inicie desde cero, verás las tres devoluciones de llamada de ciclo de vida llamadas en orden:

  • onCreate() para crear la app
  • onStart() para iniciarla y hacerla visible en la pantalla
  • onResume() para poner atención en la actividad y prepararla de modo que el usuario interactúe con ella

A pesar de su nombre, se llama al método onResume() durante el inicio, incluso si no hay nada para reanudar.

160054d59f67519.png

Ahora que la app DessertClicker está configurada para el registro, está todo listo para comenzar a usar la app de varias maneras y puedes explorar la forma en que se activan las devoluciones de llamada de ciclo de vida en respuesta a esos usos.

Caso de uso 1: Cómo abrir y cerrar la actividad

Comenzarás con el caso de uso más básico, que es iniciar tu app por primera vez y luego cerrarla por completo.

  1. Compila y ejecuta la app DessertClicker si aún no se está ejecutando. Como ya viste, se llama a las devoluciones de llamada onCreate(), onStart() y onResume() cuando la actividad se inicia por primera vez.
2020-10-16 10:27:33.244 22064-22064/com.example.android.dessertclicker D/MainActivity: onCreate Called
2020-10-16 10:27:33.453 22064-22064/com.example.android.dessertclicker D/MainActivity: onStart Called
2020-10-16 10:27:33.454 22064-22064/com.example.android.dessertclicker D/MainActivity: onResume Called
  1. Presiona el pastelito algunas veces.
  2. Presiona el botón Atrás en el dispositivo. En Logcat, observa que se llama a onPause(), onStop() y onDestroy() en ese orden.
2020-10-16 10:31:53.850 22064-22064/com.example.android.dessertclicker D/MainActivity: onPause Called
2020-10-16 10:31:54.620 22064-22064/com.example.android.dessertclicker D/MainActivity: onStop Called
2020-10-16 10:31:54.622 22064-22064/com.example.android.dessertclicker D/MainActivity: onDestroy Called

En este caso, usar el botón Atrás hace que la actividad (y la app) se cierre por completo. La ejecución del método onDestroy() significa que la actividad se cerró por completo y puede realizarse la recolección de elementos no utilizados. La recolección de elementos no utilizados se refiere a la limpieza automática de los objetos que ya no usarás. Una vez que se llame a onDestroy(), el sistema sabrá que esos recursos se pueden descartar y comenzará a limpiar esa memoria. 2dcc4d9c6478a9f4.png Tu actividad también puede cerrarse por completo si tu código llama manualmente al método finish() de la actividad o si el usuario fuerza el cierre de la app (por ejemplo, el usuario puede forzar o cerrar la app en la pantalla Recientes). El sistema Android también puede cerrar tu actividad por sí solo si tu app no aparece en la pantalla por mucho tiempo. El propósito de este proceso es conservar la batería y permitir que otras apps usen los recursos que utiliza la tuya.

  1. Para volver a la aplicación DessertClicker, busca todas las apps abiertas en la pantalla de información general. (Ten en cuenta que esta pantalla también se conoce como pantalla Recientes o de apps recientes). A continuación, te mostramos el registro de Logcat:
2020-10-16 10:31:54.622 22064-22064/com.example.android.dessertclicker D/MainActivity: onDestroy Called
2020-10-16 10:38:00.733 22064-22064/com.example.android.dessertclicker D/MainActivity: onCreate Called
2020-10-16 10:38:00.787 22064-22064/com.example.android.dessertclicker D/MainActivity: onStart Called
2020-10-16 10:38:00.788 22064-22064/com.example.android.dessertclicker D/MainActivity: onResume Called

La actividad se destruyó en el paso anterior, de modo que, cuando vuelves a la app, Android inicia una actividad nueva y llama a los métodos onCreate(), onStart() y onResume(). Observa que no se retuvo ninguno de los registros de DessertClicker de la actividad anterior.

El método onCreate() es un paso importante. Aquí es donde se realiza la primera inicialización, donde se configura el diseño por primera vez (aumentando su tamaño) y donde inicializas tus variables.

Caso de uso 2: Cómo salir de la actividad y volver a ella

Ahora que iniciaste la app y la cerraste por completo, viste la mayoría de los estados del ciclo de vida para cuando la actividad se crea por primera vez. También conoces todos los estados del ciclo de vida que la actividad atraviesa cuando se cierra y se destruye por completo. Sin embargo, a medida que los usuarios interactúan con sus dispositivos Android, pasan de una app a otra, regresan al inicio, inician apps nuevas y manejan interrupciones provocadas por otras actividades (por ejemplo, llamadas telefónicas).

Tu actividad no se cierra por completo cada vez que el usuario sale de la actividad:

  • Cuando tu actividad ya no está visible en la pantalla, se habla de que se colocó la actividad en segundo plano. (Lo contrario de esto es cuando la actividad está en primer plano o en pantalla).
  • Cuando el usuario regresa a tu app, se reinicia la misma actividad y la app vuelve a estar visible. Esta parte del ciclo de vida se denomina ciclo de vida visible de la app.

Cuando la app está en segundo plano, en general, no debe estar activa a fin de conservar los recursos del sistema y la duración de la batería. Puedes usar el ciclo de vida de Activity y sus devoluciones de llamada para saber en qué momento tu app pasa a segundo plano de modo que puedas pausar las operaciones en curso. Luego, reinicia las operaciones cuando tu app pase a primer plano.

En este paso, observas el ciclo de vida de la actividad cuando la app pasa a segundo plano y vuelve a primer plano.

  1. Con la app DessertClicker en ejecución, haz clic varias veces en el pastelito.
  2. Presiona el botón de inicio en el dispositivo y observa el Logcat en Android Studio. Cuando regresas a la pantalla principal, tu aplicación se ejecuta en segundo plano en lugar de cerrarse por completo. Ten en cuenta que se llama a los métodos onPause() y onStop(), pero no a onDestroy().
2020-10-16 10:41:05.383 22064-22064/com.example.android.dessertclicker D/MainActivity: onPause Called
2020-10-16 10:41:05.966 22064-22064/com.example.android.dessertclicker D/MainActivity: onStop Called

Cuando se llama a onPause(), la app ya no tiene foco. Después de onStop(), la app deja de estar visible en la pantalla. Aunque la actividad se detuvo, el objeto Activity todavía está en la memoria, en segundo plano. No se destruyó la actividad. El usuario podría regresar a la app, por lo que Android conserva los recursos de la actividad. b488b32801220b79.png

  1. Usa la pantalla Recientes para volver a la app. Observa en Logcat que la actividad se reinicia con onRestart() y onStart(), y luego se reanuda con onResume().
2020-10-16 10:42:18.144 22064-22064/com.example.android.dessertclicker D/MainActivity: onRestart Called
2020-10-16 10:42:18.158 22064-22064/com.example.android.dessertclicker D/MainActivity: onStart Called
2020-10-16 10:42:18.158 22064-22064/com.example.android.dessertclicker D/MainActivity: onResume Called

Cuando la actividad regresa a primer plano, no se vuelve a llamar al método onCreate(). No se destruyó el objeto de la actividad, por lo que no es necesario volver a crearlo. En lugar de onCreate(), se llama al método onRestart(). Ten en cuenta que esta vez, cuando la actividad regrese a primer plano, se conservará la cantidad de Postres vendidos.

  1. Inicia por lo menos una app que no sea DessertClicker de modo que el dispositivo tenga algunas apps en su pantalla Recientes.
  2. Abre la pantalla Recientes y abre otra actividad reciente. Luego, regresa a las apps recientes y vuelve a poner DessertClicker en primer plano.

Observa que ves las mismas devoluciones de llamada en Logcat aquí que cuando presionaste el botón de inicio. Se llama a onPause() y onStop() cuando la app pasa a segundo plano y, luego, a onRestart(), onStart() y onResume() cuando regresa.

Se llama a estos métodos cuando la app se detiene y pasa a segundo plano, o cuando la app se inicia nuevamente cuando regresa a primer plano. Si necesitas realizar algunas tareas en tu app durante estos casos, anula el método de devolución de llamada de ciclo de vida correspondiente.

¿Qué ocurre con onRestart()? El método onRestart() es muy similar a onCreate(). Se llama a onCreate() o onRestart() antes de que se muestre la actividad. Se llama al método onCreate() solo la primera vez. Luego, se llama a onRestart(). El método onRestart() es un lugar para colocar código que quieras llamar solamente si la actividad no se inicia por primera vez.

Caso de uso 3: Cómo ocultar parcialmente la actividad

Ya vimos que, cuando se inicia una app y se llama a onStart(), la app se hace visible en la pantalla. Cuando se reanuda la app y se llama a onResume(), la app atrae la atención del usuario, es decir, el usuario puede interactuar con ella. La parte del ciclo de vida en la que la app se muestra en pantalla y tiene la atención del usuario se denomina ciclo de vida interactivo.

Cuando la app pasa a segundo plano, el foco de atención se pierde después de onPause(), y la app deja de ser visible después de onStop().

La diferencia entre el foco de atención y la visibilidad es importante porque es posible que una actividad sea parcialmente visible en pantalla, pero no tenga la atención del usuario. En este paso, verás un caso de este tipo.

  1. Con la app DessertClicker en ejecución, haz clic en el botón Share, en la parte superior derecha de la pantalla.
  2. La actividad de la función compartir aparece en la mitad inferior de la pantalla, pero la actividad aún está visible en la mitad superior. e2319779260eb5ee.png

9ddc8b1dc79b1bff.png

  1. Revisa Logcat y observa que solo se llamó a onPause().
2020-10-16 11:00:53.857 22064-22064/com.example.android.dessertclicker D/MainActivity: onPause Called

En este caso de uso, no se llama a onStop() porque la actividad aún está parcialmente visible. Sin embargo, la actividad no tiene la atención del usuario, y el usuario no puede interactuar con ella (la actividad "compartir" que está en primer plano tiene el foco de atención del usuario).

¿Por qué es importante esta diferencia? Por lo general, la interrupción con solo onPause() dura un tiempo breve antes de volver a tu actividad o navegar a otra actividad o app. Por lo general, querrás seguir actualizando la IU de modo que el resto de tu app no parezca bloqueado.

Cualquier código que se ejecute en onPause() bloquea otras cosas, por lo que debes mantener un código liviano en onPause(). Por ejemplo, si llegara una llamada telefónica, el código de onPause() podría retrasar la notificación de llamada entrante.

  1. Haz clic fuera del cuadro de diálogo de la función compartir para volver a la app y observa que se llama a onResume().

onResume() y onPause() están relacionados con el foco. Se llama al método onResume() cuando la actividad tiene el foco, y se llama a onPause() cuando esta lo pierde.

Hay otro caso en la administración del ciclo de vida de la actividad que es importante comprender: se trata de cómo los cambios en la configuración afectan el ciclo de vida de tus actividades.

Un cambio de configuración ocurre cuando cambia el estado del dispositivo de manera tan radical que la forma más simple de que el sistema resuelva el cambio es cerrar y volver a crear la actividad por completo. Por ejemplo, si el usuario cambia el idioma del dispositivo, es posible que todo el diseño deba cambiar para adaptarse a diferentes orientaciones y longitudes de texto. Si el usuario enchufa el dispositivo a un conector o agrega un teclado físico, es posible que el diseño de la app deba aprovechar otro diseño o tamaño de pantalla. Además, si cambia la orientación del dispositivo (si el dispositivo rota del modo de retrato al modo de paisaje, o viceversa), es posible que el diseño deba modificarse a fin de que se ajuste a la nueva orientación. Veamos cómo se comporta la app en este caso.

Pérdida de datos tras la rotación del dispositivo

  1. Compila y ejecuta tu app, y abre Logcat.
  2. Rota el dispositivo o el emulador al modo de paisaje. Puedes rotar el emulador hacia la izquierda o la derecha con los botones de rotación o con Control y las teclas de flecha (Command y las teclas de flecha en Mac). 623fce7c623d42bd.png
  3. Revisa el resultado en Logcat. Filtra el resultado en MainActivity.
2020-10-16 11:03:09.618 23206-23206/com.example.android.dessertclicker D/MainActivity: onCreate Called
2020-10-16 11:03:09.806 23206-23206/com.example.android.dessertclicker D/MainActivity: onStart Called
2020-10-16 11:03:09.808 23206-23206/com.example.android.dessertclicker D/MainActivity: onResume Called
2020-10-16 11:03:24.488 23206-23206/com.example.android.dessertclicker D/MainActivity: onPause Called
2020-10-16 11:03:24.490 23206-23206/com.example.android.dessertclicker D/MainActivity: onStop Called
2020-10-16 11:03:24.493 23206-23206/com.example.android.dessertclicker D/MainActivity: onDestroy Called
2020-10-16 11:03:24.520 23206-23206/com.example.android.dessertclicker D/MainActivity: onCreate Called
2020-10-16 11:03:24.569 23206-23206/com.example.android.dessertclicker D/MainActivity: onStart Called

Ten en cuenta que, cuando el dispositivo o el emulador rotan la pantalla, el sistema llama a todas las devoluciones de llamada de ciclo de vida para cerrar la actividad. Luego, mientras se vuelve a crear la actividad, el sistema llama a todas las devoluciones de llamada de ciclo de vida para iniciarla.

  1. Cuando el dispositivo se rota y la actividad se cierra y se vuelve a crear, esta se inicia con los valores predeterminados: la cantidad de postres vendidos y los importes se restablecen a cero.

Usa onSaveInstanceState() para guardar los datos del paquete

El método onSaveInstanceState() es una devolución de llamada que usas para guardar los datos que podrías necesitar si se destruyera la Activity. En el diagrama de devolución de llamada de ciclo de vida, se llama a onSaveInstanceState() después de que se detiene la actividad. Se llama cada vez que tu app pasa a segundo plano.

c259ab6beca0ca88.png

Considera la llamada onSaveInstanceState() como una medida de seguridad. Te da la posibilidad de guardar una pequeña cantidad de información en un paquete cuando tu actividad se va de primer plano. El sistema guarda estos datos ahora porque, si espera hasta que se cierre tu app, corre el riesgo de hacerlo con recursos bajo presión.

Guardar los datos garantiza que los datos actualizados en el paquete estén disponibles para restablecerse si es necesario.

  1. En MainActivity, anula la devolución de llamada onSaveInstanceState() y agrega una instrucción de registro.
override fun onSaveInstanceState(outState: Bundle) {
   super.onSaveInstanceState(outState)

   Log.d(TAG, "onSaveInstanceState Called")
}
  1. Compila y ejecuta la app, y haz clic en el botón de inicio a fin de ponerla en segundo plano. Ten en cuenta que la devolución de llamada a onSaveInstanceState() se produce justo después de onPause() y onStop():
2020-10-16 11:05:21.726 23415-23415/com.example.android.dessertclicker D/MainActivity: onPause Called
2020-10-16 11:05:22.382 23415-23415/com.example.android.dessertclicker D/MainActivity: onStop Called
2020-10-16 11:05:22.393 23415-23415/com.example.android.dessertclicker D/MainActivity: onSaveInstanceState Called
  1. En la parte superior del archivo, justo antes de la definición de la clase, agrega estas constantes:
const val KEY_REVENUE = "revenue_key"
const val KEY_DESSERT_SOLD = "dessert_sold_key"

Usarás estas claves para guardar y recuperar datos del paquete de estado de la instancia.

  1. Desplázate hacia abajo hasta onSaveInstanceState() y observa el parámetro outState, que es del tipo Bundle.

Un objeto Bundle es una colección de pares clave-valor en la que las claves siempre son strings. Puedes colocar datos simples, como valores Int y Boolean, en el paquete. Debido a que el sistema conserva este paquete en la memoria, se recomienda mantener una cantidad pequeña de datos en el paquete. El tamaño de este paquete también es limitado, aunque varía según el dispositivo. Si almacenas demasiados datos, corres el riesgo de que tu app falle con el error TransactionTooLargeException. 5. En onSaveInstanceState(), coloca el valor revenue (un número entero) en el paquete con el método putInt():

outState.putInt(KEY_REVENUE, revenue)

El método putInt() (y métodos similares de la clase Bundle, como putFloat() y putString()), toma dos argumentos: una string para la clave (la constante KEY_REVENUE) y el valor real para guardar.

  1. Repite el mismo proceso con la cantidad de postres vendidos:
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)

Usa onCreate() para restablecer datos de paquetes

El estado de la actividad se puede restablecer en onCreate(Bundle) o onRestoreInstanceState(Bundle) (el Bundle propagado por el método onSaveInstanceState() se pasará a ambos métodos de devolución de llamada de ciclo de vida).

  1. Desplázate hasta onCreate() y revisa la firma del método:
override fun onCreate(savedInstanceState: Bundle) {

Observa que, cada vez que se llama a onCreate(), este obtiene un Bundle. Cuando se reinicia tu actividad debido a un cierre de proceso, el paquete que guardaste se pasa a onCreate(). Si se inició la actividad de nuevo, este Bundle en onCreate() es null. Entonces, si el paquete no es null, sabes que estás "volviendo a crear" la actividad desde un punto conocido anteriormente.

  1. Agrega este código a onCreate(), justo después de configurar la variable binding:
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
}

La prueba de null determina si hay datos en el paquete o si este es null, lo que a su vez te indicará si la app se inició de nuevo o si se volvió a crear después de un cierre. Esta prueba es un patrón común para restablecer datos del paquete.

Ten en cuenta que la clave que usaste aquí (KEY_REVENUE) es la misma que usaste para putInt(). A fin de garantizar que uses la misma clave cada vez, te recomendamos que definas esas claves como constantes. Usa getInt() a fin de obtener los datos del paquete, tal como usaste putInt() a efectos de colocar datos en él. El método getInt() toma dos argumentos:

  • Una string que actúa como la clave, por ejemplo, "key_revenue" para el valor de los importes
  • Un valor predeterminado en caso de que no exista uno para esa clave en el paquete

El número entero que se obtiene del paquete se asigna a la variable revenue y la IU usará ese valor.

  1. Agrega métodos getInt() para restablecer los importes y la cantidad de postres vendidos.
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
   dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
}
  1. Compila y ejecuta la app. Presiona el pastelito al menos cinco veces hasta que cambie a una rosquilla.
  2. Rota el dispositivo. Ten en cuenta que esta vez la app muestra los importes y la cantidad de postres vendidos correctos del paquete. No obstante, también ten en cuenta que el postre volvió a ser un pastelito. 4179956182ffc634.png Queda un paso más para asegurarse de que la app regrese desde un cierre a la manera exacta en la que estaba.
  3. En MainActivity, revisa el método showCurrentDessert(). Ten en cuenta que este método determina qué imagen de postre se debe mostrar en la actividad según la cantidad actual de postres vendidos y la lista de postres en la variable allDesserts.
for (dessert in allDesserts) {
   if (dessertsSold >= dessert.startProductionAmount) {
       newDessert = dessert
   }
    else break
}

Este método depende de la cantidad de postres vendidos a fin de elegir la imagen correcta. Por lo tanto, no es necesario realizar ninguna acción para almacenar una referencia a la imagen en el paquete de onSaveInstanceState(). En ese paquete, ya almacenaste la cantidad de postres que se venden.

  1. En onCreate(), en el bloque que restablece el estado del paquete, llama a showCurrentDessert():
 if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
   dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
   showCurrentDessert()
}
  1. Compila y ejecuta la app, y rota la pantalla. Observa ahora que la cantidad de postres vendidos, los importes totales y la imagen del postre se restablecen correctamente.

Ciclo de vida de la actividad

  • El ciclo de vida de la actividad es un conjunto de estados a través de los cuales migra una actividad. El ciclo de vida de la actividad comienza cuando se crea la actividad por primera vez y finaliza cuando esta se destruye.
  • A medida que el usuario navega entre las actividades de tu app (y también cuando ingresa a la app y sale de ella), cada actividad cambia entre diferentes estados del ciclo de vida de la actividad.
  • Cada estado del ciclo de vida de la actividad tiene un método de devolución de llamada correspondiente que puedes anular en tu clase Activity. El conjunto principal de métodos de ciclo de vida consta de los siguientes: onCreate()onStart()onPause()onRestart()onResume()onStop()onDestroy().
  • Para agregar un comportamiento que ocurra cuando tu actividad pase a un estado del ciclo de vida, anula el método de devolución de llamada del estado.
  • Para agregar el esqueleto de métodos de anulación a tus clases en Android Studio, selecciona Code > Override Methods o presiona Control+o (Command+o en Mac).

Realiza un registro con Log

  • La API de Logging de Android y, en particular, la clase Log te permiten escribir mensajes cortos que se muestran en Logcat dentro de Android Studio.
  • Usa Log.d() para escribir un mensaje de depuración. Este método usa dos argumentos: la etiqueta de registro (en general, el nombre de la clase) y el mensaje de registro (una string corta).
  • Usa la ventana Logcat de Android Studio a fin de ver los registros del sistema, incluidos los mensajes que escribas.

Conserva el estado de una actividad

  • Cuando la app pasa a segundo plano, justo después de que se llama a onStop(), los datos de app se pueden guardar en un paquete. Algunos datos de app, como el contenido de un EditText, se guardan automáticamente.
  • El paquete es una instancia de Bundle, que es una colección de claves y valores. Las claves siempre son strings.
  • Usa la devolución de llamada onSaveInstanceState() para guardar otros datos en el paquete que quieras conservar, incluso si la app se cerró automáticamente. Para colocar datos en el paquete, usa los métodos de paquete que comienzan con put, como putInt().
  • Puedes recuperar los datos del paquete en el método onRestoreInstanceState() o con mayor frecuencia en onCreate(). El método onCreate() tiene un parámetro savedInstanceState que contiene el paquete.
  • Si la variable savedInstanceState es null, se inició la actividad sin un paquete de estado y no hay datos de estado para recuperar.
  • A fin de recuperar datos del paquete con una clave, usa los métodos Bundle que comienzan con get, como getInt().

Cambios de configuración

  • Un cambio de configuración ocurre cuando cambia el estado del dispositivo de manera tan radical que la forma más simple de que el sistema resuelva el cambio sea destruir y volver a crear la actividad por completo.
  • El ejemplo más común de un cambio de configuración es cuando el usuario rota el dispositivo del modo de retrato al modo de paisaje, o viceversa. Un cambio de configuración también puede ocurrir cuando cambia el idioma del dispositivo o se conecta un teclado físico.
  • Cuando se produce un cambio de configuración, Android invoca todas las devoluciones de llamada de cierre del ciclo de vida de la actividad. Luego, Android reinicia la actividad desde cero y ejecuta todas las devoluciones de llamada de inicio del ciclo de vida.
  • Cuando Android cierra una app debido a un cambio de configuración, reinicia la actividad con el paquete de estado disponible para onCreate().
  • Al igual que con el cierre del proceso, guarda el estado de tu app en el paquete, en onSaveInstanceState().