Otra pregunta de descarga de imágenes

Sé que esto fue desaprobado muchas veces antes, pero tengo fugas de memoria usando LazyList y no tengo ideas de cómo solucionarlo. Utilizo una instancia de ImageLoader en la aplicación completa, lo creo en Application.onCreate (), porque necesito descargar imágenes en varias actividades: actividad de la lista, una actividad con el widget de la galería y la actividad de la galería de pantalla completa (todas ellas usan la misma caché ) Modifiqué el cargador de imágenes para que use HashMap basado en SoftReference. Aquí está el código para SoftHashMap:

public class SoftHashMap extends AbstractMap { private final Map hash=new HashMap(); private final int HARD_SIZE; private final LinkedList hardCache=new LinkedList(); private final ReferenceQueue queue=new ReferenceQueue(); public SoftHashMap(){ this(100); } public SoftHashMap(int hardSize){ HARD_SIZE=hardSize; } public Object get(Object key){ Object result=null; SoftReference soft_ref=(SoftReference)hash.get(key); if(soft_ref!=null){ result=soft_ref.get(); if(result==null){ hash.remove(key); }else{ hardCache.addFirst(result); if(hardCache.size()>HARD_SIZE){ hardCache.removeLast(); } } } return result; } private static class SoftValue extends SoftReference{ private final Object key; public SoftValue(Object k, Object key, ReferenceQueue q) { super(k, q); this.key=key; } } private void processQueue(){ SoftValue sv; while((sv=(SoftValue)queue.poll())!=null){ hash.remove(sv.key); } } public Object put(Object key, Object value){ processQueue(); return hash.put(key, new SoftValue(value, key, queue)); } public void clear(){ hardCache.clear(); processQueue(); hash.clear(); } public int size(){ processQueue(); return hash.size(); } public Set entrySet() { throw new UnsupportedOperationException(); } } 

