Ciclo de vida de aplicación Android

Nihon

Hola compañeros y gracias de antemano:

Hace tiempo que llevo desarrollando una aplicación móvil en java para Android para una empresa. El caso es que la aplicación tiene que pasar de segundo plano al frente de vez en cuando como todas las aplicaciones del mundo, como cuando te llaman o miras el correo, haces una foto, etc y luego vuelves a la aplicación en la que estabas.

Al ser esta la primera vez que desarrollo una aplicación como esta no he tenido en cuenta el ciclo de vida de la aplicación y estoy intentando corregirlo ya que a mucha gente le sucede que al traer de vuelta la aplicación después de hablar por teléfono, falla y se le cierra de golpe con la consecuente pérdida de datos.

En los últimos días he estado viendo muchos ejemplos sobre como funciona el ciclo de vida: onPause(), onResume(), onRestart() y demás, pero mi problema es que no se como manejar los datos de mi aplicación en esos casos. Voy a explicar un poco como manejo los datos de mi aplicación mientras la aplicación está activa:

En la pantalla principal leo un archivo XML con una lista de tareas y me creo un ArrayList de mi clase ParteTrabajo (clase con métodos y variables que necesito) con todas las que haya en el fichero XML, el usuario elige una y con esa se mueve por la aplicación. Para controlar todos los cambios que se hacen en las diferentes actividades lo que hago es crear una instancia de mi lista y sacar de ahí la tarea seleccionada por el usuario, utilizo mi clase para almacenar los datos y la vuelvo a meter en la lista si se han hecho cambios.

Pongo un ejemplo de actividad por si queda más claro:

spoiler

El caso es que en los ejemplos que he visto sólo muestran el ciclo de vida y no cómo tratar los datos de la aplicación, al manejar una instancia de mi lista no se si tengo que utilizar el bundle, que datos guardar o si tengo que recuperar también la asignación de valores de la interfaz.

Ayer conseguí que me saliera un nullpointerexception al traer de vuelta la aplicación desde segundo plano después de 20 minutos con el móvil inactivo y lo hizo en Instanciar(), fue en otra actividad más amplia en la que probé onResume if(parte==null) Instanciar(); pensando que si volvía y la actividad había borrado los datos, que los recuperara en Instanciar() como si se creara de nuevo, pero nada.

Agradezco muchísimo cualquier ayuda posible al respecto sobre pasar tareas de segundo plano al frente, y lamento no poder poner un log ya que es algo que pasa a los usuarios que están por ahí y no puedo conectar los móviles para ver sus trazas. Seguiré probando a ver si consigo que vuelva a fallar, gracias de nuevo.

Edit: Mientras escribía esto he tenido la aplicación en segundo plano y pasando de aplicaciones y al volver me ha fallado. Aquí está el log:

07-03 11:01:09.639: E/InputDispatcher(299): channel '41835cf8 com.example.tankatinformes/com.example.tankatinformes.ParteTrabajoFotos (server)' ~ Channel is unrecoverably broken and will be disposed!
07-03 11:01:10.879: E/AndroidRuntime(22496): FATAL EXCEPTION: main
07-03 11:01:10.879: E/AndroidRuntime(22496): java.lang.RuntimeException: Unable to resume activity {com.example.tankatinformes/com.example.tankatinformes.ParteTrabajoFotos}: java.lang.NullPointerException
07-03 11:01:10.879: E/AndroidRuntime(22496): 	at com.example.tankatinformes.ParteTrabajoFotos.onResume(ParteTrabajoFotos.java:60)
07-03 11:01:10.879: E/AndroidRuntime(22496): 	at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1154)
LLoid

#1 Vaya, yo también estoy peleando con Android y siento no haber visto antes este hilo, espero que no sea demasiado tarde.

