Pasé 4 días completos intentando todo lo posible para descubrir la pérdida de memoria en una aplicación que estoy desarrollando, pero las cosas dejaron de tener sentido hace mucho tiempo.
La aplicación que estoy desarrollando es de naturaleza social, así que piense en Actividades de perfil (P) y enumere Actividades con datos, por ejemplo, insignias (B). Puede saltar de un perfil a una lista de insignias, a otros perfiles, a otras listas, etc.
Así que imagina un flujo como este P1 -> B1 -> P2 -> B2 -> P3 -> B3, etc. Por coherencia, estoy cargando perfiles e insignias del mismo usuario, por lo que cada página P es igual y también lo es cada página B
La esencia general del problema es: después de navegar un poco, dependiendo del tamaño de cada página, obtengo una excepción de memoria insuficiente en lugares aleatorios (mapas de bits, cadenas, etc.), no parece ser coherente.
Después de hacer todo lo imaginable para averiguar por qué me estoy quedando sin memoria, no he encontrado nada. Lo que no entiendo es por qué Android no está matando P1, B1, etc. si se queda sin memoria al cargarse y, en cambio, se bloquea. Esperaría que estas actividades anteriores mueran y resuciten si alguna vez vuelvo a ellas a través de onCreate () y onRestoreInstanceState ().
Y mucho menos esto, incluso si hago P1 -> B1 -> Atrás -> B1 -> Atrás -> B1, todavía tengo un bloqueo. Esto indica algún tipo de pérdida de memoria, sin embargo, incluso después de descargar hprof y usar MAT y JProfiler, no puedo identificarlo.
Deshabilité la carga de imágenes desde la web (y aumenté los datos de prueba cargados para compensarlo y hacer que la prueba sea justa) y me aseguré de que la caché de imágenes usa SoftReferences. Android en realidad intenta liberar las pocas SoftReferences que tiene, pero justo antes de que se quede sin memoria.
Las páginas de insignias obtienen datos de la web, los cargan en una matriz de EntityData desde un BaseAdapter y los envían a ListView (en realidad estoy usando el excelente MergeAdapter de CommonsWare , pero en esta actividad de Insignias, en realidad solo hay 1 adaptador de todos modos, pero yo quería mencionar este hecho de cualquier manera).
Revisé el código y no pude encontrar nada que pudiera filtrarse. Borré y anulé todo lo que pude encontrar e incluso System.gc () a la izquierda y a la derecha, pero la aplicación aún se bloquea.
Todavía no entiendo por qué las actividades inactivas que están en la pila no se cosechan, y realmente me encantaría averiguarlo.
En este punto, estoy buscando sugerencias, consejos, soluciones ... cualquier cosa que pueda ayudar.
Gracias.
fuente
outOfMemory
error. ¡Gracias!Respuestas:
No es así como funcionan las cosas. La única administración de memoria que afecta el ciclo de vida de la actividad es la memoria global en todos los procesos, ya que Android decide que se está quedando sin memoria y, por lo tanto, debe eliminar los procesos en segundo plano para recuperar algo.
Si su aplicación está en primer plano iniciando más y más actividades, nunca pasará a segundo plano, por lo que siempre alcanzará su límite de memoria de proceso local antes de que el sistema se acerque a matar su proceso. (Y cuando mata su proceso, matará el proceso que aloja todas las actividades, incluyendo lo que esté actualmente en primer plano).
Entonces, me parece que su problema básico es: está dejando que se ejecuten demasiadas actividades al mismo tiempo y / o cada una de esas actividades se aferra a demasiados recursos.
Solo necesita rediseñar su navegación para no depender de acumular una cantidad arbitraria de actividades potencialmente pesadas. A menos que haga una gran cantidad de cosas en onStop () (como llamar a setContentView () para borrar la jerarquía de vista de la actividad y borrar las variables de cualquier otra cosa a la que pueda estar aferrándose), simplemente se quedará sin memoria.
Es posible que desee considerar el uso de las nuevas API de Fragment para reemplazar esta pila arbitraria de actividades con una sola actividad que administra su memoria de manera más estricta. Por ejemplo, si usa las instalaciones de la pila posterior de fragmentos, cuando un fragmento va a la pila trasera y ya no es visible, se llama a su método onDestroyView () para eliminar por completo su jerarquía de vistas, reduciendo en gran medida su huella.
Ahora, en la medida en que choques en el flujo donde presionas hacia atrás, vas a una actividad, presionas hacia atrás, vas a otra actividad, etc. y nunca tienes una pila profunda, entonces sí, solo tienes una fuga. Esta publicación de blog describe cómo depurar fugas: http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html
fuente
Activity
JavaDocs.Algunos consejos:
Asegúrese de no filtrar el contexto de la actividad.
Asegúrese de no guardar referencias en mapas de bits. Limpie todos sus ImageView en Activity # onStop, algo como esto:
Drawable d = imageView.getDrawable(); if (d != null) d.setCallback(null); imageView.setImageDrawable(null); imageView.setBackgroundDrawable(null);
Recicle los mapas de bits si ya no los necesita.
Si usa memoria caché, como memory-lru, asegúrese de que no esté usando demasiada memoria.
No solo las imágenes ocupan mucha memoria, asegúrese de no guardar demasiados datos en la memoria. Esto puede suceder fácilmente si tiene listas infinitas en su aplicación. Intente almacenar en caché los datos en DataBase.
En Android 4.2, hay un error (stackoverflow # 13754876) con la aceleración de hardware, por lo que si lo usa
hardwareAccelerated=true
en su manifiesto, perderá memoria.GLES20DisplayList
- siga guardando referencias, incluso si hizo el paso (2) y nadie más está haciendo referencia a este mapa de bits. Aquí necesitas:a) deshabilitar la aceleración de hardware para api 16/17;
o
b) separar la vista que contiene el mapa de bits
Para Android 3+, puede intentar usarlo
android:largeHeap="true"
en suAndroidManifest
. Pero no resolverá tus problemas de memoria, simplemente posponlos.Si necesita, como, navegación infinita, entonces Fragmentos - debería ser su elección. Entonces tendrás 1 actividad, que simplemente cambiará entre fragmentos. De esta forma también resolverás algunos problemas de memoria, como el número 4.
Utilice Memory Analyzer para averiguar la causa de la pérdida de memoria.
Aquí hay un muy buen video de Google I / O 2011: Administración de memoria para aplicaciones de Android
Si se trata de mapas de bits, debe leer esto: Visualización de mapas de bits de manera eficiente
fuente
outOfMemory
error. ¡Gracias!Los mapas de bits suelen ser los culpables de los errores de memoria en Android, por lo que sería una buena área para verificar.
fuente
outOfMemory
error. ¡Gracias!¿Tiene algunas referencias a cada actividad? AFAIK, esta es una razón que evita que Android elimine actividades de la pila.
¿También podemos reproducir este error en otros dispositivos? He experimentado un comportamiento extraño de algunos dispositivos Android según la ROM y / o el fabricante del hardware.
fuente
Creo que el problema quizás sea una combinación de muchos factores indicados aquí en las respuestas, lo que le está dando problemas. Como dijo @Tim, una referencia (estática) a una actividad o un elemento en esa actividad puede hacer que el GC omita la Actividad. Aquí está el artículo que discute esta faceta. Creo que el problema probable proviene de algo que mantiene la Actividad en un estado de "Proceso visible" o superior, lo que prácticamente garantizará que la Actividad y sus recursos asociados nunca se recuperen.
Pasé por el problema opuesto hace un tiempo con un Servicio, así que eso es lo que me hizo pensar: hay algo que mantiene su Actividad en la parte superior de la lista de prioridades del proceso para que no esté sujeta al sistema GC, como una referencia (@Tim) o un bucle (@Alvaro). El bucle no tiene por qué ser un elemento de ejecución prolongada o sin fin, solo algo que se parece mucho a un método recursivo o un bucle en cascada (o algo por el estilo).
EDITAR: Según tengo entendido, Android llama automáticamente a onPause y onStop según sea necesario. Los métodos están ahí principalmente para que los modifique para que pueda ocuparse de lo que necesita antes de que se detenga el proceso de alojamiento (guardar variables, guardar manualmente el estado, etc.); pero tenga en cuenta que se indica claramente que onStop (junto con onDestroy) no se puede llamar en todos los casos. Además, si el proceso de alojamiento también aloja una Actividad, Servicio, etc. que tiene un estado "Forground" o "Visible", es posible que el sistema operativo ni siquiera busque detener el proceso / hilo. Por ejemplo: una Actividad y un Servicio se combinan en el mismo proceso y el Servicio regresa
START_STICKY
deonStartCommand()
el proceso toma automáticamente al menos un estado visible. Esa podría ser la clave aquí, intente declarar un nuevo proceso para la Actividad y vea si eso cambia algo. Intente agregar esta línea a la declaración de su Actividad en el Manifiesto como:android:process=":proc2"
y luego ejecute las pruebas nuevamente si su Actividad comparte un proceso con cualquier otra cosa. La idea aquí es que si ha limpiado su Actividad y está bastante seguro de que el problema no es su Actividad, entonces el problema es otra cosa y es hora de buscar eso.Además, no puedo recordar dónde lo vi (si es que lo vi en los documentos de Android) pero recuerdo algo sobre una
PendingIntent
referencia a una Actividad que puede hacer que una Actividad se comporte de esta manera.Aquí hay un enlace a la
onStartCommand()
página con algunas ideas sobre el proceso de no matar.fuente
así que lo único que realmente puedo pensar es si tiene una variable estática que hace referencia directa o indirectamente al contexto. Incluso algo tanto como una referencia a parte de la aplicación. Estoy seguro de que ya lo ha probado, pero lo sugeriré por si acaso, intente anular TODAS sus variables estáticas en onDestroy () solo para asegurarse de que el recolector de basura lo obtenga
fuente
La mayor fuente de pérdida de memoria que he encontrado fue causada por alguna referencia global, de alto nivel o de larga data al contexto. Si mantiene el "contexto" almacenado en una variable en cualquier lugar, puede encontrar pérdidas de memoria impredecibles.
fuente
Intente pasar getApplicationContext () a cualquier cosa que necesite un contexto. Es posible que tenga una variable global que contenga una referencia a sus actividades y evite que se recolecten como basura.
fuente
Una de las cosas que realmente ayudó con el problema de la memoria en mi caso terminó siendo la configuración de Purgeable en verdadero para mis Bitmaps. Consulte ¿Por qué NO usaría la opción inPurgeable de BitmapFactory? y la discusión de la respuesta para obtener más información.
La respuesta de Dianne Hackborn y nuestra discusión posterior (también gracias, CommonsWare) ayudaron a aclarar ciertas cosas sobre las que estaba confundido, así que gracias por eso.
fuente
Encontré el mismo problema contigo. Estaba trabajando en una aplicación de mensajería instantánea, para el mismo contacto, es posible iniciar un ProfileActivity en un ChatActivity, y viceversa. Solo agrego una cadena adicional en la intención de iniciar otra actividad, toma la información del tipo de clase de actividad de inicio y la identificación del usuario. Por ejemplo, ProfileActivity inicia una ChatActivity, luego en ChatActivity.onCreate, marco el tipo de clase de invocador 'ProfileActivity' y la identificación del usuario, si va a iniciar una Activity, verificaría si es una 'ProfileActivity' para el usuario o no . Si es así, simplemente llame a 'finish ()' y regrese a la ProfileActivity anterior en lugar de crear una nueva. La pérdida de memoria es otra cosa.
fuente