Aspectos básicos de Android Kotlin 04.2: Situaciones complejas de ciclo de vida

Este codelab es parte del curso Conceptos básicos de Kotlin para Android. Aprovecharás al máximo este curso si trabajas con los codelabs en secuencia. Todos los codelabs del curso se detallan en la página de destino de codelabs sobre los aspectos básicos de Kotlin para Android.

Introducción

En el último codelab, aprendiste sobre los ciclos de vida de Activity y Fragment, y exploraste los métodos que se llaman cuando cambia el estado del ciclo de vida en actividades y fragmentos. En este codelab, explorarás con mayor detalle el ciclo de vida de la actividad. También aprenderás sobre la biblioteca de ciclo de vida de Android Jetpack, que puede ayudarte a administrar eventos de ciclo de vida con código mejor organizado y fácil de mantener.

Conocimientos que ya deberías tener

  • Qué es una actividad y cómo crear una en tu app
  • Los conceptos básicos de los ciclos de vida Activity y Fragment, y las devoluciones de llamada que se invocan cuando una actividad pasa de un estado a otro
  • Cómo anular los métodos de devolución de llamada de ciclo de vida onCreate() y onStop() para realizar operaciones en diferentes momentos de la actividad o el ciclo de vida del fragmento

Qué aprenderás

  • Cómo configurar, iniciar y detener partes de tu app en las devoluciones de llamada de ciclo de vida
  • Cómo usar la biblioteca de ciclo de vida de Android a fin de crear un observador del ciclo de vida y facilitar la administración de la actividad y el ciclo de vida del fragmento
  • La manera en que los cierres de procesos de Android afectan los datos de tu app y cómo guardarlos y restablecerlos automáticamente cuando se cierra la app
  • La manera en que la rotación del dispositivo y otros cambios de configuración crean cambios en los estados del ciclo de vida y afectan el estado de tu app

Actividades

  • Modifica la app DessertClicker a fin de incluir una función de temporizador, y comienza y detén ese temporizador en varias horas del ciclo de vida de la actividad.
  • Modifica la app para usar la biblioteca de ciclo de vida de Android y convierte la clase DessertTimer en un observador del ciclo de vida.
  • Configura y usa Android Debug Bridge (adb) para simular el cierre de procesos de tu app y las devoluciones de llamada de ciclo de vida que se producen en ese momento.
  • Implementa el método onSaveInstanceState() para conservar los datos que podrían perderse si la app se cierra de forma inesperada. Agrega un código para restablecer esos datos cuando se vuelva a iniciar la app.

En este codelab, te expandirás en la app DessertClicker del codelab anterior. Agrega un temporizador en segundo plano y luego convierte la app para que use la biblioteca de ciclo de vida de Android.

En el codelab anterior, aprendiste a observar los ciclos de vida de las actividades y los fragmentos anulando varias devoluciones de llamada de ciclo de vida y a registrarlas cuando el sistema invoca esas devoluciones de llamada. En esta tarea, explorarás un ejemplo más complejo de administración de tareas del ciclo de vida en la app DessertClicker. Usarás un temporizador que imprime una instrucción de registro cada segundo, contando el número de segundos que se estuvo ejecutando.

Paso 1: Configura DessertTimer

  1. Abre la app DessertClicker del último codelab. (Puedes descargar DessertClickerLogs aquí si no lo tienes).
  2. En la vista Project, expande java > com.example.android.dessertclicker y abre DessertTimer.kt. Ten en cuenta que, en este momento, todo el código está comentado, por lo que no se ejecuta como parte de la app.
  3. Selecciona todo el código en la ventana del editor. Selecciona Code > Comment with Line Comment o presiona Control+/ (Command+/ en una Mac). Este comando elimina el comentario de todo el código del archivo. (Es posible que Android Studio muestre errores de referencia sin resolver hasta que vuelvas a compilar la app).
  4. Observa que la clase DessertTimer incluye startTimer() y stopTimer(), que inician y detienen el temporizador. Cuando se ejecuta startTimer(), el temporizador imprime un mensaje de registro cada segundo, con el recuento total de los segundos que el tiempo estuvo en ejecución. A su vez, el método stopTimer() detiene el temporizador y las instrucciones de registro.
  1. Abre MainActivity.kt. En la parte superior de la clase, debajo de la variable dessertsSold, agrega una variable para el temporizador:
