La columna 80

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

Ciclo de vida de las actividades de Android (II)

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

En el post anterior vimos con detalle las diferentes etapas por las que puede pasar una actividad en Android y que dan lugar a lo que se conoce como ciclo de vida, por medio de los métodos que se ejecutan ante ciertos eventos que se producen durante dicha vida. Para representarlo visualmente utilicé el siguiente diagrama:

Ciclo de vida de las actividades

En el diagrama se muestran, no obstante, otros elementos: flechas que indican los diferentes caminos que puede tomar la ejecución de la actividad (con varias alternativas en algunos casos), cuadros que agrupan a varios de los métodos, y otros elementos que quizás hayan pasado más inadvertidos, como los distintos bordes de las cajas que representan los métodos.

Todos esos elementos gráficos tienen un significado, que este post tratará de aclarar.

Las vidas de una actividad

Los recuadros más grandes que aparecen en el diagrama representan los diferentes tipos de vida que puede tener una actividad, cada uno de ellos asociado a uno de los estados en los que se puede encontrar la propia actividad. Aunque pueda resultar algo confuso, en realidad esto no significa que la actividad se encuentre en ese estado durante toda la parte del ciclo representada o sólo en esa parte, sino que los eventos que se incluyen están relacionados con dicho estado.

Por ejemplo, que el recuadro Actividad viva sólo englobe a los métodos desde el onCreate al onDestroy no implica que la actividad sólo esté viva durante esos momentos. Lo cierto es que la actividad ya está viva durante la ejecución de su constructor y hasta que se llama al destructor o el proceso que la alberga es eliminado de memoria. Sin embargo, se señala que la vida activa comienza en el método onCreate, porque es ahí donde se construye la estructura de vistas y diseños que mostrará la actividad. De forma análoga, se considera que finaliza con el método onDestroy, porque una vez que se llama a ese evento ya no hay vuelta atrás, la actividad va a ser destruida y tendrá que crearse un nuevo objeto si se quiere volver a utilizar.

Algo similar ocurre con la vida visible de la actividad. Un error que se repite con frecuencia en libros y webs de Android es dar a entender que el método onStart es el primero que se ejecuta cuando la actividad se vuelve visible o, dependiendo del caso, que es el que se llama justo antes de que la actividad se vuelva visible, lo que ocurrirá en cuanto finalice dicha llamada. No es verdad.

Lo cierto es que la actividad no es completamente visible, con su barra de título y su contenido, hasta que finaliza la ejecución de onResume. Sin embargo, si la nueva actividad va a ocupar toda la pantalla, ocultando por completo a la anterior, ésta (la anterior) no estará tampoco visible hasta que se muestre la nueva. Lo que realmente ocurre es que la pantalla se pone en negro desde el momento en que comienza la ejecución del constructor de la actividad. En ese punto, si la actividad es la principal de la aplicación puede que se muestre su barra de título, pero su contenido permanecerá en negro hasta que finalice onResume. Por otro lado, en cuanto se inicia la ejecución de onPause, la pantalla vuelve a ponerse en negro (a menos que estemos retornando de la actividad principal de la aplicación, en cuyo caso se muestra la actividad de la aplicación anterior).

Si la actividad que se va a mostrar no ocupa toda la pantalla, la actividad anterior será visible hasta que finalice onResume y desde que se inicie la ejecución de onPause. Esto es así porque cuando una actividad es completamente ocultada por otra, el sistema tratará de detenerla, siguiendo su ciclo hasta el onStop; mientras que si parte de la actividad va a seguir siendo visible, el sistema sólo la pausará, llegando hasta el onPause. La única diferencia entre una actividad pausada y una detenida es que la detenida tiene muchas más probabilidades de ser eliminada por el sistema, dado que ya no es visible para el usuario.

