package at.favre.lib.dali.builder; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.support.v4.util.LruCache; import android.util.Log; import com.jakewharton.disklrucache.DiskLruCache; import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; import java.io.OutputStream; import at.favre.lib.dali.BuildConfig; import at.favre.lib.dali.util.BenchmarkUtil; import at.favre.lib.dali.util.BuilderUtil; import at.favre.lib.dali.util.LegacySDKUtil; import at.favre.lib.dali.util.Precondition; /** * A simple tow level cache with a * a fast memory and a slow disk layer. */ public class TwoLevelCache { private static final String TAG = TwoLevelCache.class.getSimpleName(); private static final int DISK_CACHE_SIZE_BYTE = 1024*1024*10; private static final int MEMORY_CACHE_SIZE_FACTOR = 10; private static final String DISK_CACHE_FOLDER_NAME = "dali_diskcache"; private static final int IO_BUFFER_SIZE_BYTE = 1024 * 8; private static final Bitmap.CompressFormat FORMAT = Bitmap.CompressFormat.PNG; private DiskLruCache diskLruCache; private BitmapLruMemoryCache memoryCache; private Context ctx; private boolean useMemoryCache; private boolean useDiskCache; private boolean debugMode; private int diskCacheSizeByte; private String diskCacheFolderName; private int memoryCacheSizeByte; public TwoLevelCache(Context ctx) { this.ctx = ctx.getApplicationContext(); this.useMemoryCache = true; this.useDiskCache = true; this.debugMode = false; this.diskCacheSizeByte = DISK_CACHE_SIZE_BYTE; this.diskCacheFolderName = DISK_CACHE_FOLDER_NAME; this.memoryCacheSizeByte = (int) Runtime.getRuntime().maxMemory() / MEMORY_CACHE_SIZE_FACTOR; } public TwoLevelCache(Context ctx, boolean useMemoryCache, boolean useDiskCache, int diskCacheSizeByte, String diskCacheFolderName, int memoryCacheSizeByte, boolean debugMode) { this.ctx = ctx; this.useMemoryCache = useMemoryCache; this.useDiskCache = useDiskCache; this.debugMode = debugMode; this.diskCacheSizeByte = diskCacheSizeByte; this.diskCacheFolderName = diskCacheFolderName; this.memoryCacheSizeByte = memoryCacheSizeByte; } public DiskLruCache getDiskCache() { if (diskLruCache == null) { try { diskLruCache = DiskLruCache.open(new File(LegacySDKUtil.getCacheDir(ctx), diskCacheFolderName), BuildConfig.VERSION_CODE, 1, diskCacheSizeByte); }catch (Exception e) { Log.e(TAG, "Could not create disk cache", e); } } return diskLruCache; } public BitmapLruMemoryCache getMemoryCache() { if(memoryCache == null) { memoryCache = new BitmapLruMemoryCache(memoryCacheSizeByte,debugMode); } return memoryCache; } public Bitmap get(String cacheKey) { Bitmap cache = null; if(useMemoryCache) { if ((cache = getFromMemoryCache(cacheKey)) != null) { BuilderUtil.logVerbose(TAG, "found in memory cache (key: " + cacheKey + ")", debugMode); return cache; } } if(useDiskCache) { if ((cache = getFromDiskCache(cacheKey)) != null) { if(useMemoryCache) { putBitmapToMemoryCache(cache, cacheKey); } BuilderUtil.logVerbose(TAG, "found in disk cache (key: " + cacheKey + ")", debugMode); } } return cache; } public boolean putInCache(Bitmap bitmap, String cacheKey) { boolean memoryResult=false, diskresult=false; if(useMemoryCache) { memoryResult = putBitmapToMemoryCache(bitmap, cacheKey); } if(useDiskCache) { diskresult = putBitmapToDiskCache(bitmap, cacheKey); } BuilderUtil.logVerbose(TAG, "could put in memoryCache: " + memoryResult + ", could put in disk cache: " + diskresult + " (key: " + cacheKey + ")", debugMode); return (memoryResult ||!useMemoryCache) && (diskresult || !useDiskCache); } public Bitmap getFromMemoryCache(String cacheKey) { Precondition.checkArgument("memory cache disabled",useMemoryCache); if(getMemoryCache() != null) { return getMemoryCache().get(cacheKey); } return null; } public boolean putBitmapToMemoryCache(Bitmap bitmap,String cacheKey) { Precondition.checkArgument("memory cache disabled", useMemoryCache); if (getMemoryCache() != null) { try { getMemoryCache().put(cacheKey, bitmap); } catch (Throwable t) { Log.e(TAG, "Could not put to memory cache", t); } } return false; } public Bitmap getFromDiskCache(String cacheKey) { Precondition.checkArgument("disk cache disabled",useDiskCache); if(getDiskCache() != null) { try { DiskLruCache.Snapshot snapshot = getDiskCache().get(cacheKey); if (snapshot != null) { return BitmapFactory.decodeStream(snapshot.getInputStream(0)); } } catch (IOException e) { Log.w(TAG, "Could not read from disk cache", e); } } return null; } public boolean putBitmapToDiskCache(Bitmap bitmap, String cacheKey) { Precondition.checkArgument("disk cache disabled",useDiskCache); if(getDiskCache() != null) { OutputStream out = null; try { DiskLruCache.Editor editor =getDiskCache().edit(cacheKey); if(editor != null) { out = new BufferedOutputStream(editor.newOutputStream(0), IO_BUFFER_SIZE_BYTE); if(bitmap.compress(FORMAT, 100, out)) { editor.commit(); return true; } else { Log.w(TAG,"Could not compress png for disk cache"); editor.abort(); } } } catch (Exception e) { Log.w(TAG,"Could not write outputstream for disk cache",e); } finally { if (out != null) { try { out.close(); } catch (IOException e) { Log.w(TAG,"Could not close outputstream while writing cache",e); } } } } return false; } public synchronized void clear() { clearMemoryCache(); clearDiskCache(); } public synchronized void clearMemoryCache() { if(memoryCache != null) { memoryCache.evictAll(); memoryCache = null; } } public synchronized void clearDiskCache() { if(diskLruCache != null) { try { diskLruCache.delete(); } catch (IOException e) { Log.w(TAG,"Could not clear diskcache",e); } diskLruCache = null; } } /** * Removes the value connected to the given key * from all levels of the cache. Will not throw an * exception on fail. * * @param cacheKey */ public void purge(String cacheKey) { try { if (useMemoryCache) { if (memoryCache != null) { memoryCache.remove(cacheKey); } } if (useDiskCache) { if (diskLruCache != null) { diskLruCache.remove(cacheKey); } } } catch(Exception e){ Log.w(TAG, "Could not remove entry in cache purge", e); } } private static class BitmapLruMemoryCache extends LruCache<String,Bitmap> { /** * @param maxSizeInBytes for caches that do not override {@link #sizeOf}, this is * the maximum number of entries in the cache. For all other caches, * this is the maximum sum of the sizes of the entries in this cache. */ public BitmapLruMemoryCache(int maxSizeInBytes, boolean debugMode) { super(maxSizeInBytes); BuilderUtil.logDebug(TAG, "Create memory cache with " + BenchmarkUtil.getScalingUnitByteSize(maxSizeInBytes),debugMode); } @Override protected int sizeOf(String key, Bitmap bitmap) { // The cache size will be measured in bytes rather than number of items. return LegacySDKUtil.byteSizeOf(bitmap); } } }