studio popbackstackimmediate fragments example backstack addtobackstack android android-fragments memory-leaks out-of-memory

android - popbackstackimmediate - onbackpressed fragment



Fragmentos de Android en Backstack ocupando demasiada memoria (2)

Es difícil ver la imagen completa (incluso aunque nos haya mostrado mucha información), sin acceso concreto a su código fuente, lo cual estoy seguro de que sería poco práctico, si no imposible.

Dicho esto, hay algunas cosas que se deben tener en cuenta al trabajar con Fragmentos. Primero una pieza de descargo de responsabilidad.

Cuando se presentaron los Fragmentos, parecían la mejor idea de todos los tiempos. Ser capaz de mostrar más de una actividad al mismo tiempo, un poco. Ese fue el punto de venta.

Así que todo el mundo comenzó lentamente a usar Fragmentos. Era el chico nuevo en la cuadra. Todo el mundo estaba usando Fragmentos. Si no estaba usando Fragmentos, lo más probable es que "lo estuviera haciendo mal".

Unos años y aplicaciones más tarde, la tendencia está (afortunadamente) volviendo a más actividad, menos fragmentos. Esto se aplica a las nuevas API (la capacidad de transición entre actividades sin que el usuario se dé cuenta realmente, como se ve en las API de transición y otras).

Entonces, en resumen: odio los fragmentos . Creo que es una de las peores implementaciones de Android de todos los tiempos, que solo ganó popularidad debido a la falta de Transition Framework (como existe en la actualidad) entre las actividades. El ciclo de vida de un Fragmento es, en todo caso, una bola de devoluciones de llamada aleatorias que nunca se garantiza que se llame cuando las esperas.

(Ok, estoy exagerando un poco, pero pregúntele a cualquier desarrollador de Android si tuvo problemas con Fragments en algún momento y la respuesta será un rotundo sí).

Con todo lo que se dice, los fragmentos funcionan. Y así tu solución debería funcionar.

Así que comencemos a ver quién / dónde puede mantener estas referencias difíciles.

Nota : solo voy a compartir ideas sobre cómo corregir esto, pero es probable que no proporcione una solución directa. Úsalo como referencia.

¿QUE ESTA PASANDO? : Estás agregando fragmentos al Backstack. El backstack almacena una referencia al fragmento, no débil ni blanda. ( source )

Ahora que almacena un backstack? FragmentManager y ... como ha adivinado, también utiliza una referencia en vivo ( source ).

Y, finalmente, cada actividad contiene una referencia al FragmentManager.

En resumen: hasta que su actividad muera, todas las referencias a sus fragmentos existirán en la memoria. Independientemente de las operaciones de agregar / eliminar que ocurrieron en el nivel / backstack de Fragment Manager.

¿QUÉ PUEDES HACER? Un par de cosas vienen a mi mente.

  1. Intente usar un cargador de imágenes / biblioteca de caché simple como Picasso , si es que hay algo para asegurarse de que las imágenes no se están filtrando. Más tarde, puede eliminarlo si desea utilizar su propia implementación. A pesar de todos sus defectos, Picasso es realmente fácil de usar y ha llegado a un estado en el que trata con la memoria "de la manera correcta".

  2. Después de que hayas eliminado de la imagen el problema de "Puede que esté perdiendo mapas de bits" (¡no es un juego de palabras!), Es hora de revisar el ciclo de vida de tu Fragmento. Cuando pones un fragmento en el backstack, no se destruye, pero ... tienes la oportunidad de borrar los recursos: Fragment#onDestroyView() se llama. Y aquí es donde desea asegurarse de que el fragmento anule los recursos.

No menciona si sus fragmentos están usando setRetainInstance(true) , tenga cuidado con eso, ya que estos no se destruyen / recrean cuando la Actividad se destruye / recrea (por ejemplo: rotación) y todas las vistas pueden filtrarse si no se manejan adecuadamente .

  1. Finalmente, pero esto es más difícil de diagnosticar, tal vez le gustaría revisar su arquitectura. Está lanzando el mismo fragmento (perfil de vista) varias veces, puede que desee considerar reutilizar la misma instancia y cargar el "nuevo usuario" en él. El backstack se puede manejar mediante el seguimiento de una lista de usuarios en el orden en que se cargan, por lo que podría interceptar en BackPressed y mover "abajo" la pila, pero siempre cargando los datos nuevos / antiguos a medida que el usuario navega. Lo mismo ocurre con su StoryViewFragment .

En resumen, todas estas sugerencias provienen de mi experiencia, pero es realmente difícil ayudarlo a menos que podamos ver más en detalle.

Esperemos que sea un punto de partida.

La mejor de las suertes.

PROBLEMA:

Tengo una aplicación para Android que le permite a un usuario navegar por el perfil de un usuario ViewProfileFragment . Dentro de ViewProfileFragment un usuario puede hacer clic en una imagen que lo llevará a StoryViewFragment donde se muestran las fotos de varios usuarios. Es posible hacer clic en una foto de perfil de usuario que los llevará a otra instancia de ViewProfileFragment con el perfil del nuevo usuario. Si un usuario hace clic repetidamente en los perfiles del usuario, hace clic en una imagen que los lleva a la galería y luego hace clic en otro perfil que los Fragmentos se apilan en la memoria rápidamente, lo que causa el temido OutOfMemoryError . Aquí hay un diagrama de flujo de lo que estoy describiendo:

