Image loading: memory leak using LazyList

I have a memory leak using LazyList . I use one instance of ImageLoader in the whole application, I create it in Application.onCreate (), because I need to load images into several types of activity: list activity, one action with gallery widgets and activitiy full-screen gallery (they all use the same cache) I modified the image downloader, so it uses HashMap based on SoftReference. Here is the code for 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(); } } 

ImageLoader Class:

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

And my application class, not the best way to do this:

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

And the worst part: after a while, I get an OOM exception when ImageLoader tries to decode another bitmap. I would be grateful for any of your help. Thanks.

EDIT I got rid of the hard cache, but I still get this OOM exception. It seems to me that I am doing something funny. I donโ€™t even know what additional information I should provide ... The images that I download from the server are quite large. And the application cannot allocate appr. 1.5 mb what I see in LogCat. But I just canโ€™t understand why my SoftHashMap doesnโ€™t clear if there is a need for memory ...

+6
source share
4 answers
  • onLowMemory will not help you, since it is not generated when your application runs out of memory, it is called when the Android system wants to have memory for another application or itself before it destroys the processes.
  • I do not see the need for a hard cache - this prevents the processing of the resource. Just leave the drawings in the soft cache - the GC will not collect memory, while the drawings have links that are not soft, so you donโ€™t have to worry about what is currently being processed in the ImageView utility.

Also, how many images do you display on the screen at one time? How big are they?

+1
source
  • Here's a great article on analyzing memory leaks. It can definitely help you. http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html

  • Are you absolutely sure that your implementation of SoftHashMap is working fine? It looks pretty complicated. You can use the debugger to ensure that SoftHashMap never contains more than 15 bitmaps. MAT can also help you determine the number of bitmaps in memory.
    You can also comment on the call to cache.put (photoToLoad.url, bmp). This way you turn off caching in memory to determine if this is the cause of the problem or not.

  • Yes, it could be a leak of activity. You can identify it. If you just look at the same activity and get OOM, it means that something else is not flowing, not the activity. If you stop / start work several times and get OOM, this means that the activity is proceeding. You can also definitely say that the activity is going on or not if you look at the MAT histogram.

  • Since you are using inSampleSize, the size of the image does not matter. It should work fine even with 5mpx images.

  • You can try replacing the implementation of SoftHashMap only with HashMap <String, SoftReference <Bitmap โ†’. Read about SoftReference. This is good for a very simple in-memory cache implementation. It contains objects in memory if there is enough memory. If there is too little memory, the SoftReference frees objects.

  • I can also recommend using LinkedHashMap for in-memory cache. He has a special constuctor for iterating over the elements in the order in which his records were last available. Therefore, when you have more than 15 items in the cache, you can delete the least recently accessed items. As the documentation says:

    This type of card is well suited for creating LRU caches.

  • You know that my implementation was developed taking into account small images, something like 50 * 50. If you have large images, you should think how much memory they consume. If they take too much, you can just cache them on an SD card, but not in memory. Performance may be slower, but OOM will no longer be a problem.

  • Not applicable to OOM. I see that you are calling clearCache () in onLowMemory (). Not good, because clearCache () also removes the cache from SD. You should only clear the cache in memory, not the SD cache.

+1
source

It sounds like you are creating bitmaps, but you never call Bitmap.recycle ().

EDIT: for development, Bitmap.recycle () frees the memory that is currently used to store the bitmap. Since bitmaps created by BitmapFactory are immutable (you can check this with Bitmap.isMutable () on newly created bitmaps), just removing the link to them from the hash table and waiting for the garbage collector is not enough to free up memory.

Call Bitmap Recycle when you are done with a specific bitmap (for example, in the "clean" method of your photoQueue or clearCache ()).

0
source

I saw that below line is commented out in your code, First of All will uncomment this line plz.

 //o2.inPurgeable=true; 

In the case of NonPurgeable, the encoded bitstream is decoded again and again to another bitmap until there is a shortage of memory. In the case of Purgeable. The memory allocated by one image will be shared by any new image whenever it is required, and later at any time, if an older link to the image is activated in this case, the OS will manage the link to the memory on its own, using the space of another image and and, thus, he always avoids errors in memory.

Even if you have a cleaned case and still encounter this memory leak, now use below. Try to catch a blog to track the error and tell me information about the stack trace.

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

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


All Articles