Por lo tanto, una nueva actividad sólo es completamente visible entre la finalización de la ejecución de onResume y el comienzo de la ejecución de onPause. ¿Por qué se incluyen, entonces, onStart y onStop como parte de la vida visible de la actividad? Pues porque si se llama a onStart es porque se tiene intención de hacer visible la actividad (aunque luego no sea realmente así). De forma similar, se llama a onStop cuando la actividad ya no va a ser visible de nuevo salvo reiniciándola (dando lugar a un nuevo ciclo de visibilidad).

Por último, a estas alturas ya habrás adivinado que, a pesar de cómo se muestra en el diagrama, la actividad sólo está realmente activa, esto es, atendiendo a interacciones del usuario, desde que finaliza onResume hasta que comienza onPause, justo el mismo tiempo que la actividad es visible.

Rutas

En el diagrama se puede observar como desde el constructor hasta el destructor (método finalize) existe un camino lineal que pasa por todos los métodos principales, ejecutándolos todos, uno detrás de otro. Ese es el ciclo de vida completo y normal (más habitual) de una actividad: la actividad se crea, se inicializa y se activa para interactuar con el usuario; más tarde, cuando va a ser reemplazada por otra, se detiene, se liberan recursos y finalmente, se destruye.

Las formas y momentos en los que el ciclo se reinicia, en su totalidad o en parte, también están detallados en el diagrama: reactivación de una actividad pausada, reinicio de una actividad detenida (pasando por onRestart) y nueva creación cuando ya ha sido destruida.

Tenemos también una línea que permite pasar de onStart a onStop, sin llegar a pasar por onResume. Representa una situación que ya hemos comentado varias veces: que una actividad que entra en onStart, porque hay intención de visualizarla, no llega realmente a mostrarse. Esta situación no se da con frecuencia, pero puede ocurrir que una actividad realice su ciclo desde el comiendo hasta onStart y no ejecute onResume porque no puede ceder el control al usuario en ese momento, normalmente porque hay otra actividad interactuando con el usuario pero que no la oculta por completo. En esta situación, si la actividad que está en onStart deja de ser visible se ejecutará directamente onStop.

Existen algunos otros atajos similares que no he querido pintar en el diagrama por no complicarlo más y porque se dan aún con menos frecuencia. Por ejemplo, se puede pasar directamente de onCreate a onDestroy si se llama al método finish en onCreate. El método finish permite terminar la ejecución de la actividad en cualquier momento (por ejemplo, al pulsar un botón Salir). Llamar a finish en onCreate no es muy común, pero podría tener sentido si la actividad tuviera algunos requisitos imprescindibles para su ejecución y alguno de ellos no se cumpliera. Aún así, hay soluciones más elegantes para resolver esa situación que eliminar una actividad que no se ha llegado ni siquiera a visualizar.

Más rutas

Otros caminos alternativos que aparecen en el diagrama, y que pueden resultar algo más confusos, son los que enlazan los métodos onRestoreInstanceState y onSaveInstanceState. Esos métodos aparecen separados de los demás no sólo porque a menudo no se implementan, sino porque muchas veces ni siquiera se llegan a ejecutar. Este es el motivo de que aparezcan esas rutas alternativas.

El método onSaveInstanceState sólo se ejecutará si hay posibilidades de que la actividad vaya a reutilizarse. Por ejemplo, si la actividad A lanza la actividad B sobre ella, se ejecutará onSaveInstanceState en A. Por otro lado, si el sistema está seguro de que no va a restaurar de nuevo una actividad, no llamará a ese método. Eso ocurre, por ejemplo, si se regresa de la actividad B a la A. En ese caso no se llama a onSaveInstanceState en B. Esto significa que si se volviera a mostrar B, tendría de nuevo el estado inicial.

Por su parte, onRestoreInstanceState sólo se ejecuta si se ha ejecutado antes onSaveInstanceState y si la actividad se está reconstruyendo (ha pasado por onCreate). El motivo por el que onRestoreInstanceState no se suele implementar es que siempre que se ejecuta se ejecuta también onCreate, y ambos reciben el mismo objeto Bundle con el estado a restaurar.

