squareup library adding android out-of-memory picasso

android - library - Picasso: sin memoria



picasso get() load (4)

Tengo un RecyclerView presentando varias imágenes usando Picasso . Después de desplazarse un poco 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 tomo en cuenta al depurar:

  1. Al instalar la aplicación en un teléfono o dispositivo virtual, las imágenes se cargan a través de la red, que es como debe ser. Esto se ve por el triángulo rojo en la esquina superior izquierda de la imagen.
  2. Cuando se desplaza para que las imágenes se vuelvan a cargar, se recuperan 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 de la memoria, como se ve por un triángulo verde en la esquina superior izquierda.
  4. Después de desplazarse un poco más, se produce la excepción de falta de memoria y se detiene la carga. Solo se muestra la imagen del marcador de posición en las imágenes que no se guardan actualmente en la memoria, mientras que las que están en la memoria se muestran correctamente con un triángulo verde.

Here hay una imagen de muestra. Es bastante grande, pero uso fit() para reducir el espacio de memoria en la aplicación.

Así que mis preguntas son:

  • ¿No deberían las imágenes volver a cargarse desde el disco cuando la memoria caché está llena?
  • ¿Son las imágenes demasiado grandes? ¿Cuánta memoria puedo esperar una, digamos 0.5 MB de imagen, para consumir cuando se decodifica?
  • ¿Hay algo incorrecto / inusual en mi código de abajo?

Configurando 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); }

Usando 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... }

El 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 continuo del RecyclerView hace que la asignación de memoria aumente indefinidamente. Hice una prueba RecyclerView simplificada para que coincida con la documentación oficial , utilizando una sola imagen para 200 CardView s con una 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 diez ImageView carga la imagen desde el disco (azul). Cuando la imagen se carga desde el disco, se realiza una asignación de memoria, lo que aumenta la asignación en el montón y por lo tanto el propio montón.

Intenté eliminar mi propia configuración de la instancia global de Picasso y usar la predeterminada, pero los problemas son los mismos.

Hice una verificación con el monitor del dispositivo Android, vea la imagen a continuación. Esto es para un Galaxy S3. Cada una de las asignaciones realizadas cuando se carga una imagen desde el disco se puede ver a la derecha en "Recuento de asignación por tamaño". El tamaño es ligeramente diferente para cada asignación de la imagen, lo que también es extraño. Al presionar "Causa GB", desaparece la asignación más a la derecha de 4.7 MB.

El comportamiento es el mismo para 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".

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


Acabo de hacer una clase de Singleton para LoadImages. El problema era que estaba usando demasiados objetos Picasso creados con demasiados Picasso.Builder. Aquí está mi implementación:

public class ImagesLoader { private static ImagesLoader currentInstance = null; private static Picasso currentPicassoInstance = null; protected ImagesLoader(Context context) { initPicassoInstance(context); } private void initPicassoInstance(Context context) { Picasso.Builder builder = new Picasso.Builder(context); builder.listener(new Picasso.Listener() { @Override public void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception) { exception.printStackTrace(); } }); currentPicassoInstance = builder.build(); } public static ImagesLoader getInstance(Context context) { if (currentInstance == null) { currentInstance = new ImagesLoader(context); } return currentInstance; } public void loadImage(ImageToLoad loadingInfo) { String imageUrl = loadingInfo.getUrl().trim(); ImageView destination = loadingInfo.getDestination(); if (imageUrl.isEmpty()) { destination.setImageResource(loadingInfo.getErrorPlaceholderResourceId()); } else { currentPicassoInstance .load(imageUrl) .placeholder(loadingInfo.getPlaceholderResourceId()) .error(loadingInfo.getErrorPlaceholderResourceId()) .into(destination); } } }

Luego, crea una clase ImageToLoad que contiene ImageView, Url, Placeholder y Error Placeholder.

public class ImageToLoad { private String url; private ImageView destination; private int placeholderResourceId; private int errorPlaceholderResourceId; //Getters and Setters }


Con picasso puedes resolver el problema usando su propiedad como:

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

Necesitas redimensionar la imagen.


No estoy seguro de que fit() funcione con android:adjustViewBounds="true" . Según algunos de los temas anteriores , parece ser problemático.

Algunas recomendaciones:

  • Establecer un tamaño fijo para el ImageView
  • GlobalLayoutListener un GlobalLayoutListener para obtener el tamaño de ImageView una vez que se haya calculado y luego de esta llamada, Picasso agregará el método resize()
  • Probar a Glide : su configuración predeterminada da como resultado una huella más baja que la de Picasso (almacena la imagen redimensionada en lugar del original y utiliza RGB565)

.memoryCache(new LruCache(100000000)) // Maybe something fishy here?

Yo diría que esto es realmente sospechoso: le estás dando a LruCache 100MB de espacio. Aunque todos los dispositivos son diferentes, esto será LruCache o superior al límite para algunos dispositivos, y tenga en cuenta que esto es solo el LruCache , sin tener en cuenta el LruCache dinámico que requiere el resto de su aplicación. Supongo que esta es la causa directa de la excepción: le está diciendo a LruCache que puede ser mucho más grande de lo que debería ser.

Reduciría esto a algo así como 5 MB para probar primero la teoría y luego experimentar con valores incrementalmente 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. Por último, está el android:largeHeap="true" que puede agregar a su manifiesto, pero, en general, es una mala práctica.

Sus imágenes son realmente grandes, así que sugeriría reducirlas también. Tenga en cuenta que aunque los esté recortando, aún deben cargarse temporalmente en la memoria en su tamaño completo.