private lateinit var dessertTimer : DessertTimer;
  1. Desplázate hacia abajo hasta onCreate() y crea un nuevo objeto DessertTimer, justo después de la llamada a setOnClickListener():
dessertTimer = DessertTimer()


Ahora que tienes un objeto de temporizador de postres, considera dónde debes comenzar y detenerlo para que se ejecute solo cuando la actividad esté en pantalla. En los próximos pasos, verás algunas opciones.

Paso 2: Inicia y detén el temporizador

Se llama al método onStart() justo antes de que la actividad se vuelva visible. Se llama al método onStop() después de que la actividad deja de ser visible. Estas devoluciones de llamada parecen buenas opciones para iniciar y detener el temporizador.

  1. En la clase MainActivity, inicia el temporizador de la devolución de llamada onStart():
    .
override fun onStart() {
   super.onStart()
   dessertTimer.startTimer()

   Timber.i("onStart called")
}
  1. Detén el temporizador en onStop():
override fun onStop() {
   super.onStop()
   dessertTimer.stopTimer()

   Timber.i("onStop Called")
}
  1. Compila y ejecuta la app. En Android Studio, haz clic en el panel Logcat. En el cuadro de búsqueda de Logcat, ingresa dessertclicker, que se filtrará por las clases MainActivity y DessertTimer. Ten en cuenta que, una vez que se inicie la app, el temporizador también comenzará a ejecutarse de inmediato.
  2. Haga clic en el botón Atrás y observe que el temporizador se detiene nuevamente. El temporizador se detiene porque se destruyen la actividad y el temporizador que controla.
  3. Usa la pantalla Recientes para volver a la app. Observa en Logcat que el temporizador se reinicia desde 0.
  4. Haz clic en el botón Compartir. En Logcat, observa que el temporizador aún está en ejecución.

  5. Haga clic en el botón Home. En Logcat, observa que el temporizador deja de ejecutarse.
  6. Usa la pantalla Recientes para volver a la app. Observa en Logcat que el temporizador vuelve a iniciarse desde donde se detuvo.
  7. En MainActivity, en el método onStop(), comenta la llamada a stopTimer(). Con los comentarios stopTimer(), se demuestra el caso en el que inicias una operación en onStart(), pero te olvidas de detenerlo en onStop().
  8. Compila y ejecuta la app, y haz clic en el botón de inicio una vez que comience el temporizador. Aunque la app está en segundo plano, el temporizador se está ejecutando y usa recursos del sistema de forma continua. Hacer que el temporizador siga ejecutándose es una fuga de memoria para tu app, y probablemente no sea el comportamiento que deseas.

    El patrón general es que cuando configuras o inicias algo en una devolución de llamada, debes detener o quitar esa información en la devolución de llamada correspondiente. De esta manera, evitarás que se ejecute algo cuando ya no sea necesario.
  1. Quita los comentarios de la línea de onStop() donde detienes el temporizador.
  2. Corta y pega la llamada startTimer() de onStart() a onCreate(). Este cambio demuestra el caso en el que inicializas e inicias un recurso en onCreate(), en lugar de usar onCreate() para inicializarlo y onStart() para iniciarlo.
  3. Compila y ejecuta la app. Observa que el temporizador comienza a ejecutarse, como se espera.
  4. Haz clic en Inicio para detener la app. Como debería esperar, el temporizador deja de ejecutarse.
  5. Usa la pantalla Recientes para volver a la app. Ten en cuenta que el temporizador no vuelve a iniciarse en este caso porque solo se llama a onCreate() cuando se inicia la app; no se llama cuando una app regresa al primer plano.

