Android Out of Memory error with lazy loading images

I found Fedor code here and embedded it in my project. The only difference is that my application does not have a list view, rather, I get one image at a time from the server. When the action starts, I call "DisplayImage (...)" to show the first image. Then there are 2 buttons (previous / next) that, when clicked on, call "DisplayImage (...)".

It works fine, but then I get an error from memory. At the top of his code, he comments that you can use SoftReference. I guess this will fix my problem, right? I played with it a bit, but when I tried to change it to use SoftReference, images never load. I have never used SoftReference, so I think I just missed something. How do I change this code (ImageLoader) to fix my OOM error? Is there a better way to cache images when viewing them?

UPDATE: Here is the code if you do not want to view other files in the source.

package com.fedorvlasov.lazylist; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.util.HashMap; import java.util.Stack; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.widget.ImageView; public class ImageLoader { //the simplest in-memory cache implementation. This should be replaced with something like SoftReference or BitmapOptions.inPurgeable(since 1.6) private HashMap<String, Bitmap> cache=new HashMap<String, Bitmap>(); private File cacheDir; public ImageLoader(Context context){ //Make the background thead low priority. This way it will not affect the UI performance photoLoaderThread.setPriority(Thread.NORM_PRIORITY-1); //Find the dir to save cached images if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) cacheDir=new File(android.os.Environment.getExternalStorageDirectory(),"LazyList"); else cacheDir=context.getCacheDir(); if(!cacheDir.exists()) cacheDir.mkdirs(); } final int stub_id=R.drawable.stub; public void DisplayImage(String url, Activity activity, ImageView imageView) { if(cache.containsKey(url)) imageView.setImageBitmap(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 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){ try { //decode image size BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; BitmapFactory.decodeStream(new FileInputStream(f),null,o); //Find the correct scale value. It should be the power of 2. final int REQUIRED_SIZE=70; int width_tmp=o.outWidth, height_tmp=o.outHeight; int scale=1; while(true){ if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE) break; width_tmp/=2; height_tmp/=2; scale*=2; } //decode with inSampleSize BitmapFactory.Options o2 = new BitmapFactory.Options(); o2.inSampleSize=scale; return BitmapFactory.decodeStream(new FileInputStream(f), null, o2); } catch (FileNotFoundException e) {} return null; } //Task for the queue private 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(); } //stores list of photos to download class PhotosQueue { private Stack<PhotoToLoad> photosToLoad=new Stack<PhotoToLoad>(); //removes all instances of this ImageView 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(); //Used to display bitmap in the UI thread 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(); } } 

Here is the same class after I tried to implement SoftReference. I don’t think I did it right, because after loading the image the screen doesn’t appear.

 package com.mycompany.myapp; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.lang.ref.SoftReference; import java.net.URL; import java.util.HashMap; import java.util.Stack; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.widget.ImageView; public class ImageLoader { //the simplest in-memory cache implementation. This should be replaced with something like SoftReference or BitmapOptions.inPurgeable(since 1.6) private HashMap<String, SoftReference<Bitmap>> cache=new HashMap<String, SoftReference<Bitmap>>(); private File cacheDir; public ImageLoader(Context context){ //Make the background thread low priority. This way it will not affect the UI performance photoLoaderThread.setPriority(Thread.NORM_PRIORITY-1); //Find the dir to save cached images if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) cacheDir=new File(android.os.Environment.getExternalStorageDirectory(),"MyApp/Temp"); else cacheDir=context.getCacheDir(); if(!cacheDir.exists()) cacheDir.mkdirs(); } final int stub_id = R.drawable.loading; public void DisplayImage(String url, Activity activity, ImageView imageView) { if(cache.containsKey(url)){ imageView.setImageBitmap(null); System.gc(); imageView.setImageBitmap(cache.get(url).get()); } else { queuePhoto(url, activity, imageView); imageView.setImageBitmap(null); System.gc(); 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 not started yet if(photoLoaderThread.getState()==Thread.State.NEW) photoLoaderThread.start(); } private SoftReference<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 SoftReference<Bitmap> b = decodeFile(f); if(b!=null) return b; //from web try { SoftReference<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 SoftReference<Bitmap> decodeFile(File f){ try { //decode image size BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; BitmapFactory.decodeStream(new FileInputStream(f),null,o); //Find the correct scale value. It should be the power of 2. final int REQUIRED_SIZE=1024; int width_tmp=o.outWidth, height_tmp=o.outHeight; int scale=1; while(true){ if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE) break; width_tmp/=2; height_tmp/=2; scale*=2; } //decode with inSampleSize BitmapFactory.Options o2 = new BitmapFactory.Options(); o2.inSampleSize=scale; return cache.get(BitmapFactory.decodeStream(new FileInputStream(f), null, o2)); } catch (FileNotFoundException e) {} return null; } //Task for the queue private 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(); } //stores list of photos to download class PhotosQueue { private Stack<PhotoToLoad> photosToLoad=new Stack<PhotoToLoad>(); //removes all instances of this ImageView 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(); } SoftReference<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(); //Used to display bitmap in the UI thread class BitmapDisplayer implements Runnable { SoftReference<Bitmap> bitmap; ImageView imageView; public BitmapDisplayer(SoftReference<Bitmap> bmp, ImageView i){bitmap=bmp;imageView=i;} public void run() { if(bitmap!=null){ imageView.setImageBitmap(null); System.gc(); imageView.setImageBitmap(bitmap.get()); } else { imageView.setImageBitmap(null); System.gc(); 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(); } } 
+4
source share
5 answers

Typically, your in-memory image cache declaration should look something like this:

 private static HashMap<String, SoftReference<Bitmap>> cache = new HashMap<String, SoftReference<Bitmap>>(); 

Please note that OOM issues are not necessarily related to the Bitmap cache. Always make sure you stop / interrupt the stream created by your image loader in your Activity onDestroy method or in finalizing any other class that controls it.

Use the Eclipse Memory analyzer tool with DDMS to analyze your application memory usage: http://www.eclipse.org/mat/

+7
source

To fix my OOM with Lazy Loader, I manually called garbage collection (System.gc ();) every time I create or destroy a page (maybe doing one or the other will be fine), because Android does not always call it like that often, as needed, and use the ClearCache Fedor method:

 if (ImageLoader.getImageCache() != null) ImageLoader.clearCache(); 

There is a function, but it is never called by anything if you do not use it. I do this in my onCreate second action, which uses Lazy Load. You can also do:

 @Override public void onLowMemory() { super.onLowMemory(); ImageLoader.clearCache(); } 

It doesn't seem to have caused me, but with how big your images are, you might need to. Also consider trimming images if you have no reason to keep them so large.

0
source

i am facing the same problem and i am using SoftReference for bitmap and url, but it seems that Dalvik VM quickly fixes SoftReference and my ListView images continue to flicker and then I tried inPurgeable = true and I OutOfMemoryException and mine listview images did not flicker.

0
source

consider using the memory allocated for the bitmap

bitmap.recyle (); raster = NULL;

calling this function when you no longer need a bitmap, it will free some memory

0
source

You must change the next line in Android 2.1 or lower.

 cacheDir=new File(android.os.Environment.getExternalStorageDirectory(),"MyApp/Temp"); 

to that

 cacheDir = new File(context.getCacheDir(), "MyApp/Temp"); 
-1
source

Source: https://habr.com/ru/post/1340842/


All Articles