La columna 80

El blog técnico-personal de Ángel J. Vico… en español

Ciclo de vida de las actividades de Android (I)

Posted by Ángel J. Vico en 3 de septiembre de 2011

Como ya hemos comentado en anteriores ocasiones, las aplicaciones Android se programan en Java, que es un lenguaje de programación orientado a objetos. Por lo tanto, resulta lógico que las actividades sean clases. En concreto, cada actividad de la aplicación es una clase definida por el usuario, que hereda de la clase Activity o de una de sus especializaciones.

Lo habitual es que sólo se cree un objeto de cada una de las actividades. El de la actividad principal se crea automáticamente cuando se inicia la aplicación y cada uno de los del resto de actividades los crea la actividad que se encarga de mostrar la siguiente.

A medida que se van apilando actividades (de una misma aplicación o de varias) se van creando los correspondientes objetos, cada uno en el proceso de la aplicación correspondiente. Sin embargo, mientras que la aplicación tiene bastante control sobre cuándo se crean esos objetos por primera vez, una vez creados es el sistema operativo el que decide cuándo son destruidos y cuándo se crean de nuevo. Y, aunque al principio pueda sorprender bastante, esto ocurre con mucha frecuencia. Por ejemplo, un simple giro del teléfono para cambiar su orientación provoca la destrucción y nueva creación de la actividad que se encuentra en la parte superior de la pila (la que es visible actualmente).

Lo malo es que la mayor parte de esas destrucciones y creaciones de actividades suceden en momentos en los que, desde el punto de vista del usuario, no debería estar creándose o destruyéndose nada. Es decir, un usuario puede asumir la creación de una nueva actividad (y que se muestre su estado inicial) nada más ejecutar la aplicación, pero no aceptaría que eso pasara simplemente por girar el teléfono. Una vez puesta en marcha la aplicación, el usuario no va a aceptar que se pierdan sus modificaciones salvo después de haber cerrado la aplicación (y, a menudo, ni siquiera en ese caso; aunque ese es otro tema). Nuestro trabajo como desarrolladores va a ser asegurarnos de que ese trajín que se trae Android con las actividades pase desapercibido y la aplicación se comporte como si permaneciera todo el tiempo en memoria, aunque no sea así.

En esto las aplicaciones Android también se parecen a las aplicaciones web. De la misma forma que una aplicación web se las apaña para mantener su estado y dar la sensación, en lo que al usuario respecta, de tener conexión constante con el servidor, nosotros tendremos que mantener el estado de nuestras actividades entre las múltiples destrucciones y creaciones a las que se verán sometidas.

Para ello, Android nos proporciona dos herramientas: almacenes donde guardar y recuperar el estado de la actividad, y un conjunto de eventos que se ejecutan en momentos clave y que nos permiten intervenir para salvaguardar y restaurar dicho estado. Esos eventos representan los diferentes estados de ejecución de la actividad (no confundir el estado de ejecución con el estado, desde el punto de vista de la información visualizada, de la propia actividad). Y son las transiciones entre esos estados las que conforman el ciclo de vida de la actividad, como se puede ver en el siguiente diagrama:

Ciclo de vida de las actividades

Eventos y métodos

Cada uno de los elementos que aparecen en el diagrama son métodos de la clase Activity que se ejecutan automáticamente en respuesta a los eventos correspondientes que se producen en la actividad (salvo el constructor y el método finalize, que los tienen todas las clases). Aunque la clase Activity proporciona implementaciones predeterminadas para todos ellos, podemos sobrescribirlos en nuestra actividad para personalizar su comportamiento. No obstante, si lo hacemos, hay una regla que debemos respetar siempre: en cada uno de ellos debemos incluir una llamada al método original de la clase Activity para que la funcionalidad predeterminada no se pierda. En la mayoría de los casos se producirá una excepción si no lo hacemos.

