Problemas con la memoria de la aplicación Android: lo probé todo y todavía está perdido

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 haga una lista de Actividades con datos, por ejemplo, distintivos (B). Puede pasar del perfil a una lista de distintivos a otros perfiles, a otras listas, etc.

Imagine un flujo como este P1 -> B1 -> P2 -> B2 -> P3 -> B3, etc. Para mayor coherencia, estoy cargando perfiles e insignias del mismo usuario, por lo que cada página P es la misma 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 falta de memoria en lugares aleatorios – mapas de bits, cadenas, etc. – no parece ser consistente.

Después de hacer todo lo imaginable para descubrir por qué me estoy quedando sin memoria, no he encontrado nada. Lo que no entiendo es por qué Android no está matando a P1, B1, etc., si se agota la memoria al cargarse y en su lugar falla. Esperaría que estas actividades anteriores murieran y resucitaran si 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, sigo teniendo un accidente. Esto indica algún tipo de pérdida de memoria, pero incluso después de descargar hprof y usar MAT y JProfiler, no puedo precisarlo.

Inhabilité la carga de imágenes desde la web (y aumenté los datos de prueba cargados para compensarla y hacer que la prueba sea justa) y me aseguré de que la memoria caché de imágenes use SoftReferences. Android en realidad trata de liberar las pocas SoftReferences que tiene, pero justo antes de que falle la memoria.

Las páginas de insignias obtienen información de la web, la cargan en una matriz de EntityData desde un BaseAdapter y la alimentan a un ListView (en realidad estoy usando el MergeAdapter excelente de CommonsWare , pero en esta actividad de Badge, en realidad solo hay 1 adaptador, pero quería mencionar este hecho de cualquier manera).

He revisado el código y no he podido encontrar nada que pueda filtrarse. Limpié 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 stack no se cosechan, y realmente me encantaría resolverlo.

En este punto, estoy buscando sugerencias, consejos, soluciones … cualquier cosa que pueda ayudar.

Gracias.

Todavía no entiendo por qué las actividades inactivas que están en la stack no se cosechan, y realmente me encantaría resolverlo.

Así no es como funcionan las cosas. La única gestió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 necesita matar procesos en segundo plano para recuperar algo.

Si su aplicación está en primer plano y comienza más y más actividades, nunca pasará a segundo plano, por lo que siempre alcanzará el límite de memoria de proceso local antes de que el sistema llegue a matar su proceso. (Y cuando mata su proceso, matará el proceso que aloja todas las actividades, incluido lo que está actualmente en primer plano).

Por lo tanto, me parece que su problema básico es: está dejando demasiadas actividades ejecutándose al mismo tiempo, y / o cada una de esas actividades se está aferrando a demasiados recursos.

Solo necesita rediseñar su navegación para no confiar en acumular un número arbitrario 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 variables de cualquier otra cosa a la que se aferre), simplemente se quedará sin memoria.

Es posible que desee considerar el uso de las nuevas Fragment APIs para reemplazar esta stack arbitraria de actividades con una actividad única que administra su memoria de forma más precisa. Por ejemplo, si usa los recursos de la stack trasera de fragmentos, cuando un fragmento va en la stack posterior y ya no está visible, se llama al método onDestroyView () para eliminar por completo su jerarquía de vista, lo que reduce en gran medida su huella.

Ahora, en la medida en que te estrelles en el flujo donde presionas hacia atrás, vas a una actividad, presionas hacia atrás, pasas a otra actividad, etc. y nunca tienes una stack profunda, entonces sí, solo tienes una filtración. Esta publicación de blog describe cómo depurar fugas: http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html

Algunos consejos:

  1. Asegúrate de no tener contexto de actividad de fuga.

  2. Asegúrese de no guardar referencias en bitmaps. Limpia todos tus ImageView en Activity # onStop, algo como esto:

     Drawable d = imageView.getDrawable(); if (d != null) d.setCallback(null); imageView.setImageDrawable(null); imageView.setBackgroundDrawable(null); 
  3. Recicle bitmaps si ya no los necesita.

  4. Si usa caché de memoria, como memory-lru, asegúrese de que no esté usando mucha memoria.

  5. No solo las imágenes toman 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. Intenta almacenar datos en caché en DataBase.

  6. En Android 4.2, hay un error (stackoverflow # 13754876) con aceleración de hardware, por lo que si usa hardwareAccelerated=true en su manifiesto, perderá memoria. GLES20DisplayList : siga manteniendo las referencias, incluso si hizo el paso (2) y nadie más está haciendo referencia a este bitmap. Aquí necesitas:

    a) desactivar la aceleración de hardware para api 16/17;
    o
    b) separar vista que sostiene bitmap

  7. Para Android 3+ puedes intentar usar android:largeHeap="true" en tu AndroidManifest . Pero no resolverá tus problemas de memoria, solo los pospones.

  8. 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.

  9. Use el Analizador de memoria para descubrir la causa de su pérdida de memoria.
    Este es un muy buen video de Google I / O 2011: administración de memoria para aplicaciones de Android
    Si se trata de mapas de bits, esto debería ser una lectura obligada: mostrar mapas de bits de manera eficiente

