package cn.trinea.android.common.service.impl; import java.io.File; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Environment; import android.view.View; import cn.trinea.android.common.entity.CacheObject; import cn.trinea.android.common.service.CacheFullRemoveType; import cn.trinea.android.common.service.FileNameRule; import cn.trinea.android.common.service.impl.ImageMemoryCache.OnImageCallbackListener; import cn.trinea.android.common.util.FileUtils; /** * <strong>Image Cache</strong><br/> * <br/> * It's a cache with primary cache and secondary cache. It's a combination of {@link ImageMemoryCache} and * {@link ImageSDCardCache}. It applies to apps those used much images, like sina weibo, twitter, taobao, huaban, weixin * and so on.<br/> * <ul> * <strong>Setting and Usage</strong> * <li>Use one of constructors in sections II to init cache</li> * <li>{@link ImageMemoryCache#setOnImageCallbackListener(OnImageCallbackListener)} set callback interface after image * get success</li> * <li>{@link ImageMemoryCache#get(String, java.util.List, android.view.View)} get image asynchronous and preload other images asynchronous * according to urlList</li> * <li>{@link ImageMemoryCache#get(String, android.view.View)} get image asynchronous</li> * <li>{@link #initData(android.content.Context, String)} or {@link #loadDataFromDb(android.content.Context, String)} to init data when app start, * {@link #saveDataToDb(android.content.Context, String)} to save data when app exit</li> * <li>{@link #setHttpReadTimeOut(int)} set http read image time out, if less than 0, not set. default is not set</li> * <li>{@link PreloadDataCache#setContext(android.content.Context)} and {@link #setAllowedNetworkTypes(int)} restrict the types of * networks over which this data can get.</li> * <li>{@link ImageMemoryCache#setOpenWaitingQueue(boolean)} set whether open waiting queue, default is true. If true, * save all view waiting for image loaded, else only save the newest one</li> * <li>{@link PreloadDataCache#setOnGetDataListener(OnGetDataListener)} set how to get image, this cache will get image * and preload images by it</li> * <li>{@link SimpleCache#setCacheFullRemoveType(CacheFullRemoveType)} set remove type when primary cache is full</li> * <li>{@link #setCacheFullRemoveTypeOfSecondaryCache(CacheFullRemoveType)} set remove type when secondary cache is full * </li> * </ul> * <ul> * <strong>Constructor</strong> * <li>{@link #ImageCache()}</li> * <li>{@link #ImageCache(int)}</li> * <li>{@link #ImageCache(int, int)}</li> * <li>{@link #ImageCache(int, int, int, int)}</li> * </ul> * <ul> * <strong>Attentions</strong> * <li>You should add <strong>android.permission.WRITE_EXTERNAL_STORAGE</strong> in manifest, to store image to sdcard.</li> * <li>You should add <strong>android.permission.ACCESS_NETWORK_STATE</strong> in manifest if you get image from * network.</li> * </ul> * * @author <a href="http://www.trinea.cn" target="_blank">Trinea</a> 2013-10-18 */ public class ImageCache extends ImageMemoryCache { private static final long serialVersionUID = 1L; private ImageSDCardCache secondaryCache; /** image compress size **/ private int compressSize = 1; /** compress listener, advanced image compress, will override compressSize **/ private CompressListener compressListener; /** cache folder path which be used when saving images **/ public static final String DEFAULT_CACHE_FOLDER = new StringBuilder() .append(Environment.getExternalStorageDirectory() .getAbsolutePath()).append(File.separator) .append("Trinea").append(File.separator) .append("AndroidCommon").append(File.separator) .append("ImageCache").toString(); /** * <ul> * <li>max size of primary cache is {@link ImageMemoryCache#DEFAULT_MAX_SIZE}, max size of secondary cache is * {@link ImageSDCardCache#DEFAULT_MAX_SIZE}</li> * <li>thread pool size of primary cache and secondary cache both are * {@link PreloadDataCache#DEFAULT_THREAD_POOL_SIZE}</li> * </ul> * * @see {@link #ImageCache(int, int, int, int)} */ public ImageCache() { this(ImageMemoryCache.DEFAULT_MAX_SIZE, PreloadDataCache.DEFAULT_THREAD_POOL_SIZE, ImageSDCardCache.DEFAULT_MAX_SIZE, PreloadDataCache.DEFAULT_THREAD_POOL_SIZE); } /** * <ul> * <li>max size of secondary cache is {@link ImageSDCardCache#DEFAULT_MAX_SIZE}</li> * <li>thread pool size of primary cache and secondary cache both are * {@link PreloadDataCache#DEFAULT_THREAD_POOL_SIZE}</li> * </ul> * * @param primaryCacheMaxSize * @param secondaryCacheMaxSize * @see {@link #ImageCache(int, int, int, int)} */ public ImageCache(int primaryCacheMaxSize) { this(primaryCacheMaxSize, PreloadDataCache.DEFAULT_THREAD_POOL_SIZE, ImageSDCardCache.DEFAULT_MAX_SIZE, PreloadDataCache.DEFAULT_THREAD_POOL_SIZE); } /** * thread pool size of primary cache and secondary cache both are {@link PreloadDataCache#DEFAULT_THREAD_POOL_SIZE} * * @param primaryCacheMaxSize * @param secondaryCacheMaxSize * @see {@link #ImageCache(int, int, int, int)} */ public ImageCache(int primaryCacheMaxSize, int secondaryCacheMaxSize) { this(primaryCacheMaxSize, PreloadDataCache.DEFAULT_THREAD_POOL_SIZE, secondaryCacheMaxSize, PreloadDataCache.DEFAULT_THREAD_POOL_SIZE); } /** * <ul> * <li>Callback interface after image get success is null, can set by * {@link PreloadDataCache#setOnImageCallbackListener(OnImageCallbackListener)}</li> * <li>Get data listener of primary cache is {@link #getOnGetImageListenerOfPrimaryCache()}, you can set by * {@link #setOnGetImageListenerOfPrimaryCache(OnGetDataListener)}, but not recommended, you may destory secondary * cache.</li> * <li>Get data listener of secondary cache is {@link #getOnGetImageListenerOfSecondaryCache()}, you can set by * {@link #setOnGetImageListenerOfSecondaryCache(OnGetDataListener)}.</li> * <li>Elements of the cache will not invalid</li> * <li>Remove type of primary cache is {@link RemoveTypeUsedCountSmall} when cache is full</li> * </ul> * * @param primaryCacheMaxSize maximum size of the primary cache * @param primaryCacheThreadPoolSize getting data thread pool size of the primary cache * @param secondaryCacheMaxSize maximum size of the secondary cache * @param secondaryCacheThreadPoolSize getting data thread pool size of the secondary cache */ public ImageCache(int primaryCacheMaxSize, int primaryCacheThreadPoolSize, int secondaryCacheMaxSize, int secondaryCacheThreadPoolSize) { super(primaryCacheMaxSize, primaryCacheThreadPoolSize); setOnGetDataListener(new OnGetDataListener<String, Bitmap>() { private static final long serialVersionUID = 1L; @Override public CacheObject<Bitmap> onGetData(String key) { try { CacheObject<String> object = secondaryCache.get(key); String imagePath = (object == null ? null : object.getData()); if (FileUtils.isFileExist(imagePath)) { if (compressListener != null) { compressSize = compressListener.getCompressSize(imagePath); } Bitmap bm; if (compressSize > 1) { BitmapFactory.Options option = new BitmapFactory.Options(); option.inSampleSize = compressSize; bm = BitmapFactory.decodeFile(imagePath, option); } else { bm = BitmapFactory.decodeFile(imagePath); } return (bm == null ? null : new CacheObject<Bitmap>(bm)); } else { secondaryCache.remove(key); } } catch (OutOfMemoryError e) { e.printStackTrace(); } return null; } }); super.setCheckNetwork(false); setCacheFullRemoveType(new RemoveTypeUsedCountSmall<Bitmap>()); secondaryCache = new ImageSDCardCache(secondaryCacheMaxSize, secondaryCacheThreadPoolSize); secondaryCache.setCacheFolder(DEFAULT_CACHE_FOLDER); secondaryCache.setFileNameRule(new FileNameRuleImageUrl().setFileExtension("")); } /** * get compressSize * * @return the compressSize */ public int getCompressSize() { return compressSize; } /** * set image compress scale * <ul> * <strong>Attentions:</strong> * <li>if {@link #setCompressListener(cn.trinea.android.common.service.impl.ImageCache.CompressListener)} is set, this function is not work</li> * </ul> * * @param compressSize the compressSize to set * @see {@link #setCompressSize(String)} */ public void setCompressSize(int compressSize) { this.compressSize = compressSize; } /** * set compressListener * <ul> * <strong>Attentions:</strong> * <li>if this function is set, the function {@link #setCompressSize(String)} is not work</li> * </ul> * * @param compressListener */ public void setCompressListener(CompressListener compressListener) { this.compressListener = compressListener; } /** * get compressListener * * @return compressListener */ public CompressListener getCompressListener() { return compressListener; } /** * set image compress scale */ public interface CompressListener { /** * get image compress scale * <ul> * <strong>Attentions:</strong> * <li>if this function is set, the function {@link #setCompressSize(String)} is not work</li> * </ul> * * @param imagePath * @return return compressSize, If > 1, requests the decoder to subsample the original image, returning a * smaller image to save memory. The sample size is the number of pixels in either dimension that * correspond to a single pixel in the decoded bitmap. For example, inSampleSize == 4 returns an image * that is 1/4 the width/height of the original, and 1/16 the number of pixels. Any value <= 1 is * treated the same as 1. Note: the decoder will try to fulfill this request, but the resulting bitmap * may have different dimensions that precisely what has been requested. Also, powers of 2 are often * faster/easier for the decoder to honor. */ public int getCompressSize(String imagePath); } /** * get http read image time out of secondary cache, if less than 0, not set. default is not set * * @return the httpReadTimeOut */ @Override public int getHttpReadTimeOut() { return secondaryCache.getHttpReadTimeOut(); } /** * set http read image time out of secondary cache, if less than 0, not set. default is not set, in mills * * @param readTimeOutMillis */ @Override public void setHttpReadTimeOut(int readTimeOutMillis) { secondaryCache.setHttpReadTimeOut(readTimeOutMillis); } /** * clear both primary cache and secondary cache */ @Override public void clear() { super.clear(); secondaryCache.clear(); } @Override public void setForwardCacheNumber(int forwardCacheNumber) { super.setForwardCacheNumber(forwardCacheNumber); secondaryCache.setForwardCacheNumber(forwardCacheNumber); } @Override public void setBackwardCacheNumber(int backwardCacheNumber) { super.setForwardCacheNumber(backwardCacheNumber); secondaryCache.setForwardCacheNumber(backwardCacheNumber); } @Override public int getAllowedNetworkTypes() { return secondaryCache.getAllowedNetworkTypes(); } @Override public void setAllowedNetworkTypes(int allowedNetworkTypes) { secondaryCache.setAllowedNetworkTypes(allowedNetworkTypes); } @Override public boolean isCheckNetwork() { return secondaryCache.isCheckNetwork(); } @Override public void setCheckNetwork(boolean isCheckNetwork) { secondaryCache.setCheckNetwork(isCheckNetwork); } @Override public boolean checkIsNetworkTypeAllowed() { return secondaryCache.checkIsNetworkTypeAllowed(); } @Override public Context getContext() { return secondaryCache.getContext(); } @Override public void setContext(Context context) { secondaryCache.setContext(context); } /** * set http request properties * <ul> * <li>If image is from the different server, setRequestProperty("Connection", "false") is recommended. If image is * from the same server, true is recommended, and this is the default value</li> * </ul> * * @param requestProperties */ public void setRequestProperties(Map<String, String> requestProperties) { secondaryCache.setRequestProperties(requestProperties); } /** * get http request properties * * @return */ public Map<String, String> getRequestProperties() { return secondaryCache.getRequestProperties(); } /** * Sets the value of the http request header field * * @param field the request header field to be set * @param newValue the new value of the specified property * @see {@link #setRequestProperties(java.util.Map)} */ public void setRequestProperty(String field, String newValue) { secondaryCache.setRequestProperty(field, newValue); } /** * get cache folder path which be used when saving images, default is {@link #DEFAULT_CACHE_FOLDER} * * @return the cacheFolder * @see ImageSDCardCache#getCacheFolder() */ public String getCacheFolder() { return secondaryCache.getCacheFolder(); } /** * set cache folder path which be used when saving images, default is {@link #DEFAULT_CACHE_FOLDER} * * @param cacheFolder * @see ImageSDCardCache#setCacheFolder(String) */ public void setCacheFolder(String cacheFolder) { secondaryCache.setCacheFolder(cacheFolder); } /** * get file name rule which be used when saving images, default is {@link FileNameRuleImageUrl} * * @return the fileNameRule * @see ImageSDCardCache#getFileNameRule() */ public FileNameRule getFileNameRule() { return secondaryCache.getFileNameRule(); } /** * set file name rule which be used when saving images, default is {@link FileNameRuleImageUrl} * * @param fileNameRule * @see ImageSDCardCache#setFileNameRule(FileNameRule) */ public void setFileNameRule(FileNameRule fileNameRule) { secondaryCache.setFileNameRule(fileNameRule); } /** * load all data from db and delete unused file in {@link #getCacheFolder()} * <ul> * <li>It's a combination of {@link #loadDataFromDb(android.content.Context, String)} and {@link #deleteUnusedFiles()}</li> * <li>You should use {@link #saveDataToDb(android.content.Context, String)} to save data when app exit</li> * </ul> * * @param context * @param tag * @see #loadDataFromDb(android.content.Context, String) * @see #deleteUnusedFiles() */ public void initData(Context context, String tag) { loadDataFromDb(context, tag); deleteUnusedFiles(); } /** * delete unused file in {@link #getCacheFolder()}, you can use it after {@link #loadDataFromDb(android.content.Context, String)} at * first time * * @see {@link ImageSDCardCache#deleteUnusedFiles()} */ public void deleteUnusedFiles() { secondaryCache.deleteUnusedFiles(); } /** * load all data in db whose tag is same to tag to this cache. just put, do not affect the original data * <ul> * <strong>Attentions:</strong> * <li>If tag is null or empty, throws exception</li> * <li>You should use {@link #saveDataToDb(android.content.Context, String)} to save data when app exit</li> * </ul> * * @param context * @param tag tag used to mark this cache when save to and load from db, should be unique and cannot be null or * empty * @return * @see ImageSDCardCache#loadDataFromDb(android.content.Context, ImageSDCardCache, String) */ public boolean loadDataFromDb(Context context, String tag) { return ImageSDCardCache.loadDataFromDb(context, secondaryCache, tag); } /** * delete all rows in db whose tag is same to tag at first, and insert all data in this cache to db * <ul> * <strong>Attentions:</strong> * <li>If tag is null or empty, throws exception</li> * <li>Will delete all rows in db whose tag is same to tag at first</li> * <li>You can use {@link #initData(android.content.Context, String)} or {@link #loadDataFromDb(android.content.Context, String)} to init data when * app start</li> * </ul> * * @param context * @param tag tag used to mark this cache when save to and load from db, should be unique and cannot be null or * empty * @return * @see ImageSDCardCache#saveDataToDb(android.content.Context, ImageSDCardCache, String) */ public boolean saveDataToDb(Context context, String tag) { return ImageSDCardCache.saveDataToDb(context, secondaryCache, tag); } /** * get image file path * * @param imageUrl * @return if not in cache return null, else return full path. */ public String getImagePath(String imageUrl) { return secondaryCache.getImagePath(imageUrl); } /** * @see java.util.concurrent.ExecutorService#shutdown() */ @Override protected void shutdown() { secondaryCache.shutdown(); super.shutdown(); } /** * @see java.util.concurrent.ExecutorService#shutdownNow() */ @Override public List<Runnable> shutdownNow() { secondaryCache.shutdownNow(); return super.shutdownNow(); } /** * get get image listener of primary cache * * @return * @see {@link PreloadDataCache#getOnGetDataListener()} */ public OnGetDataListener<String, Bitmap> getOnGetImageListenerOfPrimaryCache() { return getOnGetDataListener(); } /** * set get data listener of primary cache, primary cache will get data and preload data by it * * @param onGetImageListener * @see {@link PreloadDataCache#setOnGetDataListener(OnGetDataListener)} */ public void setOnGetImageListenerOfPrimaryCache(OnGetDataListener<String, Bitmap> onGetImageListener) { this.onGetDataListener = onGetImageListener; } /** * get get image listener of secondary cache * * @return */ public OnGetDataListener<String, String> getOnGetImageListenerOfSecondaryCache() { return secondaryCache.getOnGetDataListener(); } /** * set get data listener of secondary cache, secondary cache will get data and preload data by it * * @param onGetImageListener */ public void setOnGetImageListenerOfSecondaryCache(OnGetDataListener<String, String> onGetImageListener) { secondaryCache.setOnGetDataListener(onGetImageListener); } /** * get remove type when secondary cache is full * * @return */ public CacheFullRemoveType<String> getCacheFullRemoveTypeOfSecondaryCache() { return secondaryCache.getCacheFullRemoveType(); } /** * set remove type when secondary cache is full * * @param cacheFullRemoveType the cacheFullRemoveType to set */ public void setCacheFullRemoveTypeOfSecondaryCache(CacheFullRemoveType<String> cacheFullRemoveType) { secondaryCache.setCacheFullRemoveType(cacheFullRemoveType); } }