Puntos clave que debes recordar:

  • Cuando configuras un recurso en una devolución de llamada de ciclo de vida, también se elimina el recurso.
  • Realice la configuración y quite los métodos correspondientes.
  • Si configuraste algo en onStart(), detenlo o quítalo de nuevo en onStop().

En la app DessertClicker, es bastante fácil ver que, si iniciaste el temporizador en onStart(), debes detenerlo en onStop(). Como hay solo un temporizador, no es difícil recordarlo.

En una app para Android más compleja, puedes configurar muchos elementos en onStart() o onCreate() y, luego, eliminarlos en onStop() o onDestroy(). Por ejemplo, puedes tener animaciones, música, sensores o temporizadores que necesites configurar y eliminar, así como iniciar y detener. Si la olvidas, se producen errores y dolores de cabeza.

La biblioteca de ciclo de vida, que forma parte de Android Jetpack, simplifica esta tarea. La biblioteca es especialmente útil en los casos en que sea necesario realizar un seguimiento de muchas partes que se mueven, algunas de las cuales están en diferentes estados del ciclo de vida. La biblioteca gira en función del funcionamiento de los ciclos de vida: por lo general, la actividad o el fragmento le indica a un componente (como DessertTimer) qué hacer cuando se produce una devolución de llamada de ciclo de vida. Sin embargo, cuando usas la biblioteca de ciclo de vida, el componente observa los cambios en el ciclo de vida y, luego, hace lo que se necesita cuando se producen.

Existen tres partes principales de la biblioteca de ciclo de vida:

  • Los propietarios de ciclo de vida, que son los componentes que tienen (y, por lo tanto, & propio) un ciclo de vida. Activity y Fragment son propietarios del ciclo de vida. Los propietarios del ciclo de vida implementan la interfaz LifecycleOwner.
  • La clase Lifecycle, que contiene el estado real de un propietario del ciclo de vida y activa eventos cuando se producen cambios en el ciclo de vida.
  • Los observadores de ciclo de vida, que observan el estado del ciclo de vida y realizan tareas cuando cambia su ciclo de vida. Los observadores de Lifecycle implementan la interfaz LifecycleObserver.

En esta tarea, convertirás la app DessertClicker a fin de usar la biblioteca de ciclo de vida de Android y descubrir cómo la biblioteca facilita el trabajo con los ciclos de vida de actividades y fragmentos de Android.

Paso 1: Convierte DessertTimer en un LifecycleObserver

Una parte clave de la biblioteca de ciclo de vida es el concepto de observación del ciclo de vida. La observación permite que las clases (como DessertTimer) conozcan la actividad o el ciclo de vida del fragmento, y se inicien y se detengan en respuesta a los cambios en esos estados de ciclo de vida. Con un observador del ciclo de vida, puedes quitar la responsabilidad de iniciar y detener objetos de los métodos de actividad y fragmentos.

  1. Abre la clase DesertTimer.kt.
  2. Cambia la firma de la clase DessertTimer para que se vea de la siguiente manera:
class DessertTimer(lifecycle: Lifecycle) : LifecycleObserver {

Esta definición de clase nueva tiene dos efectos:

  • El constructor toma un objeto Lifecycle, que es el ciclo de vida que observa el temporizador.
  • La definición de clase implementa la interfaz LifecycleObserver.
  1. Debajo de la variable runnable, agrega un bloque init a la definición de la clase. En el bloque init, usa el método addObserver() para conectar el objeto de ciclo de vida que se pasó del propietario (la actividad) a esta clase (el observador).
 init {
   lifecycle.addObserver(this)
}
  1. Anota startTimer() con @OnLifecycleEvent annotation y usa el evento de ciclo de vida ON_START. Todos los eventos de ciclo de vida que el observador del ciclo de vida puede observar se encuentran en la clase Lifecycle.Event.
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {
  1. Haz lo mismo con stopTimer() usando el evento ON_STOP:
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopTimer()

Paso 2: Modifica MainActivity

Tu clase MainActivity ya es propietaria de un ciclo de vida mediante la herencia, ya que la superclase FragmentActivity implementa LifecycleOwner. Por lo tanto, no debes hacer nada para que tu actividad tenga en cuenta el ciclo de vida. Lo único que debes hacer es pasar el objeto de ciclo de vida de la actividad en el constructor DessertTimer.

  1. Abre MainActivity. En el método onCreate(), modifica la inicialización de DessertTimer para incluir this.lifecycle:
dessertTimer = DessertTimer(this.lifecycle)

La propiedad lifecycle de la actividad contiene el objeto Lifecycle que le pertenece a la actividad.

  1. Quita la llamada a startTimer() en onCreate() y la llamada a stopTimer() en onStop(). Ya no es necesario indicarle a DessertTimer qué hacer en la actividad, porque DessertTimer ahora está observando el ciclo de vida y se le notifica automáticamente cuando cambia su estado. Lo único que se debe hacer en estas devoluciones de llamada es registrar un mensaje.
  2. Compila y ejecuta la app, y abre Logcat. Observa que el temporizador comenzó a ejecutarse, como se espera.
  3. Haz clic en el botón de inicio para poner la aplicación en segundo plano. Observa que el temporizador dejó de ejecutarse, según lo esperado.

¿Qué sucede con tu app y sus datos si Android la cierra mientras está en segundo plano? Es importante comprender este caso raro.

Cuando la app pasa a segundo plano, no se destruye, solo se detiene y se espera a que el usuario vuelva a ella. Sin embargo, una de las principales inquietudes del SO Android es que la actividad en primer plano se ejecute sin problemas. Por ejemplo, si tu usuario está usando una app de GPS para tomar un autobús, es importante que muestres rápidamente esa app de GPS y siga mostrando las instrucciones sobre cómo llegar. Es menos importante mantener la app DessertClicker, que posiblemente el usuario no haya visto por algunos días, sin problemas y en segundo plano.

Android regula las apps en segundo plano para que la de primer plano pueda ejecutarse sin problemas. Por ejemplo, Android limita la cantidad de procesamiento que pueden realizar las apps en segundo plano.

En ocasiones, incluso Android cierra todo el proceso de la app, lo que incluye todas las actividades asociadas con ella. Android realiza este tipo de cierre cuando el sistema está sobrecargado y corre el riesgo de tener un atraso visual, por lo que en este momento no se ejecutan devoluciones de llamadas ni código adicional. El proceso de tu app simplemente se cierra, de manera silenciosa, en segundo plano. Sin embargo, para el usuario, no parece que la app se haya cerrado. Cuando el usuario vuelve a la app que el SO Android cerró, la app se reinicia.

En esta tarea, simularás el cierre del proceso de Android y examinarás lo que sucede con tu app cuando se vuelve a iniciar.

Paso 1: Usa adb para simular el cierre de un proceso

Android Debug Bridge (adb) es una herramienta de línea de comandos que te permite enviar instrucciones a emuladores y dispositivos conectados a tu computadora. En este paso, usas adb para cerrar el proceso de tu app y ver qué sucede cuando Android la cierra.

  1. Compila y ejecuta tu app. Haz clic en el pastelito algunas veces.
  2. Presiona el botón de inicio para poner la aplicación en segundo plano. Tu app ahora está detenida y la app está sujeta a cierre si Android necesita los recursos que la app usa.
  3. En Android Studio, haz clic en la pestaña Terminal para abrir la terminal de línea de comandos.
  4. Escribe adb y presiona Intro.

    Si observas muchos resultados que comienzan con Android Debug Bridge version X.XX.X y terminan con tags to be used by logcat (see logcat —help, todo está bien. Si, en cambio, ves adb: command not found, asegúrate de que el comando adb esté disponible en la ruta de ejecución. Para obtener instrucciones, consulta &adb; Agrega adb a tu ruta de ejecución en el capítulo Utilidades.
  5. Copia y pega este comentario en la línea de comandos y presiona Volver:
adb shell am kill com.example.android.dessertclicker

Este comando indica a los emuladores o dispositivos conectados que detengan el proceso con el nombre del paquete dessertclicker, pero solo si la app está en segundo plano. Como la app estaba en segundo plano, no se muestra nada en el dispositivo o en la pantalla del emulador para indicar que se detuvo el proceso. En Android Studio, haz clic en la pestaña Run para ver un mensaje que dice "Application completed." Haz clic en la pestaña Logcat para ver que nunca se ejecutó la devolución de llamada onDestroy(): tu actividad simplemente finalizó.

  1. Usa la pantalla Recientes para volver a la app. Aparece en la pestaña Recientes si pasó a segundo plano o se detuvo por completo. Cuando usas la pantalla Recientes para volver a la app, la actividad se inicia nuevamente. La actividad pasa por todo el conjunto de devoluciones de llamada de ciclo de vida de inicio, incluido onCreate().
  2. Ten en cuenta que, cuando se reinicia la app, se restablecen los valores predeterminados (la cantidad de postres vendidos y total) a los valores predeterminados (0). Si Android cerró tu app, ¿por qué no guardó tu estado?

    Cuando el SO reinicia la app por ti, Android hace todo lo posible para restablecerla al estado que tenía antes. Android toma el estado de algunas de tus vistas y las guarda en un paquete cuando sales de la actividad. Algunos ejemplos de datos que se guardan automáticamente son el texto de un EditText (siempre que tenga un ID establecido en el diseño) y la pila de actividades de tu actividad.

    Sin embargo, a veces el SO Android no conoce todos tus datos. Por ejemplo, si tienes una variable personalizada como revenue en la app DessertClicker, el SO Android no conoce estos datos ni su importancia para la actividad. Debes agregar estos datos al paquete.

Paso 2: Usa onSaveInstanceState() para guardar los datos del paquete

El método onSaveInstanceState() es la devolución de llamada que usas para guardar los datos que podrías necesitar si el SO Android destruye tu app. 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.

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, es posible que el SO esté bajo presión de recursos. Guardar los datos garantiza que los datos de actualizaciones del 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 Timber.
override fun onSaveInstanceState(outState: Bundle) {
   super.onSaveInstanceState(outState)

   Timber.i("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():
  2. 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"
const val KEY_TIMER_SECONDS = "timer_seconds_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 paquete es una colección de pares clave-valor en la que las claves siempre son strings. Puedes colocar valores básicos, como valores int y boolean, en el paquete.
    Como el sistema conserva este paquete en la RAM, se recomienda mantener pequeños los datos en el paquete. El tamaño de este paquete también es limitado, aunque varía según el dispositivo. En general, debes almacenar mucho menos de 100,000; de lo contrario, correrás el riesgo de que tu app falle con el error TransactionTooLargeException.
  2. 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 y el estado del temporizador:
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)
outState.putInt(KEY_TIMER_SECONDS, dessertTimer.secondsCount)

Paso 3: Usa onCreate() para restablecer datos de paquetes

  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 al cierre de un proceso, el paquete que guardaste se pasa a onCreate(). Si se inició la actividad de nuevo, este paquete en onCreate() es null. De modo que, si el paquete no es null, sabes que estás recreando la actividad desde un punto conocido anteriormente.

  1. Agrega este código a onCreate(), después de la configuración DessertTimer:
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 la cantidad de postres vendidos y el valor del temporizador:
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
   dessertTimer.secondsCount =
       savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
}
  1. Compila y ejecuta la app. Presiona el pastelito al menos cinco veces hasta que cambie a una rosquilla. Haz clic en Inicio para colocar la aplicación en segundo plano.
  2. En la pestaña Terminal de Android Studio, ejecuta adb para cerrar el proceso de la app.
adb shell am kill com.example.android.dessertclicker
  1. Usa la pantalla Recientes para volver a la app. Ten en cuenta que esta vez la app muestra los ingresos y postres correctos del paquete. No obstante, también ten en cuenta que el postre volvió a ser un pastelito. Queda algo más para asegurarte de que la app regrese desde un cierre a la manera exacta en la que estaba.
  2. 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)
   dessertTimer.secondsCount = 
      savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
   showCurrentDessert()                   
}
  1. Compila y ejecuta la app, y la coloca en segundo plano. Usa adb para cerrar el proceso. Usa la pantalla Recientes para volver a la app. Ten en cuenta que se restablecieron correctamente los valores de los postres, los ingresos totales y la imagen del postre.

Hay un último caso especial en la administración del ciclo de vida de la actividad y el fragmento que es importante de comprender: cómo los cambios en la configuración afectan el ciclo de vida de tus actividades y fragmentos.

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 direcciones 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.

Paso 1: Explora la rotación del dispositivo y las devoluciones de llamada de ciclo de vida

  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).
  3. Revisa el resultado en Logcat. Filtra el resultado en MainActivity.
    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.
  4. En MainActivity, comenta todo el método onSaveInstanceState().
  5. Vuelve a compilar y ejecutar tu app. Haz clic en el pastelito algunas veces y rota el dispositivo o emulador. Esta vez, cuando se rote el dispositivo y la actividad se apague y se vuelva a crear, la actividad se iniciará con los valores predeterminados.

    Cuando se realice un cambio de configuración, Android usará el mismo paquete de estado de instancia que aprendiste en la tarea anterior para guardar y restablecer el estado de la app. Al igual que con el cierre de proceso, usa onSaveInstanceState() para colocar los datos de tu app en el paquete. Luego, restablece los datos en onCreate() para evitar perder datos de estado de la actividad si se rota el dispositivo.
  6. En MainActivity, quita los comentarios del método onSaveInstanceState(), ejecuta la app, haz clic en el pastelito y rota la app o el dispositivo. Observa esta vez que los datos del postre se conservan en toda la rotación de la actividad.