En el caso de onSaveInstanceState, las rutas que aparecen son algo más liosas y están representadas en diferentes colores. Lo que pretende mostrar el diagrama (aunque no sé si lo he conseguido) es que el método onSaveInstanceState puede ejecutarse (si lo hace) antes o después de onPause, pero onPause siempre se ejecuta. O sea, podemos pasar de onResume a onPause y de ahí a onStop sin ejecutar onSaveInstanceState (ruta normal, en negro). Y, si se ejecuta, las opciones son: onResume – onPause – onSaveInstanceState – onStop (ruta en rojo), o bien onResume – onSaveInstanceState – onPause – onStop (ruta en verde).

Destrucción de la actividad

Ya hemos comentado varias veces que la actividad puede ser destruida de varias formas y en distintos momentos. En su ciclo normal la actividad se destruye cuando ya no es visible y no va a ser necesaria más, pasando de onStop a onDestroy y ejecutando el destructor en un momento posterior. Por otro lado, el programador puede destruir la actividad en cualquier momento llamando al método finish y el sistema también puede acabar con ella si necesita memoria eliminando el proceso en el que se ejecuta.

No obstante, el sistema siempre trata de ser lo más eficiente posible, por lo que intentará no eliminar actividades que puedan volver a ser necesarias salvo que no quede más remedio. Por lo tanto, si puede siempre optará por seguir el ciclo normal y pasar por onDestroy. Pero si las necesidades de memoria son acuciantes, puede eliminar la actividad aunque esté en otros estados, sin que el resto de métodos llegue a ejecutarse.

En el diagrama he representado los métodos que podrían no llegar a ejecutarse porque la actividad haya sido eliminada de memoria dibujándolos sin borde negro: onDestroy y finalize. El método onStop aparece con una línea punteada para indicar que su ejecución es segura o no dependiendo de la versión de Android que tengamos.

Antes de la versión 3.0 de Android (Honeycomb), onPause era el último método seguro. Tras terminar su ejecución la actividad podía ser destruida en cualquier momento. Por este motivo, onPause era el lugar donde tenía que almacenarse la información permanente, la que tiene que permanecer entre ejecuciones de la aplicación. Además, por este mismo motivo, antes de la versión 3.0, el método onSaveInstanceState no se podía ejecutar después de onPause. Siempre se ejecutaba antes.

Desde Honeycomb, el método onStop siempre se ejecuta, por lo que es un punto seguro para almacenar los cambios permanentes y onSaveInstanceState se puede ejecutar antes o después de onPause. El diagrama trata de representar ambas posibilidades.

En cualquier caso, salvo que la aplicación esté diseñada de forma exclusiva para la versión 3.0 o posterior, deberíamos guardar los cambios en onPause y asumir que onStop podría no ejecutarse.

Finalmente, simplemente comentar que en casos de extrema necesidad, el sistema podría destruir la actividad estando en cualquiera de sus estados, incluso aunque en ese momento fuera visible. Esto sólo se produce si se requiere la memoria para algo muy prioritario o si el sistema considera que la aplicación no está funcionando correctamente o ha dejado de responder. No podemos hacer nada para protegernos contra esto, así que tendremos que asumir que en esos casos se podría llegar a perder información. Afortunadamente, esas situaciones no se dan con frecuencia y, por lo general, sólo se producen en dispositivos con muy poca memoria y muchas aplicaciones en ejecución.

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