Cada uno de los métodos se ejecuta en un momento determinado y eso determina la funcionalidad que puede o debe contener. Es necesario que conozcamos qué condiciones provocan cada evento para determinar cuáles debemos utilizar en nuestras actividades y con qué propósito. Así que veamos más detalles de cada uno de ellos:

  • Constructor: La actividad, al ser una clase, tiene su propio constructor, que se ejecuta cada vez que se crea un nuevo objeto (una nueva versión de la actividad). Es importante tener en cuenta que hasta que no finaliza la ejecución de este método, la actividad no está realmente creada. Eso nos impide hacer uso de algunas utilidades que incorporan las actividades, lo que se traduce en que no podremos utilizar determinadas funciones sin que se produzcan excepciones. Por este motivo no es habitual implementar el constructor, relegando las tareas de inicialización al método onCreate.

    Sí que se podría utilizar, por ejemplo, para construir e inicializar estructuras de datos propias, reservar memoria o cosas similares que no dependieran de objetos de la API de Android.

  • onCreate: Se ejecuta en cuanto se crea la actividad, después del constructor. Es el lugar idóneo para inicializar la actividad: crear las vistas que contendrá, cargar los datos que se vayan a visualizar, etc.

    Este método recibe un objeto de tipo Bundle. Si no es la primera vez que se crea esta actividad en la ejecución actual de la aplicación, esto es, si la actividad ya ha existido antes pero fue destruida, el objeto Bundle contendrá lo necesario para restaurar su estado anterior de forma que el usuario se encuentre la actividad tal y como se la espera. Si no es así, el objeto será nulo.</p<

  • onStart: Este método se ejecuta cuando el sistema tiene intención de hacer visible la actividad, aunque luego no llegue a ocurrir realmente. Se suele utilizar para crear aquellos procesos cuyo objetivo es actualizar la interfaz de usuario: animaciones, temporizadores, localización GPS etc. Se hace en este método en lugar de en onCreate para no consumir recursos de forma innecesaria hasta que realmente hay intención de mostrar la actividad.

  • onResume: Este método se ejecuta justo antes de que la actividad sea completamente visible y el usuario pueda empezar a interactuar con ella. Es el sitio ideal para iniciar animaciones, acceder a recursos que se tienen que utilizar de forma exclusiva (como la cámara), etc. De esta forma no utilizamos los recursos más valiosos hasta que realmente van a ser necesarios. Hay que tener en cuenta que aunque se ejecute onStart, la actividad podría no llegar a visualizarse, por lo que no tendría sentido bloquear recursos o activar procesos que actualizan una interfaz de usuario que nadie va a ver.

  • onPause: Se ejecuta cuando el usuario deja de poder interactuar con la actividad actual, porque va a ser reemplazada por otra (la anterior o una que se superponga). Es, por lo tanto, el sitio ideal para detener todo lo que se ha activado en onResume y liberar los recursos de uso exclusivo (dado que podría necesitarlos la nueva actividad). También se suele aprovechar este método para guardar los cambios que no queremos que se pierdan entre ejecuciones de la aplicación, dado que desde este punto del ciclo de vida la actividad puede ser destruida o su proceso eliminado de memoria en cualquier momento (como veremos más adelante). En cualquier caso, la ejecución de este método debería ser lo más rápida posible, puesto que el usuario no podrá utilizar la nueva actividad hasta que finalice onPause.

  • onStop: Se llama a este método cuando la actividad ha sido ocultada totalmente por otra que ya está interactuando con el usuario. Aquí se suelen destruir los procesos creados en el método onStart, para liberar recursos y permitir que la actividad que está interactuando actualmente con el usuario pueda ir lo más fluida posible.

  • onRestart: Tal y como se aprecia en el diagrama, este método se ejecuta cuando una actividad había dejado de ser completamente visible pero tiene que mostrarse de nuevo (sin que haya sido destruida entre tanto, claro). Su uso no es tan habitual como el del resto de métodos, pero puede ser útil en algunos casos donde no compensa destruir ciertos elementos en onStop para luego tener que crearlos de nuevo en onStart, por cuestiones de eficiencia o porque eso provocaría el reinicio no deseado de alguna operación (consultas a una base de datos, por ejemplo). En esos casos lo que se hace es crear los procesos en onStart sólo la primera vez, detenerlos sin destruirlos en onStop y reanudarlos de nuevo en onRestart.

  • onDestroy: El sistema ejecuta este método cuando ya no tiene intención de reutilizar más el objeto que contiene la actividad, ya sea porque se ha eliminado la actividad desde el propio código o porque el sistema está tratando de liberar memoria. Aquí ya no se puede hacer otra cosa que destruir los objetos, hilos y demás que hayamos creado en onCreate, para no dejar basura atrás.

  • finalize: Este método es el destructor de la clase, lo ejecuta la máquina Dalvik cuando el recolector de basura va a eliminar el objeto. Por paralelismo, aquí habría que destruir lo que hayamos creado en el constructor, pero lo habitual es no usar ninguno de ellos. Si se usa hay que tener en cuenta que no hay forma de saber cuándo va a ser ejecutado (ni en qué orden, si hay varios objetos pendientes de destruir). Algo que suele ocurrir con frecuencia es que se creen varios objetos de una misma actividad y que no se llame a sus métodos finalize hasta que la aplicación completa se retire de memoria (e incluso en ese caso, esas llamadas podrían no llegar a hacerse nunca).

