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 de forma secuencial. Todos los codelabs del curso se enumeran en la página de destino de los codelabs de Android Kotlin Fundamentals.
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 el ciclo de vida de la actividad con más detalle. También aprenderás sobre la biblioteca de ciclos de vida de Android Jetpack, que puede ayudarte a administrar los eventos del ciclo de vida con código mejor organizado y más 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 de
Activity
yFragment
, 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()
yonStop()
para realizar operaciones en diferentes momentos del ciclo de vida de la actividad o el 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 ciclos de vida de Android para crear un observador de ciclos de vida y facilitar la administración del ciclo de vida de actividades y fragmentos
- Cómo los cierres de procesos de Android afectan los datos de tu app y cómo guardar y restablecer esos datos automáticamente cuando Android cierra tu app
- Cómo la rotación del dispositivo y otros cambios de configuración generan cambios en los estados del ciclo de vida y afectan el estado de tu app
Actividades
- Modifica la app de DessertClicker para incluir una función de temporizador, y comienza y detén ese temporizador en varios momentos 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 de la app que podrían perderse si esta se cierra de forma inesperada. Agrega un código para restablecer esos datos cuando se vuelva a iniciar la app.
En este codelab, ampliarás la app de DessertClicker del codelab anterior. Agregarás un temporizador en segundo plano y, luego, convertirás 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 la actividad y el fragmento anulando varias devoluciones de llamada de ciclo de vida y registrando cuándo el sistema invoca esas devoluciones de llamada. En esta tarea, explorarás un ejemplo más complejo de administración de tareas de ciclo de vida en la app de DessertClicker. Usarás un temporizador que imprime una instrucción de registro cada segundo, con el recuento de la cantidad de segundos que se ha estado ejecutando.
Paso 1: Configura DessertTimer
- Abre la app de DessertClicker del último codelab. (Puedes descargar DessertClickerLogs aquí si no tienes la app).
- En la vista Project, expande java > com.example.android.dessertclicker y abre
DessertTimer.kt
. Observa que, en este momento, todo el código está comentado, por lo que no se ejecuta como parte de la app. - Selecciona todo el código en la ventana del editor. Selecciona Code > Comment with Line Comment o presiona
Control+/
(Command+/
en Mac). Este comando quita los comentarios 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). - Ten en cuenta que la clase
DessertTimer
incluyestartTimer()
ystopTimer()
, que inician y detienen el temporizador. CuandostartTimer()
se está ejecutando, el temporizador imprime un mensaje de registro cada segundo, con el recuento total de los segundos que se ha ejecutado el temporizador. A su vez, el métodostopTimer()
detiene el temporizador y las instrucciones de registro.
- Abre
MainActivity.kt
. En la parte superior de la clase, justo debajo de la variabledessertsSold
, agrega una variable para el temporizador:
private lateinit var dessertTimer : DessertTimer;
- Desplázate hacia abajo hasta
onCreate()
y crea un nuevo objetoDessertTimer
, justo después de la llamada asetOnClickListener()
:
dessertTimer = DessertTimer()
Ahora que tienes un objeto de temporizador de postre, considera dónde deberías iniciar y detener el temporizador 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 se muestre la actividad. Se llama al método onStop()
después de que la actividad deja de ser visible. Estas devoluciones de llamada parecen ser buenas candidatas para iniciar y detener el temporizador.
- En la clase
MainActivity
, inicia el temporizador en la devolución de llamadaonStart()
:
override fun onStart() {
super.onStart()
dessertTimer.startTimer()
Timber.i("onStart called")
}
- Detén el temporizador en
onStop()
:
override fun onStop() {
super.onStop()
dessertTimer.stopTimer()
Timber.i("onStop Called")
}
- Compila y ejecuta la app. En Android Studio, haz clic en el panel de Logcat. En el cuadro de búsqueda de Logcat, ingresa
dessertclicker
, que filtrará por las clasesMainActivity
yDessertTimer
. Observa que, una vez que se inicia la app, el temporizador también comienza a ejecutarse de inmediato. - Haz clic en el botón Atrás y observa que el temporizador se detiene de nuevo. El temporizador se detiene porque se destruyeron tanto la actividad como el temporizador que controla.
- Usa la pantalla Recientes para volver a la app. Observa en Logcat que el temporizador se reinicia desde 0.
- Haz clic en el botón Compartir. Observa en Logcat que el temporizador sigue en ejecución.
- Haz clic en el botón Inicio. Observa en Logcat que el temporizador deja de ejecutarse.
- Usa la pantalla Recientes para volver a la app. Observa en Logcat que el temporizador se reinicia desde donde lo dejaste.
- En
MainActivity
, en el métodoonStop()
, comenta la llamada astopTimer()
. Si agregas un comentario astopTimer()
, se muestra el caso en el que inicias una operación enonStart()
, pero olvidas detenerla de nuevo enonStop()
. - Compila y ejecuta la app, y haz clic en el botón de inicio después de que se inicie el temporizador. Aunque la app esté en segundo plano, el temporizador se ejecuta y usa recursos del sistema de forma continua. Si el temporizador sigue funcionando, se produce una pérdida de memoria en 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, detienes o quitas ese elemento en la devolución de llamada correspondiente. De esta manera, evitas que se ejecute algo cuando ya no es necesario.
- Quita los comentarios de la línea en
onStop()
donde detienes el temporizador. - Corta y pega la llamada a
startTimer()
deonStart()
aonCreate()
. Este cambio demuestra el caso en el que inicializas y comienzas un recurso enonCreate()
, en lugar de usaronCreate()
para inicializarlo yonStart()
para comenzarlo. - Compila y ejecuta la app. Observa que el temporizador comienza a funcionar, como se espera.
- Haz clic en Home para detener la app. El temporizador dejará de funcionar, como se espera.
- Usa la pantalla Recientes para volver a la app. Observa que el temporizador no se reinicia en este caso, ya que
onCreate()
solo se llama cuando se inicia la app, no cuando una app vuelve a primer plano.
Puntos clave para recordar:
- Cuando configures un recurso en una devolución de llamada de ciclo de vida, también debes desconfigurarlo.
- Realiza la configuración y la limpieza en los métodos correspondientes.
- Si configuras algo en
onStart()
, detén o desmantela la configuración enonStop()
.
En la app de DessertClicker, es bastante fácil ver que, si iniciaste el temporizador en onStart()
, debes detenerlo en onStop()
. Solo hay un temporizador, por lo que no es difícil recordar cómo detenerlo.
En una app para Android más compleja, es posible que configures muchas cosas en onStart()
o onCreate()
y, luego, las desactives todas en onStop()
o onDestroy()
. Por ejemplo, es posible que tengas animaciones, música, sensores o temporizadores que debas configurar y desmontar, y también iniciar y detener. Si olvidas uno, se producen errores y dolores de cabeza.
La biblioteca de Lifecycle, que forma parte de Android Jetpack, simplifica esta tarea. La biblioteca es especialmente útil en los casos en que debes hacer un seguimiento de muchas partes móviles, algunas de las cuales se encuentran en diferentes estados del ciclo de vida. La biblioteca invierte el funcionamiento de los ciclos de vida: Por lo general, la actividad o el fragmento le indican 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 en sí observa los cambios de ciclo de vida y, luego, hace lo que se necesita cuando ocurren esos cambios.
La biblioteca de Lifecycle tiene tres partes principales:
- Propietarios del ciclo de vida, que son los componentes que tienen (y, por lo tanto, "poseen") un ciclo de vida.
Activity
yFragment
son propietarios del ciclo de vida. Los propietarios del ciclo de vida implementan la interfazLifecycleOwner
. - 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 - Observadores del ciclo de vida, que observan el estado del ciclo de vida y realizan tareas cuando cambia el ciclo de vida Los observadores del ciclo de vida implementan la interfaz
LifecycleObserver
.
En esta tarea, convertirás la app de DessertClicker para usar la biblioteca de ciclo de vida de Android y aprenderás cómo la biblioteca facilita la administración de 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 el ciclo de vida de la actividad o el fragmento, y se inicien y detengan en respuesta a los cambios en esos estados del 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 fragmento.
- Abre la clase
DesertTimer.kt
. - Cambia la firma de la clase
DessertTimer
para que se vea de la siguiente manera:
class DessertTimer(lifecycle: Lifecycle) : LifecycleObserver {
Esta nueva definición de clase hace dos cosas:
- El constructor toma un objeto
Lifecycle
, que es el ciclo de vida que observa el temporizador. - La definición de la clase implementa la interfaz
LifecycleObserver
.
- Debajo de la variable
runnable
, agrega un bloqueinit
a la definición de la clase. En el bloqueinit
, usa el métodoaddObserver()
para conectar el objeto de ciclo de vida que se pasó desde el propietario (la actividad) a esta clase (el observador).
init {
lifecycle.addObserver(this)
}
- Anota
startTimer()
con@OnLifecycleEvent annotation
y usa el evento de ciclo de vidaON_START
. Todos los eventos de ciclo de vida que tu observador de ciclo de vida puede observar se encuentran en la claseLifecycle.Event
.
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {
- Haz lo mismo con
stopTimer()
, usando el eventoON_STOP
:
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopTimer()
Paso 2: Modifica MainActivity
Tu clase MainActivity
ya es propietaria del ciclo de vida a través de la herencia, ya que la superclase FragmentActivity
implementa LifecycleOwner
. Por lo tanto, no es necesario que hagas nada para que tu actividad tenga en cuenta el ciclo de vida. Todo lo que tienes que hacer es pasar el objeto de ciclo de vida de la actividad al constructor de DessertTimer
.
- Abre
MainActivity
. En el métodoonCreate()
, modifica la inicialización deDessertTimer
para incluirthis.lifecycle
:
dessertTimer = DessertTimer(this.lifecycle)
La propiedad lifecycle
de la actividad contiene el objeto Lifecycle
que posee esta actividad.
- Quita la llamada a
startTimer()
enonCreate()
y la llamada astopTimer()
enonStop()
. Ya no es necesario que le digas aDessertTimer
qué hacer en la actividad, ya queDessertTimer
ahora observa el ciclo de vida por sí mismo y recibe notificaciones automáticamente cuando cambia el estado del ciclo de vida. Ahora, todo lo que haces en estas devoluciones de llamada es registrar un mensaje. - Compila y ejecuta la app, y abre Logcat. Observa que el temporizador comenzó a funcionar, como se esperaba.
- Haz clic en el botón de inicio para poner la app en segundo plano. Observa que el temporizador dejó de funcionar, como se esperaba.
¿Qué sucede con tu app y sus datos si Android la cierra mientras está en segundo plano? Es importante comprender este caso límite complejo.
Cuando tu app pasa a segundo plano, no se destruye, solo se detiene y espera a que el usuario vuelva a acceder a ella. Sin embargo, una de las principales preocupaciones del SO Android es mantener la actividad que está en primer plano funcionando sin problemas. Por ejemplo, si el usuario está usando una app de GPS para ayudarlo a tomar un autobús, es importante renderizar esa app de GPS rápidamente y seguir mostrando las indicaciones. Es menos importante que la app de DessertClicker, que el usuario tal vez no haya visto en unos días, se ejecute sin problemas 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, Android incluso 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 una app que el SO Android cerró, Android la reinicia.
En esta tarea, simularás el cierre de un proceso de Android y examinarás qué sucede con tu app cuando se inicia de nuevo.
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, usarás adb
para cerrar el proceso de tu app y ver qué sucede cuando Android la cierra.
- Compila y ejecuta tu app. Haz clic en el pastelito varias veces.
- Presiona el botón de inicio para poner la app en segundo plano. Tu app ahora está detenida y está sujeta a cerrarse si Android necesita los recursos que está usando.
- En Android Studio, haz clic en la pestaña Terminal para abrir la terminal de línea de comandos.
- Escribe
adb
y presiona Retorno.
Si ves muchos resultados que comienzan conAndroid Debug Bridge version X.XX.X
y terminan contags to be used by logcat (see logcat —h
elp), todo está bien. Si, en cambio, vesadb: command not found
, asegúrate de que el comandoadb
esté disponible en tu ruta de ejecución. Para obtener instrucciones, consulta "Agrega adb a tu ruta de ejecución" en el capítulo Utilidades. - Copia y pega este comentario en la línea de comandos y presiona Retorno:
adb shell am kill com.example.android.dessertclicker
Este comando indica a los dispositivos o emuladores conectados que detengan el proceso con el nombre de paquete dessertclicker
, pero solo si la app se ejecuta en segundo plano. Como tu app estaba en segundo plano, no se muestra nada en la pantalla del dispositivo o 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 terminated". Haz clic en la pestaña Logcat para ver que nunca se ejecutó la devolución de llamada onDestroy()
. Tu actividad simplemente finalizó.
- Usa la pantalla Recientes para volver a la app. Tu app aparecerá en Recientes, ya sea que se haya ejecutado en segundo plano o se haya detenido por completo. Cuando usas la pantalla Recientes para volver a la app, la actividad se inicia de nuevo. La actividad pasa por todo el conjunto de devoluciones de llamada de inicio del ciclo de vida, incluida
onCreate()
. - Observa que, cuando se reinició la app, se restableció tu "puntuación" (tanto la cantidad de postres vendidos como el total en dólares) a los valores predeterminados (0). Si Android cerró tu app, ¿por qué no guardó su estado?
Cuando el SO reinicia tu app, Android hace todo lo posible para restablecerla al estado en el que se encontraba antes. Android toma el estado de algunas de tus vistas y lo guarda en un paquete cada vez que sales de la actividad. Algunos ejemplos de datos que se guardan automáticamente son el texto en un EditText (siempre que tengan 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 comorevenue
en la app de DessertClicker, el SO de Android no conoce estos datos ni su importancia para tu actividad. Debes agregar estos datos al paquete por tu cuenta.
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 cada vez garantiza que los datos de actualización en el paquete estén disponibles para restablecerse si es necesario.
- En
MainActivity
, anula la devolución de llamadaonSaveInstanceState()
y agrega una instrucción de registroTimber
.
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Timber.i("onSaveInstanceState Called")
}
- 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 deonPause()
yonStop()
: - 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.
- Desplázate hacia abajo hasta
onSaveInstanceState()
y observa el parámetrooutState
, que es del tipoBundle
.
Un paquete es una colección de pares clave-valor en la que las claves siempre son cadenas. Puedes colocar valores primitivos, como valoresint
yboolean
, en el paquete.
Debido a que el sistema conserva este paquete en la RAM, 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. Por lo general, debes almacenar mucho menos de 100,000, de lo contrario, corres el riesgo de que tu app falle con el errorTransactionTooLargeException
. - En
onSaveInstanceState()
, coloca el valorrevenue
(un número entero) en el paquete con el métodoputInt()
:
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.
- 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
- 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 paquete 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.
- Agrega este código a
onCreate()
, después de la configuración deDessertTimer
:
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.
- 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)
}
- Compila y ejecuta la app. Presiona el pastelito al menos cinco veces hasta que cambie a una rosquilla. Haz clic en Inicio para poner la app en segundo plano.
- 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
- Usa la pantalla Recientes para volver a la app. Observa que, esta vez, la app regresa con los valores correctos de ingresos y postres vendidos del paquete. No obstante, también ten en cuenta que el postre volvió a ser un pastelito. Queda un paso más para asegurarse de que la app regrese desde un cierre a la manera exacta en la que estaba.
- En
MainActivity
, revisa el métodoshowCurrentDessert()
. 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 variableallDesserts
.
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.
- En
onCreate()
, en el bloque que restablece el estado del paquete, llama ashowCurrentDessert()
:
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()
}
- Compila y ejecuta la app, y ponla en segundo plano. Usa
adb
para detener el proceso. Usa la pantalla Recientes para volver a la app. Observa ahora que la cantidad de postres vendidos, los ingresos totales y la imagen del postre se restablecen correctamente.
Hay un último caso especial en la administración del ciclo de vida de la actividad y el fragmento que es importante comprender: se trata de 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 orientaciones 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 visualización. 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 del ciclo de vida
- Compila y ejecuta tu app, y abre Logcat.
- Rota el dispositivo o el emulador al modo horizontal. 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). - 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. - En
MainActivity
, comenta todo el métodoonSaveInstanceState()
. - Vuelve a compilar y ejecutar tu app. Haz clic en el pastelito varias veces y rota el dispositivo o el emulador. Esta vez, cuando se rota el dispositivo y la actividad se cierra y se vuelve a crear, esta se inicia con los valores predeterminados.
Cuando se produce un cambio de configuración, Android usa 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 del proceso, usaonSaveInstanceState()
para colocar los datos de la app en el paquete. Luego, restablece los datos enonCreate()
para evitar perder los datos del estado de la actividad si se rota el dispositivo. - En
MainActivity
, quita la marca de comentario del métodoonSaveInstanceState()
, ejecuta la app, haz clic en el cupcake y rota la app o el dispositivo. Observa que, esta vez, los datos de postres se conservan durante 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. Al detener el elemento, te aseguras de que no siga funcionando cuando ya no sea necesario. Por ejemplo, si configuras un temporizador en
onStart()
, debes pausarlo o detenerlo enonStop()
. - Usa
onCreate()
solo para inicializar las partes de tu app que se ejecutan una vez, cuando se inicia la app por primera vez. UsaonStart()
para iniciar las partes de tu app que se ejecutan cuando se inicia la app y cada vez que vuelve al primer plano.
Biblioteca de Lifecycle
- Usa la biblioteca de ciclos de vida de Android para transferir el control del ciclo de vida de la actividad o el fragmento al componente real que debe tener en cuenta el ciclo de vida.
- Los propietarios del ciclo de vida son componentes que tienen (y, por lo tanto, "poseen") ciclos de vida, incluidos
Activity
yFragment
. Los propietarios del ciclo de vida implementan la interfazLifecycleOwner
. - Los observadores del ciclo de vida prestan atención al estado actual del ciclo de vida y realizan tareas cuando cambia el ciclo de vida. Los observadores del ciclo de vida implementan la interfaz
LifecycleObserver
. - Los objetos
Lifecycle
contienen los estados del ciclo de vida reales y activan eventos cuando cambia el ciclo de vida.
Para crear una clase que priorice el ciclo 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 del observador del ciclo de vida, anota los métodos optimizados para el ciclo 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 está observando el evento de ciclo de vidaonStart
.
Cierre de procesos y guardado del estado de la actividad
- Android regula las apps que se ejecutan en segundo plano para que la de primer plano pueda ejecutarse sin problemas. Esta regulación incluye limitar la cantidad de procesamiento que pueden realizar las apps en segundo plano y, a veces, incluso cerrar todo el proceso de la app.
- El usuario no puede saber si el sistema cerró una app en segundo plano. La app sigue apareciendo en la pantalla de Recientes y debería reiniciarse en el mismo estado en el que la dejó el usuario.
- 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 usaradb
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
onDestroy()
. La app simplemente se detiene.
Cómo conservar el estado de actividades y fragmentos
- Cuando la app pasa a segundo plano, justo después de que se llama a
onStop()
, los datos de la app se guardan en un paquete. Algunos datos de app, como el contenido de unEditText
, 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 conput
, comoputInt()
. - Puedes recuperar los datos del paquete en el método
onRestoreInstanceState()
o con mayor frecuencia enonCreate()
. El métodoonCreate()
tiene un parámetrosavedInstanceState
que contiene el paquete. - Si la variable
savedInstanceState
contienenull
, 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 conget
, comogetInt()
.
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 vertical al modo horizontal, 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:
- Activities (guía de la API)
Activity
(referencia de la API)- Cómo interpretar el ciclo de vida de una actividad
- Cómo manejar ciclos de vida con componentes optimizados para ciclos de vida
LifecycleOwner
Lifecycle
LifecycleObserver
onSaveInstanceState()
- Cómo administrar los cambios en la configuración
- Cómo guardar estados de IU
Otro:
- Timber (GitHub)
En esta sección, se enumeran las posibles actividades para el hogar para los alumnos que trabajan en este codelab como parte de un curso dirigido por un instructor. Depende del instructor hacer lo siguiente:
- Si es necesario, asigna una tarea.
- Comunicarles a los alumnos cómo enviar las actividades para el hogar.
- Califica las actividades para el hogar.
Los instructores pueden usar estas sugerencias en la medida que quieran y deben asignar cualquier otra actividad para el hogar que consideren apropiada.
Si estás trabajando en este codelab por tu cuenta, usa estas actividades para el hogar para probar tus conocimientos.
Cómo cambiar una app
Abre la app de DiceRoller de la lección 1. (Si no la tienes, puedes descargar la app aquí). Compila y ejecuta la app, y observa que, si rotas el dispositivo, se pierde el valor actual del dado. Implementa onSaveInstanceState()
para conservar ese valor en el paquete y restablecerlo 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
Pregunta 4
¿Bajo qué circunstancias el método onCreate()
en tu actividad recibe un objeto Bundle
con datos en él (es decir, que el objeto Bundle
no es null
)? Es posible que se aplique más de una respuesta.
- La actividad se reinicia luego de que el dispositivo se rota.
- La actividad se inicia desde cero.
- Se retoma la actividad una vez que vuelve del segundo plano.
- Se reinicia el dispositivo.
Comienza la siguiente lección:
Para obtener vínculos a otros codelabs de este curso, consulta la página de destino de los codelabs de Conceptos básicos de Kotlin para Android.