5 comentarios to “Ciclo de vida de las actividades de Android (II)”

  1. erly cj said

    buena respuesta a gracias

  2. kursh said

    Buenas tardes,
    despues de llevar ya unas semanas peleandome con android y aprendiendo poco a poco.. estoy haciendo una app que descarga información de una base de datos.. la misma tiene varias activitys diferentes que muestran diferente info… cuando paso de una a otra se destruyen las activitys y al volver se vuelven a crear por lo que vuelve a descargar toda la inforamción de mi base de datos en un servidor…

    La historia es… no hay una instruccion que guarde el estado de una activity (tengo listas customizadas, imagenes, botones, textviews.. etc..) o tengo que volverme loco a guardar cada una de las miles de variables que tengo????????? me acabo de traumatizar dandome cuenta de lo mal que pinta la cosa… gracias..

    • Que las actividades se destruyan y se vuelvan a crear es el comportamiento normal en Android, así que no queda otro remedio que vivir con ello (o, al menos, asumirlo es bastante más recomendable que tratar de impedirlo).

      Para guardar el estado de la actividad tienes tres opciones: el objeto Bundle, la clase SharedPreferences o usar almacenamiento externo (archivos XML, bases de datos SQLite, etc).

      La primera opción es la que se comenta en este post: sobrescribir onSaveInstanceState y guardar ahí los datos y recuperarlos en onRestoreInstanceState o en onCreate. El evento onRestoreInstanceState sólo será lanzado si hay datos que recuperar. onCreate recibirá un objeto Bundle nulo si la actividad se está creando por primera vez (lo podrías usar para no recuperar la información de la base de datos).

      Los valores de las vistas se guardan solos (siempre y cuando te acuerdes de llamar a la versión predeterminada de onSaveInstanceState dentro de tu implementación). Para las vistas personalizadas tienes que implementar sus propios métodos onSaveInstanceState y onRestoreInstanceState y crear una clase hija de Parcelable. Aquí puedes ver un ejemplo: http://stackoverflow.com/questions/3542333/how-to-prevent-custom-views-from-losing-state-across-screen-orientation-changes.

      El problema que tiene esta opción es que no siempre se llama a onSaveInstanceState, como ya comento en el post. Si necesitas mantener los datos también en esas situaciones (por ejemplo, actividades que se abren y se cierran más de una vez), tendrás que usar otros métodos.

      La clase SharedPreferences permite guardar datos en la carpeta de la aplicación, en la memoria interna del dispositivo. Los datos son persistentes, es decir, se mantienen entre ejecuciones de la aplicación. Por eso motivo, esta clase se suele utilizar para guardar las preferencias del usuario. No obstante, se puede usar también para guardar el estado de una actividad, aunque tendrás que tener en cuenta que los tendrás también cuando la aplicación se ejecute de nuevo (aunque siempre tienes la opción de borrarlos cuando se pulse el botón Salir, por ejemplo).

      Para ello tienes que obtener el objeto SharedPreferences mediante el método getPreferences de la actividad. Luego tienes que obtener el editor del objeto SharedPreferences (SharedPreferences.Editor) usando el método edit. El editor tiene métodos put similares a los del objeto Bundle. Los datos se pueden recuperar directamente del objeto SharedPreferences, usando los diversos métodos get.

      Tienes que tener en cuenta que con este método no se guardan automáticamente los datos de las vistas, por lo que tendrás que guardarlos “manualmente”. El lugar indicado para hacer esto es onPause. Y para recuperar los datos, onCreate. Ten en cuenta que el objeto SharedPreferences es único para toda la aplicación, por lo que tendrás que asegurarte de que las claves que utilices para los datos almacenados no se repitan de una actividad a otra.

      Tienes un ejemplo aquí: http://eigo.co.uk/labs/managing-state-in-an-android-activity/.

      Respecto a tu última pregunta, no parece que exista nada que permita guardar todo el estado en una única operación.
      Puedes intentar obtener todas las vistas accediendo a la vista principal de la actividad y usando getChildCount y getChildAt (de la clase ViewGroup) de forma recursiva hasta recorrerlas todas, pero seguirás tendiendo el problema de saber qué datos obtener de cada una de ellas.
      Y todavía tendrás que guardar los datos de la actividad que no estén en las vistas.
      Así que me temo que te tocará armarte de paciencia.

  3. Jorge said

    Muchas gracias, me ayudó mucho a comprender el tema
    Su explicación es muy clara y también detallada y concisa al mismo tiempo
    Nuevamente gracias por tomarse el tiempo de compartir su conocimiento.
    Saludos

  4. JB said

    La verdad que muy clara la explicación, tenía ya en mente de antes algunos detalles que quería repasar, y en el momento de buscarlos en internet la gran mayoría de los post que he leído los omiten, pues aquí no🙂

    Saludos

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