El usuario A hace clic en el perfil de Bob. Dentro del perfil de Bob, UserA hace clic en ImageA y lo lleva a una galería de fotos de varios usuarios (incluido el de Bob). UserA hace clic en el perfil de Sue y luego en una de sus imágenes: proceso de repetición, etc., etc.

UserA -> ViewProfileFragment StoryViewFragment -> ViewProfileFragment StoryViewFragment -> ViewProfileFragment

Entonces, como puede ver en un flujo típico, hay muchas instancias de ViewProfileFragment y StoryViewFragment acumulándose en el backstack.

CÓDIGO PERTINENTE

Los estoy cargando como fragmentos con la siguiente lógica:

//from MainActivity fm = getSupportFragmentManager(); ft = fm.beginTransaction(); ft.replace(R.id.activity_main_content_fragment, fragment, title); ft.addToBackStack(title);

Lo que he probado

1) Estoy utilizando específicamente la replace FragmentTransaction para que el método onPause se onPause cuando tenga lugar la replace . Inside onPause liberar tantos recursos como puedo (como borrar datos en adaptadores ListView , "anular" las variables, etc.) para que cuando el fragmento no sea el fragmento activo y se coloque en el backstack haya más memoria liberada. Pero mis esfuerzos por liberar recursos son solo un éxito parcial. De acuerdo con MAT, todavía tengo una gran cantidad de memoria consumida por GalleryFragment y ViewProfileFragment .

2) También eliminé la llamada a addToBackStack () pero obviamente eso ofrece una mala experiencia de usuario porque no pueden retroceder (la aplicación simplemente se cierra cuando el usuario pulsa el botón de retroceso).

3) He usado MAT para encontrar todos los objetos que ocupo mucho espacio y los he tratado de varias maneras dentro de los onPause (y onResume ) para liberar recursos, pero aún son de un tamaño considerable.

4) También escribí un bucle for en el onPause ambos fragmentos que establece que todas mis ImageViews nulas usando la siguiente lógica:

for (int i=shell.getHeaderViewCount(); i<shell.getCount(); i++) { View h = shell.getChildAt(i); ImageView v = (ImageView) h.findViewById(R.id.galleryImage); if (v != null) { v.setImageBitmap(null); } } myListViewAdapter.clear()

Preguntas

1) ¿Estoy pasando por alto una forma de permitir que un Fragmento permanezca en el backstack, pero también libere sus recursos para que el ciclo de .replace (fragmento) no se corra toda mi memoria?

2) ¿Cuáles son las "mejores prácticas" cuando se espera que muchos fragmentos se puedan cargar en el backstack? ¿Cómo un desarrollador trata correctamente este escenario? (¿O es que la lógica en mi aplicación es inherentemente defectuosa y simplemente lo estoy haciendo mal?)

Cualquier ayuda en una lluvia de ideas para una solución a esto sería muy apreciada.


Resulta que los fragmentos comparten el mismo ciclo de vida que su actividad principal. Según la documentación del Fragmento :

Un fragmento siempre debe estar incrustado en una actividad y el ciclo de vida del fragmento se ve directamente afectado por el ciclo de vida de la actividad del host. Por ejemplo, cuando la actividad está en pausa, también lo son todos los fragmentos, y cuando la actividad se destruye, también lo son todos los fragmentos. Sin embargo, mientras se está ejecutando una actividad (se encuentra en el estado del ciclo de vida reanudado), puede manipular cada fragmento de forma independiente.

Por lo tanto, el paso que tomó para limpiar algunos recursos en onPause() del fragmento no se activará a menos que la actividad principal se detenga. Si tiene varios fragmentos que están siendo cargados por una actividad principal, lo más probable es que esté usando algún tipo de mecanismo para cambiar cuál está activo.

Es posible que pueda resolver su problema al no confiar en onPause sino al reemplazar setUserVisibleHint en el fragmento. Esto le proporciona un buen lugar para determinar dónde realizar la configuración de los recursos o la limpieza de los recursos cuando el fragmento aparece y no se ve (por ejemplo, cuando tiene un PagerAdapter que cambia de FragmentA a FragmentB).

public class MyFragment extends Fragment { @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (isVisibleToUser) { //you are visible to user now - so set whatever you need initResources(); } else { //you are no longer visible to the user so cleanup whatever you need cleanupResources(); } } }

Como ya se mencionó, está acumulando artículos en un backstack, por lo que se espera que haya al menos un poco de espacio en memoria, pero se puede minimizar el espacio eliminando recursos cuando el fragmento está fuera de la vista con la técnica anterior.

La otra sugerencia es ser realmente buenos para entender la salida de la herramienta analizadora de memoria ( MAT ) y el análisis de memoria en general. Aquí es un buen punto de partida . Es realmente fácil perder memoria en Android, por lo que es una necesidad, en mi opinión, familiarizarse con el concepto y cómo la memoria puede alejarse de usted. Es posible que sus problemas se deban a que no libera recursos cuando el fragmento se pierde de vista , así como a una pérdida de memoria de algún tipo, por lo que si utiliza el setUserVisibleHint para activar la limpieza de sus recursos y aún ve un alto el volumen de memoria que se está utilizando, entonces una pérdida de memoria podría ser el culpable, así que asegúrese de descartarlos.