Clase ImageLoader:

 public class ImageLoader { private SoftHashMap cache=new SoftHashMap(15); private File cacheDir; final int stub_id=R.drawable.stub; private int mWidth, mHeight; public ImageLoader(Context context, int h, int w){ mWidth=w; mHeight=h; photoLoaderThread.setPriority(Thread.NORM_PRIORITY); if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) cacheDir=new File(android.os.Environment.getExternalStorageDirectory(),"CacheDir"); else cacheDir=context.getCacheDir(); if(!cacheDir.exists()) cacheDir.mkdirs(); } public void DisplayImage(String url, Activity activity, ImageView imageView) { Log.d("IMAGE LOADER", "getNativeHeapSize()-"+String.valueOf(Debug.getNativeHeapSize()/1024)+" kb"); Log.d("IMAGE LOADER", "getNativeHeapAllocatedSize()-"+String.valueOf(Debug.getNativeHeapAllocatedSize()/1024)+" kb"); Log.d("IMAGE LOADER", "getNativeHeapFreeSize()-"+String.valueOf(Debug.getNativeHeapFreeSize()/1024)+" kb"); if(cache.get(url)!=null){ imageView.setImageBitmap((Bitmap)cache.get(url)); } else { queuePhoto(url, activity, imageView); imageView.setImageResource(stub_id); } } private void queuePhoto(String url, Activity activity, ImageView imageView) { //This ImageView may be used for other images before. So there may be some old tasks in the queue. We need to discard them. photosQueue.Clean(imageView); PhotoToLoad p=new PhotoToLoad(url, imageView); synchronized(photosQueue.photosToLoad){ photosQueue.photosToLoad.push(p); photosQueue.photosToLoad.notifyAll(); } //start thread if it's not started yet if(photoLoaderThread.getState()==Thread.State.NEW) photoLoaderThread.start(); } private Bitmap getBitmap(String url) { //I identify images by hashcode. Not a perfect solution, good for the demo. String filename=String.valueOf(url.hashCode()); File f=new File(cacheDir, filename); //from SD cache Bitmap b = decodeFile(f); if(b!=null) return b; //from web try { Bitmap bitmap=null; InputStream is=new URL(url).openStream(); OutputStream os = new FileOutputStream(f); Utils.CopyStream(is, os); os.close(); bitmap = decodeFile(f); return bitmap; } catch (Exception ex){ ex.printStackTrace(); return null; } } //decodes image and scales it to reduce memory consumption private Bitmap decodeFile(File f){ Bitmap b=null; try { //decode image size BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; FileInputStream fis=new FileInputStream(f); BitmapFactory.decodeStream(fis,null,o); try { fis.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } //Find the correct scale value. It should be the power of 2. //final int REQUIRED_SIZE=mWidth; int width_tmp=o.outWidth, height_tmp=o.outHeight; int scale=1; while(true){ if(width_tmp/2<=mWidth || height_tmp/2<=mHeight) break; width_tmp/=2; height_tmp/=2; scale*=2; } //decode with inSampleSize BitmapFactory.Options o2 = new BitmapFactory.Options(); o2.inSampleSize=scale; //o2.inPurgeable=true; fis=new FileInputStream(f); b=BitmapFactory.decodeStream(fis, null, o2); try { fis.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return b; } catch (FileNotFoundException e) {} return null; } class PhotoToLoad{ public String url; public ImageView imageView; public PhotoToLoad(String u, ImageView i){ url=u; imageView=i; } } PhotosQueue photosQueue=new PhotosQueue(); public void stopThread() { photoLoaderThread.interrupt(); } class PhotosQueue{ private Stack<PhotoToLoad> photosToLoad=new Stack<PhotoToLoad>(); public void Clean(ImageView image) { for(int j=0 ;j<photosToLoad.size();){ if(photosToLoad.get(j).imageView==image) photosToLoad.remove(j); else ++j; } } } class PhotosLoader extends Thread{ public void run(){ try { while(true) { //thread waits until there are any images to load in the queue if(photosQueue.photosToLoad.size()==0) synchronized(photosQueue.photosToLoad){ photosQueue.photosToLoad.wait(); } if(photosQueue.photosToLoad.size()!=0) { PhotoToLoad photoToLoad; synchronized(photosQueue.photosToLoad){ photoToLoad=photosQueue.photosToLoad.pop(); } Bitmap bmp=getBitmap(photoToLoad.url); cache.put(photoToLoad.url, bmp); Object tag=photoToLoad.imageView.getTag(); if(tag!=null && ((String)tag).equals(photoToLoad.url)){ BitmapDisplayer bd=new BitmapDisplayer(bmp, photoToLoad.imageView); Activity a=(Activity)photoToLoad.imageView.getContext(); a.runOnUiThread(bd); } } if(Thread.interrupted()) break; } } catch (InterruptedException e) { //allow thread to exit } } } PhotosLoader photoLoaderThread=new PhotosLoader(); class BitmapDisplayer implements Runnable { Bitmap bitmap; ImageView imageView; public BitmapDisplayer(Bitmap b, ImageView i){bitmap=b;imageView=i;} public void run() { if(bitmap!=null) imageView.setImageBitmap(bitmap); else imageView.setImageResource(stub_id); } } public void clearCache() { //clear memory cache cache.clear(); //clear SD cache File[] files=cacheDir.listFiles(); for(File f:files) f.delete(); } } 

Y mi clase de aplicación, no la mejor manera de hacerlo, sin embargo:

 public class MyApplication extends Application { ImageLoader mImageLoader; @Override public void onCreate(){ int h =((WindowManager)getApplicationContext().getSystemService(WINDOW_SERVICE)).getDefaultDisplay().getHeight(); int w =((WindowManager)getApplicationContext().getSystemService(WINDOW_SERVICE)).getDefaultDisplay().getWidth(); mImageLoader=new ImageLoader(getApplicationContext(), h, w); super.onCreate(); public ImageLoader getImageLoader(){ return mImageLoader; } @Override public void onLowMemory(){ mImageLoader.clearCache(); Log.d("MY APP", "ON LOW MEMORY"); super.onLowMemory(); } } 

Y lo peor: después de algún tiempo recibo una excepción de OOM cuando ImageLoader intenta decodificar otro bitmap. Apreciaré su ayuda. Gracias.

EDIT Me he librado de caché duro, pero sigo recibiendo esta excepción de OOM. Me parece que estoy haciendo algo más divertido. Ni siquiera sé qué información adicional debo proporcionar … Las imágenes que descargar desde el servidor son bastante grandes, sin embargo. Y la aplicación no asigna aprox. 1,5 mb, eso es lo que veo en LogCat. Pero simplemente no puedo entender por qué no vm claro mi SoftHashMap si hay necesidad de memoria …

4 Solutions collect form web for “Otra pregunta de descarga de imágenes”

  1. onLowMemory no te ayudará, ya que no se genera cuando la aplicación se agota. Se llama cuando el sistema Android desea la memoria para una aplicación diferente o antes de eliminar los procesos.
  2. No veo la necesidad de la caché dura – esto está evitando que los drawables sean reciclados. Simplemente deje los dibujables en la memoria caché suave – el GC no recogerá la memoria mientras que los drawables tienen referencias que no son suaves así que usted no necesita preocuparse de un drawable fijado actualmente en un ImageView que es reciclado.

También cuántas imágenes que se muestran en la pantalla a la vez? ¿Cuán grandes son?

  1. Aquí hay un artículo sorprendente sobre el análisis de fugas de memoria. Definitivamente puede ayudarle. http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html .

  2. ¿Estás absolutamente seguro de que tu implementación de SoftHashMap funciona bien? Parece bastante complicado. Puede utilizar el depurador para garantizar SoftHashMap nunca tiene más de 15 mapas de bits. MAT también puede ayudarle a identificar cuántos mapas de bits hay en la memoria.
    También puede hacer una llamada de cache.put (photoToLoad.url, bmp). De esta manera, desactivará el almacenamiento en caché en memoria para identificar si es una causa de un problema o no.

  3. Sí, puede ser una Fuga de actividad. Puedes identificar eso. Si sólo se desplaza por la misma actividad y recibe MOO, significa que algo más está filtrando no actividad. Si detiene / inicia la actividad varias veces y recibe MOO significa que la actividad está goteando. También se puede decir definitivamente es la actividad de fuga o no si echa un vistazo a MAT histograma.

  4. Al usar inSampleSize el tamaño de la imagen no importa. Debe funcionar bien incluso con imágenes de 5mpx.

  5. Puede intentar reemplazar su implementación de SoftHashMap con sólo HashMap <String, SoftReference <Bitmap>. Lea sobre SoftReference. Es bueno para la implementación muy simple de caché en memoria. Mantiene objetos en la memoria si hay suficiente memoria. Si hay muy poca memoria, SoftReference libera objetos.

  6. También puedo recomendarle usar LinkedHashMap para caché en memoria. Tiene un constructor especial para iterar los elementos en el orden en que se accedieron por última vez a sus entradas. Por lo tanto, cuando tiene más de 15 elementos en la memoria caché, puede quitar los elementos que se accedieron recientemente. Como dice la documentación:

    Este tipo de mapa es muy adecuado para construir cachés LRU.

  7. Sabes que mi implementación fue diseñada con pequeñas imágenes en mente, algo así como 50 * 50. Si tienes imágenes más grandes debes pensar en la cantidad de memoria que consumen. Si toman demasiado usted podría apenas ponerlos en caché a la tarjeta del SD pero no a la memoria. El rendimiento puede ser más lento, pero OOM no sería un problema más.

  8. No relacionado con OOM. Puedo ver que usted llama a clearCache () en onLowMemory (). No es bueno porque clearCache () también elimina la caché de SD. Sólo debe borrar la memoria caché en la memoria caché no SD.

He visto que por debajo de la línea se comenta en su código, en primer lugar, descomente esa línea plz.

 //o2.inPurgeable=true; 

En el caso NonPurgeable, un flujo de bits codificado se decodifica a un mapa de bits diferente una y otra vez hasta que se produce memoria insuficiente. En el caso Purgeable, la memoria asignada por una imagen será compartida por cualquier imagen nueva cuando sea necesario y más tarde en cualquier momento si la referencia de imagen más antigua es activada en ese caso el sistema operativo administrará la referencia de memoria a sí mismo utilizando un espacio de otra imagen y Vice verce y de esta manera siempre evitar el error de memoria.

Si incluso tienes un caso purgable y aún frente a esa fuga de memoria, a continuación, utilice a continuación tratar de bloquear el blog para rastrear el error y hágamelo saber el Detalle de Traza de la pila.

  try{ //your code to download or decode image }catch(Error e){ //Print the stack trace } 

Parece que está creando bitmaps, pero nunca llamando a Bitmap.recycle ().

EDIT: Para elaborar, Bitmap.recycle () libera la memoria que se está utilizando actualmente para almacenar el Bitmap. Puesto que los mapas de bits creados por BitmapFactory son inmutables, puedes ver esto con Bitmap.isMutable () en tus mapas de bits recién creados), simplemente quitando una referencia a ellos de la tabla hash y esperar en el recolector no es suficiente para liberar memoria.

Call Bitmap Recicle cada vez que haya terminado con un mapa de bits específico (como en el método "limpio" de su photoQueue o clearCache ()).

FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.