11

I'm implementing an image cache system for caching downloaded image.

My strategy is based upon two-level cache: Memory-level and disk-level.

My class is very similar to the class used in the droidfu project

My downloaded images are put into an hashmap and the Bitmap objet is wrapped inside a SoftRererence object. Also every image is saved permanently to the disk. If a requested image is not found into the Hashmap<String,SoftReference<Bitmap>> it will be searched on the disk, readed, and then pushed back into the hashmap. Otherwise the image will be downloaded from the network. Since I store the images into the phisical device momery, I have added a check for preserve the device space and stay under a 1M of occupied space:

private void checkCacheUsage() {

        long size = 0;
        final File[] fileList = new File(mCacheDirPath).listFiles();
        Arrays.sort(fileList, new Comparator<File>() {
            public int compare(File f1, File f2) {
                return Long.valueOf(f2.lastModified()).compareTo(
                        f1.lastModified());
            }
        });
        for (File file : fileList) {
            size += file.length();
            if (size > MAX_DISK_CACHE_SIZE) {
                file.delete();
                Log.d(ImageCache.class.getSimpleName(),
                        "checkCacheUsage: Size exceeded  " + size + "("
                                + MAX_DISK_CACHE_SIZE + ") wiping older file {"+file.toString()+"}");
            }
        }

    }

This method is called sometime afte a disk writing:

Random r = new Random();
        int ra = r.nextInt(10);

        if (ra % 2 == 0){
            checkCacheUsage();
        }

What I'd like to add is the same check on the HashMap size to prevent it will grow too much. Something like this:

private synchronized void checkMemoryCacheUsage(){

            long size = 0;

            for (SoftReference<Bitmap> a : cache.values()) {

                final Bitmap b = a.get();

                if (b != null && ! b.isRecycled()){
                    size += b.getRowBytes() * b.getHeight();
                }

                if (size > MAX_MEMORY_SIZE){
                  //Remove some elements from the cache
                }

            }

            Log.d(ImageCache.class.getSimpleName(),
                    "checkMemoryCacheUsage: " + size + " in memory");

    }

My question is: What could be a right MAX_MEMORY_SIZE value? Also, Is it a good approach? A good answer also could be: "Don't do it! SoftReference is already enough"

Sagar
  • 3,107
  • 2
  • 26
  • 35
Francesco Laurita
  • 23,434
  • 8
  • 55
  • 63
  • You may also consider this API http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/CacheBuilder.html – carfield Oct 02 '11 at 16:39

3 Answers3

8

Don't do it! SoftReference is already enough! Actually SoftReference is designed to do exactly what you need. Sometimes SoftReference doesn't do what you need. Then you just get rid of SoftReference and write your own memory management logic. But as far as you use SoftReference you should not be worried about memory consumption, SoftReference does it for you.

Fedor
  • 43,261
  • 10
  • 79
  • 89
  • Android implements soft references in a way that makes it unsuitable for this kind of caching. See this bug report for more information: http://code.google.com/p/android/issues/detail?id=20015&can=1&q=softReference&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars. The images will be removed from memory at the very moment they are only reachable through a SoftReference. The Google team recommends the use of a LRU Cache. The problem here is to determine the maximum size of the cache. – Janusz Jan 21 '12 at 18:33
  • @Janusz, That's very true. But as there's no LRU cache implementation that every beginner can take and reuse they have to start with SoftReference. It's not perfect (I'm aware of issue you're talking about) but it's good enough to start with. – Fedor Jan 22 '12 at 06:26
  • I don't think that it is goog enough to start with. With the speed the memory is freed at the moment soft references are useless and cause a lot of confusion. There is absolutely nothing to gain using them. – Janusz Jan 23 '12 at 07:45
  • @Janusz Yeah probably you're right. Then we should figure out a very basic approach that we can recommend to beginners. – Fedor Jan 23 '12 at 11:07
  • fyi - came across this today, untested yet. http://blog.wu-man.com/2012/01/lrucache-with-softreference-on-android.html – Mathias Conradt Mar 08 '12 at 16:43
  • 1
    Who is asking a solution "for beginners"? I think the question is about a correct solution. if the system will remove the images only because they are not referenced by others than the cache, this is not a correct solution. – User Jan 02 '13 at 16:25
1

I am using one-third of the heap for Image cache.

int memoryInMB = activityManager.getMemoryClass();
long totalAppHeap = memoryInMB * 1024 * 1024;
int runtimeCacheLimit =  (int)totalAppHeap/3;

By the way, about soft reference, in Android Soft references do not work as you expect. There is a platform issue that soft references are collected too early, even when there is plenty of memory free.

Check http://code-gotcha.blogspot.com/2011/09/softreference.html

Mazed
  • 499
  • 4
  • 4
0

I've been looking into different caching mechanisms for my scaled bitmaps, both memory and disk cache examples. The examples where to complex for my needs, so I ended up making my own bitmap memory cache using LruCache. You can look at a working code-example here or use this code:

Memory Cache:

public class Cache {
    private static LruCache<Integer, Bitmap> bitmaps = new BitmapLruCache();

    public static Bitmap get(int drawableId){
        Bitmap bitmap = bitmaps.get(drawableId);
        if(bitmap != null){
            return bitmap;  
        } else {
            bitmap = SpriteUtil.createScaledBitmap(drawableId);
            bitmaps.put(drawableId, bitmap);
            return bitmap;
        }
    }
}

BitmapLruCache:

public class BitmapLruCache extends LruCache<Integer, Bitmap> {
    private final static int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    private final static int cacheSize = maxMemory / 2;

    public BitmapLruCache() {
        super(cacheSize);
    }

    @Override
    protected int sizeOf(Integer key, Bitmap bitmap) {
        // The cache size will be measured in kilobytes rather than number of items.
        return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
    }
}
Jan-Terje Sørensen
  • 14,468
  • 8
  • 37
  • 37