Picasso: fuera de la memoria

Tengo un RecyclerView presenta varias imágenes usando Picasso . Después de desplazarse un rato hacia arriba y hacia abajo la aplicación se queda sin memoria con mensajes como este:

 E/dalvikvm-heap﹕ Out of memory on a 3053072-byte allocation. I/dalvikvm﹕ "Picasso-/wp-content/uploads/2013/12/DSC_0972Small.jpg" prio=5 tid=19 RUNNABLE I/dalvikvm﹕ | group="main" sCount=0 dsCount=0 obj=0x42822a50 self=0x59898998 I/dalvikvm﹕ | sysTid=25347 nice=10 sched=0/0 cgrp=apps/bg_non_interactive handle=1500612752 I/dalvikvm﹕ | state=R schedstat=( 10373925093 843291977 45448 ) utm=880 stm=157 core=3 I/dalvikvm﹕ at android.graphics.BitmapFactory.nativeDecodeStream(Native Method) I/dalvikvm﹕ at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:623) I/dalvikvm﹕ at com.squareup.picasso.BitmapHunter.decodeStream(BitmapHunter.java:142) I/dalvikvm﹕ at com.squareup.picasso.BitmapHunter.hunt(BitmapHunter.java:217) I/dalvikvm﹕ at com.squareup.picasso.BitmapHunter.run(BitmapHunter.java:159) I/dalvikvm﹕ at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:390) I/dalvikvm﹕ at java.util.concurrent.FutureTask.run(FutureTask.java:234) I/dalvikvm﹕ at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080) I/dalvikvm﹕ at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573) I/dalvikvm﹕ at java.lang.Thread.run(Thread.java:841) I/dalvikvm﹕ at com.squareup.picasso.Utils$PicassoThread.run(Utils.java:411) I/dalvikvm﹕ [ 08-10 18:48:35.519 25218:25347 D/skia ] --- decoder->decode returned false 

Las cosas que noto al depurar:

  1. Al instalar la aplicación en un teléfono o un dispositivo virtual, las imágenes se cargan a través de la red, que es cómo se pretende que sea. Esto se ve por el triángulo rojo en la esquina superior izquierda de la imagen.
  2. Al desplazarse para que las imágenes se vuelvan a cargar, se extraen del disco. Esto se ve por el triángulo azul en la esquina superior izquierda de la imagen.
  3. Al desplazarse un poco más, algunas de las imágenes se cargan desde la memoria, como se ve por un triángulo verde en la esquina superior izquierda.
  4. Después de desplazarse un poco más, la excepción de memoria insuficiente se produce y la carga se detiene. Sólo se muestra la imagen de marcador de posición en las imágenes que no se guardan en la memoria, mientras que las que están en la memoria se muestran correctamente con un triángulo verde.

Aquí hay una imagen de ejemplo. Es bastante grande, pero utilizo fit() para reducir la huella de memoria en la aplicación.

Así que mis preguntas son:

  • ¿No deberían recargarse las imágenes desde el disco cuando la memoria caché está llena?
  • ¿Las imágenes son demasiado grandes? ¿Cuánta memoria puedo esperar de una imagen, digamos 0,5 MB, para consumir cuando se decodifica?
  • ¿Hay algo malo / inusual en mi código de abajo?