En el diagrama he incluido además dos métodos que, sorprendentemente, no suelen aparecer cuando se habla del ciclo de vida de las actividades, pero que creo que son lo suficientemente importantes como para hablar de ellos aquí:

  • onSaveInstanceState: Este método se ejecuta cuando la actividad podría ser destruida y necesitada después como si realmente no lo hubiera sido. Esto ocurre, por ejemplo, cuando se lanza una nueva actividad sobre la actual. En ese caso, la anterior puede llegar a ser destruida (si el sistema necesita memoria). Pero si el usuario usara el botón de retroceso, la actividad debería volver a mostrarse tal y como estaba. Esta situación también se da si se cambia la orientación de la pantalla.

    Resulta obvio, entonces, el objetivo de este método: guardar el estado de la actividad. O sea, los elementos seleccionados, las cadenas introducidas en los cuadros de texto, etc. Sin embargo, la implementación de este método en la clase Activity (al que tenemos que llamar obligatoriamente desde nuestro método) ya realiza la mayor parte de este trabajo, llamando a los métodos onSaveInstanceState de cada vista de la actividad. La versión de las vistas de este método se encarga de guardar todo lo necesario para que al volver a crearlas se muestren igual que estaban. O sea, la cadena introducida en un cuadro de texto, la opción seleccionada en una lista, etc.

    Entonces, ¿para qué se utiliza realmente este método? Pues para guardar todo lo demás. La ejecución de una actividad a menudo requiere del uso de una serie de atributos que almacenan valores necesarios para controlar su comportamiento: elecciones previas del usuario que condicionan el funcionamiento actual, valores calculados que determinan cómo se muestran ciertos elementos de la actividad. etc. En resumen, datos que evolucionan a medida que el usuario va utilizando la actividad y que no se guardan directamente en las vistas mostradas (aunque puedan determinar su aspecto o funcionamiento).

    Todo eso forma también parte del estado de la actividad y, en caso de perderse, podría provocar que la actividad ya no se comportase de la misma forma cuando el usuario regresara a ella.Para guardar todo esto se utiliza este método, que recibe el mismo objeto Bundle que después obtendrá el método onCreate (y también onRestoreInstanceState) y donde almacenaremos los datos necesarios, junto con los que las vistas guardan por su cuenta.

  • onRestoreInstanceState: Este método es el complementario del anterior y sirve para recuperar los datos almacenados en onSaveInstanceState. También, de forma análoga, la versión del método en la clase Activity ya se encarga de recuperar la información de la vistas automáticamente, por lo que sólo habría que restaurar los datos específicos que hayamos guardado nosotros.En cualquier caso, este método no se suele utilizar habitualmente, dado que el objeto Bundle que recibe es exactamente el mismo que recibe onCreate, por lo que la recuperación se suele hacer ahí.

    No obstante, hay situaciones en las que no conviene recuperar una determinada información hasta que toda la inicialización de la actividad (que se hace en onCreate) se haya llevado a cabo. En esos casos este método resulta de gran ayuda.

Como este post ya ha quedado bastante largo, dejaré para el próximo el resto de elementos que se muestran en el diagrama y de los que aún no he hablado: orden de ejecución de los métodos, estados de la actividad, cuándo puede ser destruida, etc.

Recuerda que en esta página dispones de enlaces a todos los artículos sobre Android que he publicado en La columna 80.

4 comentarios to “Ciclo de vida de las actividades de Android (I)”

  1. Jhonny Villarroel said

    Seria bueno un ejemplo mi estimado

    • Tengo previsto poner ejemplos completos más adelante, cuando haya publicado unos cuantos posts que estoy preparando sobre diseños y widgets.

      Me parece más lógico explicar cómo se construyen las interfaces de usuario antes de meterme con la funcionalidad de las actividades que muestran esas interfaces. Para entender un ejemplo de creación y destrucción de actividades, uso de los diferentes eventos y almacenamiento de información hay que conocer algunas cosas de las que aún no he hablado en este blog, como intents y callbacks. Simplemente intento ir tratando los diferentes temas en el orden que considero más adecuado.

      En cualquier caso, trataré de adelantar los ejemplos en la planificación del blog.

  2. Eduardo said

    Muy buena explicación, gracias por el aporte

  3. Alejandro said

    Excelente explicación, impecable!

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s