Los mapas de bits a menudo son los culpables de errores de memoria en Android, por lo que sería una buena área para verificar.

¿Tiene algunas referencias a cada actividad? AFAIK esta es una razón que impide que Android elimine actividades de la stack.

¿También puedes reproducir este error en otros dispositivos? Experimenté un comportamiento extraño de algunos dispositivos Android dependiendo del fabricante de la ROM y / o del hardware.

Creo que el problema tal vez sea una combinación de muchos factores que se mencionan 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 discutiendo esta faceta. Creo que el problema probable proviene de mantener la Actividad en un estado de “Proceso visible” o superior, lo que garantizará que la Actividad y sus recursos asociados nunca sean recuperados.

Pasé por el problema opuesto hace un tiempo con un servicio, así que eso fue lo que me impulsó a pensar: hay algo que mantiene su actividad en lo alto de la lista de prioridad del proceso para que no esté sujeta al GC del sistema, como una referencia (@Tim) o un bucle (@Alvaro). No es necesario que el bucle sea un elemento interminable o de larga ejecución, simplemente algo que se ejecuta de manera muy parecida a un método recursivo o un bucle en cascada (o algo parecido).

EDITAR: según tengo entendido, Android llama a las funciones onPause y onStop cuando sea necesario. Los métodos están ahí principalmente para que los supere, de modo que pueda ocuparse de lo que necesita antes de detener el proceso de alojamiento (guardar variables, guardar el estado manualmente, etc.); pero tenga en cuenta que está claramente establecido que no se puede llamar a OnStop (junto con onDestroy) 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”, el sistema operativo ni siquiera podría ver detener el proceso / thread. Por ejemplo: una actividad y un servicio se publican en el mismo proceso y el servicio devuelve START_STICKY desde onStartCommand() el proceso toma automáticamente al menos un estado visible. Esa podría ser la clave aquí, intenta declarar un nuevo proceso para la Actividad y ver si eso cambia algo. Intente agregar esta línea a la statement de su Actividad en el Manifiesto como: android:process=":proc2" y luego vuelva a ejecutar las pruebas si su Actividad comparte un proceso con cualquier otra cosa. El pensamiento aquí es que si has limpiado tu actividad y estás bastante seguro de que el problema no es tu actividad, entonces algo más es el problema y es hora de cazar para eso.

Además, no puedo recordar dónde lo vi (si es que lo vi en los documentos de Android), pero recuerdo algo sobre un PendingIntent referencia a una actividad puede hacer que una actividad se comporte de esta manera.

Aquí hay un enlace para la página onStartCommand() con algunas ideas sobre el proceso de no matar frente.

entonces, lo único en lo que realmente puedo pensar es si tienes una variable estática que haga referencia directa o indirectamente al contexto. Incluso algo así como una referencia a parte de la aplicación. Estoy seguro de que ya lo ha intentado, pero lo sugeriré por si acaso, intente anular TODAS sus variables estáticas en el onDestroy () solo para asegurarse de que el recolector de basura lo reciba

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.

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 recolecte basura.

Una de las cosas que realmente ayudó a la cuestión de la memoria en mi caso, fue configurarse en Virtuable en True para mis Bitmaps. Ver ¿Por qué nunca usaría la opción inPurgeable de BitmapFactory? y la discusión de la respuesta para más información.

La respuesta de Dianne Hackborn y nuestra discusión posterior (también gracias, CommonsWare) ayudaron a aclarar ciertas cosas con las que estaba confundido, así que gracias por eso.

Me encontré con el mismo problema contigo. Estaba trabajando en una aplicación de mensajería instantánea, para el mismo contacto, es posible iniciar una ProfileActivity en una ChatActivity, y viceversa. Solo agrego una cadena adicional en el bash de comenzar otra actividad, toma la información del tipo de clase de actividad inicial y la identificación del usuario. Por ejemplo, ProfileActivity inicia una ChatActivity, luego en ChatActivity.onCreate, marco el tipo de clase de invoker ‘ProfileActivity’ y el ID de usuario, si va a iniciar una actividad, comprobaría si es una ‘ProfileActivity’ para el usuario o no. . Si es así, simplemente llame a ‘finish ()’ y regrese a la anterior ProfileActivity en lugar de crear una nueva. La pérdida de memoria es otra cosa.

    Intereting Posts