Proyecto de Android Studio: DessertClickerFinal

Sugerencias sobre el ciclo de vida

  • Si configuras o inicias algo en una devolución de llamada de ciclo de vida, detén o quita ese elemento en la devolución de llamada correspondiente. Cuando detienes el problema, te aseguras de que no se siga ejecutando cuando ya no sea necesario. Por ejemplo, si configuras un temporizador en onStart(), debes pausarlo o detenerlo en onStop().
  • Usa onCreate() solo para inicializar las partes de la app que se ejecutan una vez, cuando esta se inicia por primera vez. Usa onStart() para iniciar las partes de la app que se ejecutan cuando se inicia y cada vez que la app regresa al primer plano.

Biblioteca de Lifecycle

  • Usa la biblioteca de ciclo de vida de Android a fin de cambiar el control del ciclo de vida de la actividad o del fragmento al componente real que debe estar optimizado para los ciclos de vida.
  • Los propietarios de ciclos de vida son componentes que tienen (y, por lo tanto, &propio) ciclos de vida, incluidos Activity y Fragment. Los propietarios del ciclo de vida implementan la interfaz LifecycleOwner.
  • Los observadores de Lifecycle prestan atención al estado actual del ciclo de vida y realizan tareas cuando cambia su ciclo de vida. Los observadores de Lifecycle implementan la interfaz LifecycleObserver.
  • Los objetos Lifecycle contienen los estados reales del ciclo de vida y activan eventos cuando cambia el ciclo de vida.

