47

Have anyone worked on DiskLruCache?

http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

provides an example of using this caching method. But i observe the implementation for DiskLruCache in 4.0 source code does not have defined methods. Any idea how to use this?

Pavankumar Vijapur
  • 1,156
  • 2
  • 10
  • 15

3 Answers3

119

You can use Picasso as image loader.

Or

This is what I did:

I created a class named DiskLruImageCache with a DiskLruCache (the one from Jake Wharton) object and the same methods in the simple implementation on the dev guide (displaying bitmaps efficiently):

public class DiskLruImageCache {

    private DiskLruCache mDiskCache;
    private CompressFormat mCompressFormat = CompressFormat.JPEG;
    private int mCompressQuality = 70;
    private static final int APP_VERSION = 1;
    private static final int VALUE_COUNT = 1;
    private static final String TAG = "DiskLruImageCache";

    public DiskLruImageCache( Context context,String uniqueName, int diskCacheSize,
        CompressFormat compressFormat, int quality ) {
        try {
                final File diskCacheDir = getDiskCacheDir(context, uniqueName );
                mDiskCache = DiskLruCache.open( diskCacheDir, APP_VERSION, VALUE_COUNT, diskCacheSize );
                mCompressFormat = compressFormat;
                mCompressQuality = quality;
            } catch (IOException e) {
                e.printStackTrace();
            }
    }

    private boolean writeBitmapToFile( Bitmap bitmap, DiskLruCache.Editor editor )
        throws IOException, FileNotFoundException {
        OutputStream out = null;
        try {
            out = new BufferedOutputStream( editor.newOutputStream( 0 ), Utils.IO_BUFFER_SIZE );
            return bitmap.compress( mCompressFormat, mCompressQuality, out );
        } finally {
            if ( out != null ) {
                out.close();
            }
        }
    }

    private File getDiskCacheDir(Context context, String uniqueName) {

    // Check if media is mounted or storage is built-in, if so, try and use external cache dir
    // otherwise use internal cache dir
        final String cachePath =
            Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
                    !Utils.isExternalStorageRemovable() ?
                    Utils.getExternalCacheDir(context).getPath() :
                    context.getCacheDir().getPath();

        return new File(cachePath + File.separator + uniqueName);
    }

    public void put( String key, Bitmap data ) {

        DiskLruCache.Editor editor = null;
        try {
            editor = mDiskCache.edit( key );
            if ( editor == null ) {
                return;
            }

            if( writeBitmapToFile( data, editor ) ) {               
                mDiskCache.flush();
                editor.commit();
                if ( BuildConfig.DEBUG ) {
                   Log.d( "cache_test_DISK_", "image put on disk cache " + key );
                }
            } else {
                editor.abort();
                if ( BuildConfig.DEBUG ) {
                    Log.d( "cache_test_DISK_", "ERROR on: image put on disk cache " + key );
                }
            }   
        } catch (IOException e) {
            if ( BuildConfig.DEBUG ) {
                Log.d( "cache_test_DISK_", "ERROR on: image put on disk cache " + key );
            }
            try {
                if ( editor != null ) {
                    editor.abort();
                }
            } catch (IOException ignored) {
            }           
        }

    }

    public Bitmap getBitmap( String key ) {

        Bitmap bitmap = null;
        DiskLruCache.Snapshot snapshot = null;
        try {

            snapshot = mDiskCache.get( key );
            if ( snapshot == null ) {
                return null;
            }
            final InputStream in = snapshot.getInputStream( 0 );
            if ( in != null ) {
                final BufferedInputStream buffIn = 
                new BufferedInputStream( in, Utils.IO_BUFFER_SIZE );
                bitmap = BitmapFactory.decodeStream( buffIn );              
            }   
        } catch ( IOException e ) {
            e.printStackTrace();
        } finally {
            if ( snapshot != null ) {
                snapshot.close();
            }
        }

        if ( BuildConfig.DEBUG ) {
            Log.d( "cache_test_DISK_", bitmap == null ? "" : "image read from disk " + key);
        }

        return bitmap;

    }

    public boolean containsKey( String key ) {

        boolean contained = false;
        DiskLruCache.Snapshot snapshot = null;
        try {
            snapshot = mDiskCache.get( key );
            contained = snapshot != null;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if ( snapshot != null ) {
                snapshot.close();
            }
        }

        return contained;

    }

    public void clearCache() {
        if ( BuildConfig.DEBUG ) {
            Log.d( "cache_test_DISK_", "disk cache CLEARED");
        }
        try {
            mDiskCache.delete();
        } catch ( IOException e ) {
            e.printStackTrace();
        }
    }

    public File getCacheFolder() {
        return mDiskCache.getDirectory();
    }

}

Utils source code is:

public class Utils {
    public static final int IO_BUFFER_SIZE = 8 * 1024;

    private Utils() {};

    public static boolean isExternalStorageRemovable() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
            return Environment.isExternalStorageRemovable();
        }
        return true;
    }

    public static File getExternalCacheDir(Context context) {
        if (hasExternalCacheDir()) {
            return context.getExternalCacheDir();
        }

        // Before Froyo we need to construct the external cache dir ourselves
        final String cacheDir = "/Android/data/" + context.getPackageName() + "/cache/";
        return new File(Environment.getExternalStorageDirectory().getPath() + cacheDir);
    }

    public static boolean hasExternalCacheDir() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
    }

}

Remember to put

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

on your AndroidManifest.xml

I figured out this way by reading this code and the javadoc from Jake Wharton's DiskLruCache

