/* Copyright (c) 2009 Matthias Käppler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.droidfu.imageloader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.Collection; import java.util.Map; import java.util.Set; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Bitmap.CompressFormat; import com.google.common.collect.MapMaker; /** * <p> * A simple 2-level cache for bitmap images consisting of a small and fast * in-memory cache (1st level cache) and a slower but bigger disk cache (2nd * level cache). For second level caching, the application's cache directory * will be used. Please note that Android may at any point decide to wipe that * directory. * </p> * <p> * When pulling from the cache, it will first attempt to load the image from * memory. If that fails, it will try to load it from disk. If that succeeds, * the image will be put in the 1st level cache and returned. Otherwise it's a * cache miss, and the caller is responsible for loading the image from * elsewhere (probably the Internet). * </p> * <p> * Pushes to the cache are always write-through (i.e., the image will be stored * both on disk and in memory). * </p> * * @author Matthias Kaeppler */ public class ImageCache implements Map<String, Bitmap> { private int cachedImageQuality = 75; // private int firstLevelCacheSize = 10; private String secondLevelCacheDir; private Map<String, Bitmap> cache; private CompressFormat compressedImageFormat = CompressFormat.JPEG; public ImageCache(Context context, int initialCapacity, int concurrencyLevel) { this.cache = new MapMaker().initialCapacity(initialCapacity).concurrencyLevel( concurrencyLevel).weakValues().makeMap(); this.secondLevelCacheDir = context.getApplicationContext().getCacheDir() + "/droidfu/imagecache"; new File(secondLevelCacheDir).mkdirs(); } // /** // * Changing the in-memory cache size will invalidate the cache if new // value // * < old value. // * // * @param firstLevelCacheSize // * maximum number of objects that should be held in memory at any // * time // */ // public void setFirstLevelCacheSize(int firstLevelCacheSize) { // this.firstLevelCacheSize = firstLevelCacheSize; // // invalidate the cache // if (firstLevelCacheSize < this.firstLevelCacheSize) { // this.clear(); // } // } // // public int getFirstLevelCacheSize() { // return firstLevelCacheSize; // } /** * @param cachedImageQuality * the quality of images being compressed and written to disk (2nd * level cache) as a number in [0..100] */ public void setCachedImageQuality(int cachedImageQuality) { this.cachedImageQuality = cachedImageQuality; } public int getCachedImageQuality() { return cachedImageQuality; } public synchronized Bitmap get(Object key) { String imageUrl = (String) key; Bitmap bitmap = cache.get(imageUrl); if (bitmap != null) { // 1st level cache hit (memory) return bitmap; } File imageFile = getImageFile(imageUrl); if (imageFile.exists()) { // 2nd level cache hit (disk) bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath()); if (bitmap == null) { // treat decoding errors as a cache miss return null; } cache.put(imageUrl, bitmap); return bitmap; } // cache miss return null; } public Bitmap put(String imageUrl, Bitmap image) { File imageFile = getImageFile(imageUrl); try { imageFile.createNewFile(); FileOutputStream ostream = new FileOutputStream(imageFile); image.compress(compressedImageFormat, cachedImageQuality, ostream); ostream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return cache.put(imageUrl, image); } public void putAll(Map<? extends String, ? extends Bitmap> t) { throw new UnsupportedOperationException(); } public boolean containsKey(Object key) { return cache.containsKey(key); } public boolean containsValue(Object value) { return cache.containsValue(value); } public Bitmap remove(Object key) { return cache.remove(key); } public Set<String> keySet() { return cache.keySet(); } public Set<java.util.Map.Entry<String, Bitmap>> entrySet() { return cache.entrySet(); } public int size() { return cache.size(); } public boolean isEmpty() { return cache.isEmpty(); } public void clear() { cache.clear(); } public Collection<Bitmap> values() { return cache.values(); } private File getImageFile(String imageUrl) { String fileName = Integer.toHexString(imageUrl.hashCode()) + "." + compressedImageFormat.name(); return new File(secondLevelCacheDir + "/" + fileName); } }