Para crear una clase optimizada para los ciclos de vida, haz lo siguiente:

  • Implementa la interfaz LifecycleObserver en las clases que deben tener en cuenta el ciclo de vida.
  • Inicializa una clase de observador del ciclo de vida con el objeto de ciclo de vida de la actividad o el fragmento.
  • En la clase de observador del ciclo de vida, anota los métodos optimizados para ciclos de vida con el cambio de estado del ciclo de vida que les interesa.

    Por ejemplo, la anotación @OnLifecycleEvent(Lifecycle.Event.ON_START) indica que el método observa el evento de ciclo de vida onStart.

Cómo procesar cierres y guardar el estado de una actividad

  • Android regula las apps que se ejecutan en segundo plano para que la de primer plano pueda ejecutarse sin problemas. Esta reglamentación incluye limitar la cantidad de procesamiento que pueden realizar las apps en segundo plano y, a veces, incluso cerrar todo el proceso.
  • El usuario no puede saber si el sistema cerró una app en segundo plano. La app aún aparece en la pantalla de recientes y debe reiniciarse en el mismo estado en que el usuario la dejó.
  • Android Debug Bridge (adb) es una herramienta de línea de comandos que te permite enviar instrucciones a emuladores y dispositivos conectados a tu computadora. Puedes usar adb para simular el cierre de un proceso en tu app.
  • Cuando Android cierra el proceso de tu app, no se llama al método del ciclo de vida de onDestroy(). La app simplemente se detiene.