Platonius
  • 1,368
  • 1
  • 10
  • 11
  • Hi platonius, Thanks for your time and effort. So i can use this for my grid view implementation for diplaying the images in the sdcard and make it work as efficiently as default gallery. Meanwhile have you tried this above piece of implementation anywhere? – Pavankumar Vijapur Apr 20 '12 at 05:09
  • Awesome, So the scroll will be fast enough as well as the memory usage is also taken care right? – Pavankumar Vijapur Apr 20 '12 at 06:28
  • Yes, you can use this with a grid view. I'm using it for my current project following a similar code as the one you can find on the [dev guide](http://developer.android.com/training/displaying-bitmaps/index.html) and the sample code for that lesson. It has been working pretty well. Remember to put READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE on the Android manifest. – Platonius Apr 20 '12 at 06:37
  • I think so. I have run some simple tests on it and it works fast enough. – Platonius Apr 20 '12 at 06:51
  • For some reason, it caches all my images but the last added one. I have went through the code and everything runs correctly, the logger says "image put on disk cache", but as soon as its needed next time, it will not be found. Anybody else faced this problem? – Anuj Aug 18 '12 at 08:37
  • 4
    Figured it out. I need to call close() on the DiskLruCache when my activity finishes. Works fine now. – Anuj Aug 27 '12 at 08:34
  • @RameezHussain It's on the source code for "bitmap fun" on the dev guide: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html – Platonius Oct 08 '12 at 18:52
  • Hi. I don't get where to find the Utils class... can you give more info? – Daniel López Lacalle Oct 30 '12 at 19:46
  • 1
    Sorry, I forgot I used my own Utils class. I'll update the answer with the proper source code. – Platonius Oct 30 '12 at 23:44
  • 1
    I am getting a java.lang.IllegalStateException: edit didn't create file 1. Any idea why? – mipreamble Dec 05 '12 at 14:19
  • 1
    @Platonius Thanks. This has been very helpful. In `put()` you have `mDiskCache.flush()` before `editor.commit()` . Shouldn't it be the other way round? Using your code as it is, leaves the last entry as dirty, and if the app restarts, that cache entry is no longer available. – sajal Dec 31 '12 at 10:48
  • @sajal I think I misunderstood how a lru cache works. I thought it was normal to always lose the last entry since it is the least-recently used by the time the app restarts. UPDATE: I've done some tests and changing the order of flush and commit doesn't seem to change journal's content. Although, if flush means "write whatever we've done with disk cache to journal" then I'm ok with putting commit first and then flush. – Platonius Jan 01 '13 at 22:23
  • flush() will write on your disk (external storage). Therefore calling flush() everytime you put somthing into this cache could leed to performance issues. Normally its enough to call close(), which will call flush(), when you are done (example activity gets destroyed) and then all commited changes will be written to the disk. But what I dont understand is, why a snapshot has an array of InputStreams to read from and not exactly one. The demo code above uses only the first one to read ( snapshot.getInputStream(0); ) and write (editor.newOutputStream(0); ). Does anybody know why? – sockeqwe Jan 05 '13 at 23:29
  • 1
    In your `DiskLruImageCache.getDiskCacheDir()`, would `Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED` be better replaced by `Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())`? This is based on http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html#disk-cache – Pang Jan 28 '13 at 08:36
  • @Pang I think you're right. Environment.MEDIA_MOUNTED is a String, so it should be compared with equals. Thanks, I'll change the code. – Platonius Jan 28 '13 at 16:13
  • I was constantly getting IOException on the mDiskCache.flush(); That call is incorrect since you cannot flush it before commiting the edit. DiskLruCache was failing with "Failed to delete" error. Removing that line fixed the problem. – Felipe Lima Jan 30 '13 at 22:11
  • That's weird. I never have that problem. Have you tried putting `flush()` after `commit()`, as @sajal suggest? – Platonius Jan 31 '13 at 00:46
  • 1
    Hi, I'm using your class to cache Bitmaps, but for some reason all are deleted when you close the app. When it goes throught onPause works great. It deletes everything when It close. How can I avoid that? – MBRebaque Apr 20 '13 at 19:06
  • I am trying to implement the above code with Volley. How do I use this class? Can someone help me out? – Vamsi Challa Feb 19 '14 at 07:45
  • Please post sample code how to use DiskLruImageCache class in Adapter. Many thank. – green.android Mar 13 '14 at 04:59
  • @Platonius `out = new BufferedOutputStream(editor.newOutputStream(0), Utils.IO_BUFFER_SIZE);` in **writeBitmapToFile** is always null? – Muhammad Babar Aug 22 '14 at 06:39
  • Can you please help @Platonius – Muhammad Babar Aug 22 '14 at 06:40
  • Because I thought about implementing something myself: Picasso is just GREAT! Pretty straight forward with quite much functionality. – svennergr Jul 14 '15 at 11:35
  • What about synchronization when writing item to cache? – Jemshit Iskenderov Jul 12 '16 at 12:48
9

You can also use the SimpleDiskCache, which wraps the DiskLruCache with more developer friendly interface.

fhucho
  • 31,862
  • 40
  • 125
  • 178
4

I wrote a library based on this nice piece of code posted by @Platonius. It includes some bug fixes, for example frequent IOExceptions in the put() method with "failed to delete file". It uses both LRU and disk cache: https://github.com/felipecsl/Android-ImageManager

Felipe Lima
  • 9,809
  • 4
  • 38
  • 38