Configuración de la instancia estática de Picasso al crear la Activity :

 private void setupPicasso() { Cache diskCache = new Cache(getDir("foo", Context.MODE_PRIVATE), 100000000); OkHttpClient okHttpClient = new OkHttpClient(); okHttpClient.setCache(diskCache); Picasso picasso = new Picasso.Builder(this) .memoryCache(new LruCache(100000000)) // Maybe something fishy here? .downloader(new OkHttpDownloader(okHttpClient)) .build(); picasso.setIndicatorsEnabled(true); // For debugging Picasso.setSingletonInstance(picasso); } 

Uso de la instancia estática de Picasso en mi RecyclerView.Adapter :

 @Override public void onBindViewHolder(RecipeViewHolder recipeViewHolder, int position) { Picasso.with(mMiasMatActivity) .load(mRecipes.getImage(position)) .placeholder(R.drawable.picasso_placeholder) .fit() .centerCrop() .into(recipeViewHolder.recipeImage); // recipeImage is an ImageView // More... } 

ImageView en el archivo XML:

 <ImageView android:id="@+id/mm_recipe_item_recipe_image" android:layout_width="match_parent" android:layout_height="wrap_content" android:adjustViewBounds="true" android:paddingBottom="2dp" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:clickable="true" /> 

Actualizar

Parece que el desplazamiento del RecyclerView hace que el aumento de la asignación de memoria permanezca indefinidamente. Hice una prueba de RecyclerView despojado para coincidir con la documentación oficial , utilizando una sola imagen para 200 CardView s con un ImageView , pero el problema persiste. La mayoría de las imágenes se cargan desde la memoria (verde) y el desplazamiento es suave, pero aproximadamente cada décimo ImageView carga la imagen desde el disco (azul). Cuando se carga la imagen desde el disco, se realiza una asignación de memoria, aumentando así la asignación en el montón y, por lo tanto, el propio montón.

He intentado eliminar mi propia configuración de la instancia global de Picasso y usar el valor predeterminado en su lugar, pero los problemas son los mismos.

Hice una comprobación con el monitor del dispositivo del androide, vea la imagen abajo. Esto es para un Galaxy S3. Cada una de las asignaciones hechas cuando una imagen se carga desde el disco se puede ver a la derecha en "Conteo de asignación por tamaño". El tamaño es ligeramente diferente para cada asignación de la imagen, que también es extraño. Presionando "Causa GB" hace que la asignación más a la derecha de 4,7 MB desaparezca.

Monitor de dispositivos Android

El comportamiento es el mismo para los dispositivos virtuales. La imagen de abajo lo muestra para un Nexus 5 AVD. También aquí, la asignación más grande (la de 10,6 MB) desaparece al presionar "Causa GB".

Monitor de dispositivos Android

Además, aquí hay imágenes de las ubicaciones de asignación de memoria y los subprocesos del Monitor de dispositivos Android. Las asignaciones recurrentes se realizan en los hilos de Picasso mientras que el que se eliminó con la Cause GB se realiza en el hilo principal.

Seguimiento de asignación Trapos

No estoy seguro que fit() funcione con android:adjustViewBounds="true" . De acuerdo con algunas de las ediciones pasadas parece ser problemático.

Algunas recomendaciones:

  • Establecer un tamaño fijo para el ImageView
  • Utilice un GlobalLayoutListener para obtener el tamaño de ImageView una vez que se calcula y después de esta llamada Picasso agregando el método resize()
  • Dale a Glide un intento – su configuración predeterminada da como resultado una menor huella que Picasso (almacena la imagen redimensionada en lugar de la original y usa RGB565)
 .memoryCache(new LruCache(100000000)) // Maybe something fishy here? 

Yo diría que esto es realmente a pescado – que está dando el LruCache 100MB de espacio. Aunque todos los dispositivos son diferentes, esto va a estar en o por encima del límite para algunos dispositivos, y tenga en cuenta que este es sólo el LruCache , sin tener en cuenta el mucho espacio de montón que el resto de su aplicación requiere. Mi conjetura es que ésta es la causa directa de la excepción – le estás diciendo a LruCache que se permite obtener mucho más grande de lo que debería ser.

Yo reduciría esto a algo así como 5 MB para probar primero la teoría, y luego experimentar con incrementalmente valores más altos en sus dispositivos de destino. También puede consultar el dispositivo para saber cuánto espacio tiene y establecer este valor mediante programación si lo desea. Finalmente, está el android:largeHeap="true" que puedes agregar a tu manifiesto, pero he recogido que esto es generalmente mala práctica.

Sus imágenes son realmente grandes por lo que sugeriría reducir los así. Tenga en cuenta que a pesar de que usted está recortándolos, todavía temporalmente necesitan ser cargados en la memoria en su tamaño completo.

Con picasso puedes resolver el problema usando su propiedad como:

  Picasso.with(context) .load(url) .resize(300,300) .into(listHolder.imageview); 

Necesita cambiar el tamaño de la imagen.

  • Cómo cargar un mapa de bits con Picasso sin usar un ImageView?
  • Gestión de la memoria de Android: Densidad de pantalla, tamaños de imagen solicitados y montón disponible
  • Con RecyclerView, ¿Picasso sigue siendo necesario?
  • Memoria caché de picasso para Android
  • Picasso para cargar matriz de bytes
  • Obtener imagen uri de picasso?
  • ¿Cómo puedo configurar la imagen de fondo con picasso en el código
  • Cargar mapa de bits con Picasso
  • ¿Por qué Picasso no está usando imageView.getContext ()?
  • Uso de Picasso con Image Getter
  • Zoom de TouchImageView ajustado a fotograma con Picasso
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.