Yet another image download question
<p>I know that this was disscused many times before, but I've got memory leak using <a href="http://stackoverflow.com/questions/541966/android-how-do-i-do-a-lazy-load-of-images-in-listview/3068012#3068012">LazyList</a> and I've got no ideas how to fix it.
I use one instance of ImageLoader in whole app, I create it in Application.onCreate(), because I need image downloading in several activities: list activity, one activity with gallery widget and full-screen gallery activitiy(all of them use same cache)
I modified image loader so it uses SoftReference-based HashMap. Here's code for SoftHashMap:</p>

<pre><code>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()&gt;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();
    }

}
</code></pre>

<p>ImageLoader class:</p>

<pre><code>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&lt;=mWidth || height_tmp/2&lt;=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&lt;PhotoToLoad&gt; photosToLoad=new Stack&lt;PhotoToLoad&gt;(); 

         public void Clean(ImageView image)
            {
                for(int j=0 ;j&lt;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 &amp;&amp; ((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();
        }
}
</code></pre>

<p>And my Application class, not the best way to do it, though:</p>

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

<p>And the worst part: after some time I receive OOM exception when ImageLoader tries to decode another bitmap.
I'll appreciate any your help. Thanks.</p>

<p><strong>EDIT</strong> I've got rid of hard cache, but i still get this OOM exception. It seems to me that I'm doing smth funny. I don't even know what extra information should I provide...
The images which i download from server are pretty big, though. And app fails to allocate appr. 1.5 mb, that's what I see in LogCat. But I just can't figure out why doesn't vm clear my SoftHashMap if there is need for memory...</p>
