Android: ciclo de vida y persistencia

El desarrollo de aplicaciones para Android tiene sus ventajas respecto al PC, como la geolocalización y la integración con la cámara; también tiene inconvenientes, entre ellos el reducido tamaño de pantalla o la poca memoria disponible, y algunos no tan obvios.

Entre los mayores problemas que nos puede dar programar para Android destaca la tremenda diferencia de sus ciclos de vida. En el PC podemos dejar una aplicación abierta y, si no le da por colgarse o se va la luz, no pasa nada. Pero la vida de una app es mucho, mucho más dura…  gira la pantalla y tiembla.. minimiza y muere.

(imagen “donada involuntariamente” por Ewoko.com)
Es fundamental que comprendamos el ciclo de vida de una app para programar con seguridad; veámoslo paso a paso:

1- Android minimiza pero no cierra. En el PC lo habitual es guardar y salir, y en ocasiones existe la opción de autoguardado. En Android usamos el botón Home para minimizar, y aunque podríamos agregar alguna opción de cierre está mal visto, ya que interferiríamos con la forma en la que el sistema operativo gestiona sus recursos. Además, cualquier aplicación emergente puede minimizar la aplicación activa.

Conclusión: nuestra aplicación puede minimizarse en cualquier momento, y también es posible que entre en pausa por el ahorro de energía y acabe deteniéndose. Se dice que la aplicación está detenida (Stopped).

2- Si no hay bastante memoria Android empieza a cerrar (Destroy) aplicaciones detenidas. Es decir, los datos de una aplicación minimizada corren peligro si no los guardamos, y el proceso debe ser automático. Afortunadamente disponemos del método onStop(), que se activa cada vez que una aplicación se detiene, y onDestroy(), que “salta” cuando se destruye.

Solución: utilizamos onStop() y/o onDestroy() para guardar nuestra información en alguno de los sistemas de almacenamiento permanente que nos ofrece Android (shared preferences, SQLite, etc.), y comprobamos estos datos cada vez que se inicia nuestra aplicación, devolviéndola al estado original si es necesario.

Guía SQLite en  El baul de Android
Guia shared preferences en El android Libre

3- No sólo las aplicaciones minimizadas corren peligro, sino el estado de la “pantalla” (actividad). Normalmente las actividades se destruyen al pulsar físicamente el botón back o cuando invocamos el método finish(); sin embargo el sistema también puede destruirlas si la aplicación ha estado minimizada durante un cierto tiempo (normalmente entre 30 minutos y una hora); aunque la aplicación permanece y recuerda la actividad en la que se encontraba, se pierden todos los datos asociados a ella, como cálculos, posiciones o textos que hayamos escrito en pantalla. Por cierto, cuando giramos la pantalla Android destruye y recrea la actividad actual; tiene cierta lógica ya que la visualización puede ser distinta en horizontal y vertical, pero los datos se pierden.

Solución: antes de destruir una actividad Android invoca el método onSaveInstanceState(), y llama a onRestoreInstanceState() cuando la reconstruye. Como la aplicación persiste, normalmente nos limitaremos a guardar los valores en forma de pares texto-dato en el mismo bundle que usamos para transferir información de una actividad a otra mediante getExtras().

Guía rotación de pantalla en Androideity

El gran problema de usar un bundle es que a veces una actividad maneja grandes cantidades de datos u objetos complejos, que son difíciles de guardar a golpe de pares texto-dato, y recurrir al almacenamiento permanente cada vez que giramos la pantalla no es la mejor de las ideas ya que ralentiza la aplicación.

Una de las mejores soluciones es usar Parcelable, una versión optimizada de Serializable que nos ayuda a guardar objetos en un bundle.

Guía sobre el uso de Parcelable en Androcode

Otra solución que puede parecer obvia para aquellos que estén acostumbrados a compartir datos entre clases (típico en Java y C++), es crear un único objeto que los contenga (singleton) y llamarlo cuando sea necesario. El problema de los singletons en Android es que, o tenemos un cuidado extremo al crearlos y usarlos (por ejemplo siguiendo el tutorial que publicaré en mi próximo post :-D), o acabaremos provocando fugas de memoria y excepciones no controladas.

En resumen:

  • Normalmente pasaremos los datos de una actividad a otra mediante un bundle y getExtras()
  • Usaremos el mismo bundle para recuperar la actividad si es destruida, mediante onSaveInstanceState(), onRestoreInstanceState(), y quizás con la ayuda de Parcelable.
  •  Si la aplicación es detenida/destruida podemos usar onStop()/onDestroy() para guardar información en el almacenamiento permanente, que posteriormente recuperaremos al recrear la aplicación.