Supongo que te conoces el ciclo de vida completo de las actividades (http://developer.android.com/guide/components/activities.html ). No termino de entender lo que quieres decir con lo de tratar los datos de la aplicación, entiendo que te refieres a guardar la información de la actividad cuando pasa a segundo plano (el onPause() o el onStop()) y a recuperarla después cuando se recrea la actividad o vuelve a primer plano.

Posiblemente haya entendido mal tu problema, pero si lo que quieres es manejar la persistencia entre distintas ejecuciones de la aplicación o al recrear/recuperar la actividad, lo normal es usar sharedPreferences o SQLite.

Supongamos que vas a tirar por sharedPreferences porque entiendo que no has usado una bbdd porque no te interesa.

SharedPreferences tiene un funcionamiento muy sencillo, para almacenar los datos en los que quieras tener persistencia, sobreescribes el onStop() y le metes una instancia a SharedPreferences y a continuación vas guardando en esa instancia todos los datos que quieras almacenados por campos:

protected void onStop(){
		super.onStop(); //Fundamental
		
	SharedPreferences preferencias = getSharedPreferences("nombre_random_que quieras",Context.MODE_PRIVATE); //Instancia del sharedPrefs, toda la info se guarda en el fichero del primer parámetro

	SharedPreferences.Editor editorPreferencias = preferencias.edit(); //Editor de sharedPrefs, si no no podrías escribir, solo leer
	
	editorPreferencias.putInt("nombre_campo", dato); //El primer parámetro sirve para referenciar el dato posteriormente, el segundo es el dato en sí, puedes usar putInt para int, putString para string, putDouble y así, creo que también existen para estructuras como arrayLists, arrays y otros más genéricos

editorPreferencias.commit(); //confirmación
		

Este código se ejecuta automáticamente cuando la actividad entra en el onStop(), esto es, cuando es destruida o pasada a segundo plano, es decir, siempre que dejes de verla en pantalla.

Para recuperar la información guardada al recuperar la actividad te vas al onCreate() y vas cargando:

protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.nombre_actividad);
		
	SharedPreferences preferencias = getSharedPreferences("nombre_del_fichero",Context.MODE_PRIVATE);	//instancia del sharedPrefs, el nombre del fichero tiene que ser obviamente le mismo en el que guardaste en el onStop()
		
		int ejemplo = preferencias.getInt("campo_dato", 0); //metes en ejemplo el valor de campo_dato, tienes que usar el mismo nombre que usaste en el onStop() para este campo. El segundo parámetro es por si no se encuentra y lo aplica por defecto

En fin, no sé si he entendido tu problema correctamente, pero si lo que quieres es persistencia de datos en las actividades esta es la solución más sencilla y común.

1 respuesta
Nihon

#2 Gracias por tu ayuda, es una situación extraña así que igual no me he explicado bien. SharedPreferences lo utilizo para mantener 2 datos más importantes que me interesan durante toda la aplicación, si tuviera que guardar datos como dices en onPause() o en onStop() lo haría así, no hay problema.

El problema es que yo me muevo por la aplicación con una clase personal llamada ListaTareas, una lista de objetos y datos que necesito, y en cada nueva actividad recojo una instancia de esa Lista para obtener siempre los datos más recientes, pero al pasar a segundo plano y volver a primer plano (me parece) se pierde la referencia (se libera memoria, se borra, que se yo XD) a esa lista por lo que el programa intenta acceder a ello, no lo encuentra y peta. Al menos eso me parece que se ve en el log.

Pero claro, sharedpreferences no puede guardar una clase propia como la mía (guarda bool, int, strings...) y ahí está el problema que no se como guardar de verdad una instancia de mi clase lista sin perder la referencia a esta cuando la aplicación pasa a segundo plano durante mucho tiempo y que vuelva y siga teniendo mi objeto activo, ya digo que si pierdo los datos no guardados por el usuario me da igual en este momento.

1 respuesta
LLoid

#3 Vale, ya entiendo lo que quieres decir. Android libera memoria cuando la actividad lleva mucho tiempo en segundo plano o cuando la memoria se llena y la necesita para actividades que están por encima en la pila de actividades.

La verdad es que no tengo ni idea de cómo enfocar tu problema. Por lo que se comenta aquí http://stackoverflow.com/questions/2984594/android-keep-activity-from-dieing no hay manera de mantener en memoria actividades que no estén en primer plano si Android decide borrarlas.

Quizá esto http://stackoverflow.com/questions/3469947/saving-state-of-arraylist-of-custom-objects te sea de ayuda, pero imagino que habrás buscado por pasiva y por activa xD

Siento no ser de más ayuda.

1 1 respuesta
Nihon

#4 Faltaría más, gracias por el interés por lo menos. No uso base de datos SQLite ya que no me merece mucho la pena el fregao, por decirlo así. Lo que uso para guardar todos los datos, es un archivo XML que al iniciar la aplicación convierto en mi lista. Además que cuando el usuario ha recogido todos los datos envía ese archivo con toda la información al servidor y en vez de envíar datos ida y vuelta por conexión SQL lo hace enviando un archivo de apenas 3kb.

Con esto vengo a decir que lo único que se me ocurre es que si ha perdido la referencia a mi objeto (habrá que descubrir también como comprobar eso), me guardo el nº del objeto que estaba activo y vuelvo a leer el archivo XML y generar la lista entera, pero como obviamente eso requiere más trabajo, voy a ver si consigo algo más sencillo.

1 respuesta
freskito24

Tienes varias formas de guardar el estado de la aplicación cuando se va de primer plano.

Lo más sencilllo es saber que android llamará al método onSaveInstnace(Bundle args)
(o un nombre similar de método, no sé si es ese exactamente).

El bundle que llega como parámetro en onCreate es el mismo bundle que has guardado antes. No tiene mucho misterio.

Si estás utilizando singletons o objetos compartidos y android decide liberar la memoria de tu app debes usar un patrón lazy (instanciar si es null en el get al objeto). Esto ocurre con los DBHelpers por ejemplo.

1 respuesta
freskito24

Te he dicho una verdad a medias, las actividades no tienen bundle en el onCreate, es de los fragments. Para activities es en onRestoreInstance.

Czhincksx

Puedes guardar lo que necesites en una base de datos local. Eso es persistente creo recordar. En onResume() vuelves a cargar todos los datos (sin pararte a comprobar si has perdido la referencia o no) machacando los antiguos y listo.

sasher

#5 No es para nada difícil crearte una SQLite así rápido y empezar a utilizarla. Quizá hasta te has quebrado mas la cabeza haciendo lo del XML xD

1 1 respuesta
Nihon

#9 Pero es que yo manejo los datos obligado por un XML, me descargo un XML con los datos que muestro al usuario, guardo ese mismo archivo y lo vuelvo a subir. La implementación en código de un XML es largo, pero muy simple.

Aún así el problema que tengo es el mismo, si voy manejando un singleton y el programa decide liberar memoria, me da igual guardarlo en una BBDD que en un XML. El problema es saber como volver a instanciar los datos (o referencia) de mi lista sin que pete la aplicación.

#6 SaveInstanceState me vale para guardar muchas cosas, pero estuve mirando y no me vale para clases propias, según tenía entendido tenía que crearme un patrón parceable también. Pero como no tengo ni idea, estoy un poco perdido.

1 respuesta
freskito24

#10 Usa Gson y serializa a json todos los objetos que quieras:

Objeto java -> String json -> Bundle guardado -> Bundle recuperado -> String json -> Objeto java

Para evitarte parcelar viene muy bien, el rendimiento es increíble y todo es automático (usa reflection).

Cuidado con el tamaño de los objetos eso sí, el máximo de un String en android es medio mega si luego lo pasas en intents.

Nihon

Al final lo he arreglado a lo bruto, cada vez que recupero la actividad con el "restoreInstanceState" leo mi datos y me genero otra vez los objetos como desde el principio y de momento no se ha colgado ni una sola vez.

Ahora tengo otro problema sobre otro tema para acceder a la tarjeta SD, pero supongo que tendré que abrir otro hilo para ello.

wineMan

-

1 respuesta
elkaoD

#13 es lo que os queda a los que hacéis OOP :P

wineMan

-

24 días después
kraneok

#1 Creo que ya lo has solucionado, pero de todos modos te digo.

El ciclo de vida de una app en Android, como habrás comprobado es OnCreate, onStart, onResume, onPause, onStop, onDestroy.

Cuando la app pasa a segundo plano, debemos en el método onPause realizar el guardado de todas las variables que se estén utilizando en ese momento, si no, Android relanzará la app como en el inicio.

Otro modo es como lo has hecho, pero depende del problema, se le debería aplicar una solución u otra, ya sabes, con el savedInstanceState, este es el mas cómodo pero quizás no es el mejor.

1 respuesta
Tig

#16 confundes ciclo de vida de aplicación con ciclo de vida de actividad, y no tiene nada que ver. En una aplicación no tienes ningún aviso de que va a morir, y de hecho un una activity te garantizan hasta el onPause (o ni eso), pero el onStop y onDestroy puede que ni sean llamados

http://developer.android.com/reference/android/app/Activity.html#onStop()

Note that this method may never be called, in low memory situations where the system does not have enough memory to keep your activity's process running after its onPause() method is called.

http://developer.android.com/reference/android/app/Activity.html#onDestroy()

There are situations where the system will simply kill the activity's hosting process without calling this method (or any others) in it, so it should not be used to do things that are intended to remain around after the process goes away.

1 respuesta
kraneok

#17 Na, se me fué la perola, creía que se refería a activity.