Conserva la actividad y el estado de los fragmentos

  • Cuando tu app pasa a segundo plano, justo después de llamar a onStop(), los datos de la app se guardan 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 contiene 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 es cerrar y volver a crear la actividad.
  • 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().

Curso de Udacity:

Documentación para desarrolladores de Android:

Otro:

En esta sección, se enumeran las posibles tareas para los alumnos que trabajan con este codelab como parte de un curso que dicta un instructor. Depende del instructor hacer lo siguiente:

  • Si es necesario, asigna la tarea.
  • Informa a los alumnos cómo enviar los deberes.
  • Califica las tareas.

Los instructores pueden usar estas sugerencias lo poco o lo que quieran, y deben asignar cualquier otra tarea que consideren apropiada.

Si estás trabajando en este codelab por tu cuenta, usa estas tareas para poner a prueba tus conocimientos.

Cómo cambiar una app

Abre la app de DiceRoller de la Lección 1. (Puedes descargar la app aquí si no la tienes). Compila y ejecuta la app. Ten en cuenta que, si giras el dispositivo, se perderá el valor actual del dado. Implementa onSaveInstanceState() para conservar ese valor en el paquete y restablece ese valor en onCreate().

Responde estas preguntas

Pregunta 1

Tu app contiene una simulación física que requiere un procesamiento intensivo para mostrarse. Luego, el usuario recibe una llamada telefónica. ¿Cuál de estas afirmaciones es verdadera?

  • Durante la llamada telefónica, debes continuar procesando las posiciones de los objetos en la simulación física.
  • Durante una llamada telefónica, debes dejar de procesar las posiciones de los objetos en la simulación física.

Pregunta 2

¿Qué método de ciclo de vida deberías anular para pausar la simulación cuando la app no está en pantalla?

  • onDestroy()
  • onStop()
  • onPause()
  • onSaveInstanceState()

Pregunta 3

Para hacer que una clase priorice el ciclo de vida a través de la biblioteca de ciclo de vida de Android, ¿qué interfaz debe implementar la clase?

  • Lifecycle
  • LifecycleOwner
  • Lifecycle.Event
  • LifecycleObserver

Question 4

¿En qué circunstancias el método onCreate() de tu actividad recibe un Bundle con datos? (es decir, el Bundle no es null). Es posible que se aplique más de una respuesta.

  • La actividad se reinicia después de rotar el dispositivo.
  • La actividad se inicia desde cero.
  • La actividad se reanuda después de regresar del fondo.
  • Se reinicia el dispositivo.

Comienza la siguiente lección: 5.1: ViewModel y ViewModelFactory

Para ver vínculos a otros codelabs de este curso, consulta la página de destino de codelabs sobre aspectos básicos de Kotlin para Android.