1. 程式人生 > >Volley ImageLoader Memory-Disk兩級Cache的實現

Volley ImageLoader Memory-Disk兩級Cache的實現

暮鼓集    行走集

原作於2017年01月01日

Volley的ImageLoader可以自定義快取機制,但是官方只給出了Memory Cache的實現,這樣,一旦應用被關閉,Cache Memory被系統回收,下次再開啟時,仍然需要請求網路來重新獲取。 在實際開發中,很多應用是希望將快取持久化到Disk中。但是,單純的將Memory Cache修改為Disk Cache,會帶來效率的問題,而Memory-Disk兩級快取可以有效解決。

實現的思路是使用LruCache構建Memory Cache,使用DiskLruImageCache實現Disk Cache。 在寫快取時,同時寫入到Memory與Disk中, 在讀快取時,先在Memory中檢索,如果沒有命中再在Disk中檢索。

具體實現如下,包括三個檔案。

ImageCache.java

public class ImageCache implements ImageLoader.ImageCache{

    private static final int DEFAULT_MAX_MEM_ENTRIES = 50;
    private static final int DEFAULT_MAX_DISK_SIZE = 20 * 1024 * 1024;

    private LruCache<String, Bitmap> mLruCache;  // L1 Cache
    private DiskLruImageCache mDiskLruCache; // L2 Cache

    public ImageCache( int maxMemEntries, String cacheDir, int maxDiskSize  ){
        mLruCache = new LruCache<String, Bitmap>(maxMemEntries);
        mDiskLruCache = new DiskLruImageCache(cacheDir, maxDiskSize, Bitmap.CompressFormat.JPEG, 70);
    }

    @Override
    public Bitmap getBitmap(String url) {
        Bitmap bmp = mLruCache.get(url);
        if( bmp != null )
            return bmp;

        bmp = mDiskLruCache.get(key(url));

        return bmp;
    }

    @Override
    public void putBitmap(String url, Bitmap bitmap) {

        if( bitmap == null )
            return;

        mLruCache.put(url, bitmap);

        mDiskLruCache.put(key(url), bitmap);;
    }


    public String key(String url) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(url.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(url.hashCode());
        }
        return cacheKey;
    }

    private String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }
}

DiskLruImageCache.java

public class DiskLruImageCache {

    private DiskLruCache mDiskCache;
    private CompressFormat mCompressFormat = CompressFormat.JPEG;
    private static int IO_BUFFER_SIZE = 8 * 1024;
    private int mCompressQuality = 70;
    private static final int APP_VERSION = 1;
    private static final int VALUE_COUNT = 1;

    public DiskLruImageCache(String cacheDir, int diskCacheSize,
                             CompressFormat compressFormat, int quality) {
        try {
            final File diskCacheDir = new File(cacheDir);
            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), IO_BUFFER_SIZE);
            return bitmap.compress(mCompressFormat, mCompressQuality, out);
        } finally {
            if (out != null) {
                out.close();
            }
        }
    }

    private File getDiskCacheDir(Context context, String uniqueName) {

        final String cachePath = 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 get(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, 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;

    }
}

DiskLruCache.java

官方連結

https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java