package cn.trinea.android.common.service.impl; import java.io.File; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import android.content.Context; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import android.view.View; import cn.trinea.android.common.dao.impl.ImageSDCardCacheDaoImpl; import cn.trinea.android.common.entity.CacheObject; import cn.trinea.android.common.entity.FailedReason; import cn.trinea.android.common.entity.FailedReason.FailedType; import cn.trinea.android.common.service.CacheFullRemoveType; import cn.trinea.android.common.service.FileNameRule; import cn.trinea.android.common.util.FileUtils; import cn.trinea.android.common.util.ImageUtils; import cn.trinea.android.common.util.SizeUtils; import cn.trinea.android.common.util.SqliteUtils; import cn.trinea.android.common.util.StringUtils; import cn.trinea.android.common.util.SystemUtils; /** * <strong>Image SDCard Cache</strong><br/> * <br/> * It applies to images those uesd frequently and their size is big that we cannot store too much in memory, like * pictures of twitter or sina weibo. Cache of small images you can consider of {@link cn.trinea.android.common.service.impl.ImageMemoryCache}.<br/> * <ul> * <strong>Setting and Usage</strong> * <li>Use one of constructors in sections II to init cache</li> * <li>{@link #setOnImageSDCallbackListener(cn.trinea.android.common.service.impl.ImageSDCardCache.OnImageSDCallbackListener)} set callback interface when getting image</li> * <li>{@link #get(String, java.util.List, android.view.View)} get image asynchronous and preload other images asynchronous according to * urlList</li> * <li>{@link #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 #setFileNameRule(FileNameRule)} set file name rule which be used when saving images, default is * {@link FileNameRuleImageUrl}</li> * <li>{@link #setCacheFolder(String)} set cache folder path which be used when saving images, default is * {@link #DEFAULT_CACHE_FOLDER}</li> * <li>{@link #setHttpReadTimeOut(int)} set http read image time out, if less than 0, not set. default is not set</li> * <li>{@link #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 cn.trinea.android.common.service.impl.SimpleCache#setCacheFullRemoveType(CacheFullRemoveType)} set remove type when cache is full</li> * <li>other see {@link PreloadDataCache} and {@link cn.trinea.android.common.service.impl.SimpleCache}</li> * </ul> * <ul> * <strong>Constructor</strong> * <li>{@link #ImageSDCardCache()}</li> * <li>{@link #ImageSDCardCache(int)}</li> * <li>{@link #ImageSDCardCache(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> 2012-4-5 */ public class ImageSDCardCache extends PreloadDataCache<String, String> { private static final long serialVersionUID = 1L; private static final String TAG = "ImageSDCardCache"; /** callback interface when getting image **/ private OnImageSDCallbackListener onImageSDCallbackListener; /** cache folder path which be used when saving images, default is {@link #DEFAULT_CACHE_FOLDER} **/ private String cacheFolder = DEFAULT_CACHE_FOLDER; /** file name rule which be used when saving images, default is {@link FileNameRuleImageUrl} **/ private FileNameRule fileNameRule = new FileNameRuleImageUrl(); /** http read image time out, if less than 0, not set. default is not set **/ private int httpReadTimeOut = -1; /** * whether open waiting queue, default is true. If true, save all view waiting for image loaded, else only save the * newest one **/ private boolean isOpenWaitingQueue = true; /** http request properties **/ private Map<String, String> requestProperties = null; /** recommend default max cache size according to dalvik max memory **/ public static final int DEFAULT_MAX_SIZE = getDefaultMaxSize(); /** 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("ImageSDCardCache").toString(); /** message what for get image successfully **/ private static final int WHAT_GET_IMAGE_SUCCESS = 1; /** message what for get image failed **/ private static final int WHAT_GET_IMAGE_FAILED = 2; /** thread pool whose wait for data got, attention, not the get data thread pool **/ private transient ExecutorService threadPool = Executors .newFixedThreadPool(SystemUtils.DEFAULT_THREAD_POOL_SIZE); /** * key is image url, value is the newest view which waiting for image loaded, used when {@link #isOpenWaitingQueue} * is false **/ private transient Map<String, View> viewMap; /** * key is image url, value is view set those waiting for image loaded, used when {@link #isOpenWaitingQueue} is true **/ private transient Map<String, HashSet<View>> viewSetMap; private transient Handler handler; /** * get image asynchronous. when get image success, it will pass to * {@link cn.trinea.android.common.service.impl.ImageSDCardCache.OnImageSDCallbackListener#onGetSuccess(String, String, android.view.View, boolean)} * * @param imageUrl * @param view * @return whether image already in cache or not */ public boolean get(String imageUrl, View view) { return get(imageUrl, null, view); } /** * get image asynchronous and preload other images asynchronous according to urlList * * @param imageUrl * @param urlList url list, if is null, not preload, else preload forward by * {@link PreloadDataCache#preloadDataForward(Object, java.util.List, int)}, preload backward by * {@link PreloadDataCache#preloadDataBackward(Object, java.util.List, int)} * @param view * @return whether image already in cache or not */ public boolean get(final String imageUrl, final List<String> urlList, final View view) { if (onImageSDCallbackListener != null) { onImageSDCallbackListener.onPreGet(imageUrl, view); } if (StringUtils.isEmpty(imageUrl)) { if (onImageSDCallbackListener != null) { onImageSDCallbackListener.onGetNotInCache(imageUrl, view); } return false; } /** * if already in cache, call onImageSDCallbackListener, else new thread to wait for it */ CacheObject<String> object = getFromCache(imageUrl, urlList); if (object != null) { String imagePath = object.getData(); if (!StringUtils.isEmpty(imagePath) && FileUtils.isFileExist(imagePath)) { onGetSuccess(imageUrl, imagePath, view, true); return true; } else { remove(imageUrl); } } if (isOpenWaitingQueue) { synchronized (viewSetMap) { HashSet<View> viewSet = viewSetMap.get(imageUrl); if (viewSet == null) { viewSet = new HashSet<View>(); viewSetMap.put(imageUrl, viewSet); } viewSet.add(view); } } else { viewMap.put(imageUrl, view); } if (onImageSDCallbackListener != null) { onImageSDCallbackListener.onGetNotInCache(imageUrl, view); } if (isExistGettingDataThread(imageUrl)) { return false; } startGetImageThread(imageUrl, urlList); return false; } /** * get cache folder path which be used when saving images, default is {@link #DEFAULT_CACHE_FOLDER} * * @return the cacheFolder */ public String getCacheFolder() { return cacheFolder; } /** * set cache folder path which be used when saving images, default is {@link #DEFAULT_CACHE_FOLDER} * * @param cacheFolder */ public void setCacheFolder(String cacheFolder) { if (StringUtils.isEmpty(cacheFolder)) { throw new IllegalArgumentException("The cacheFolder of cache can not be null."); } this.cacheFolder = cacheFolder; } /** * get file name rule which be used when saving images, default is {@link FileNameRuleImageUrl} * * @return the fileNameRule */ public FileNameRule getFileNameRule() { return fileNameRule; } /** * set file name rule which be used when saving images, default is {@link FileNameRuleImageUrl} * * @param fileNameRule */ public void setFileNameRule(FileNameRule fileNameRule) { if (fileNameRule == null) { throw new IllegalArgumentException("The fileNameRule of cache can not be null."); } this.fileNameRule = fileNameRule; } /** * get callback interface when getting image * * @return the onImageSDCallbackListener */ public OnImageSDCallbackListener getOnImageSDCallbackListener() { return onImageSDCallbackListener; } /** * set callback interface when getting image * * @param onImageSDCallbackListener the onImageSDCallbackListener to set */ public void setOnImageSDCallbackListener(OnImageSDCallbackListener onImageSDCallbackListener) { this.onImageSDCallbackListener = onImageSDCallbackListener; } /** * get http read image time out, if less than 0, not set. default is not set * * @return the httpReadTimeOut */ public int getHttpReadTimeOut() { return httpReadTimeOut; } /** * set http read image time out, if less than 0, not set. default is not set, in mills * * @param readTimeOutMillis */ public void setHttpReadTimeOut(int readTimeOutMillis) { this.httpReadTimeOut = readTimeOutMillis; } /** * get whether open waiting queue, default is true. If true, save all view waiting for image loaded, else only save * the newest one * * @return */ public boolean isOpenWaitingQueue() { return isOpenWaitingQueue; } /** * set whether open waiting queue, default is true. If true, save all view waiting for image loaded, else only save * the newest one * * @param isOpenWaitingQueue */ public void setOpenWaitingQueue(boolean isOpenWaitingQueue) { this.isOpenWaitingQueue = isOpenWaitingQueue; } /** * 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) { this.requestProperties = requestProperties; } /** * get http request properties * * @return */ public Map<String, String> getRequestProperties() { return requestProperties; } /** * 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) { if (StringUtils.isEmpty(field)) { return; } if (requestProperties == null) { requestProperties = new HashMap<String, String>(); } requestProperties.put(field, newValue); } /** * <ul> * <li>Get data listener is {@link #getDefaultOnGetImageListener()}</li> * <li>callback interface when getting image is null, can set by * {@link #setOnImageSDCallbackListener(cn.trinea.android.common.service.impl.ImageSDCardCache.OnImageSDCallbackListener)}</li> * <li>Maximum size of the cache is {@link #DEFAULT_MAX_SIZE}</li> * <li>Elements of the cache will not invalid</li> * <li>Remove type is {@link RemoveTypeUsedCountSmall} when cache is full</li> * </ul> * * @see PreloadDataCache#PreloadDataCache() */ public ImageSDCardCache() { this(DEFAULT_MAX_SIZE, PreloadDataCache.DEFAULT_THREAD_POOL_SIZE); } /** * <ul> * <li>Get data listener is {@link #getDefaultOnGetImageListener()}</li> * <li>callback interface when getting image is null, can set by * {@link #setOnImageSDCallbackListener(cn.trinea.android.common.service.impl.ImageSDCardCache.OnImageSDCallbackListener)}</li> * <li>Elements of the cache will not invalid</li> * <li>Remove type is {@link RemoveTypeUsedCountSmall} when cache is full</li> * </ul> * * @param maxSize maximum size of the cache * @see PreloadDataCache#PreloadDataCache(int) */ public ImageSDCardCache(int maxSize) { this(maxSize, PreloadDataCache.DEFAULT_THREAD_POOL_SIZE); } /** * <ul> * <li>Get data listener is {@link #getDefaultOnGetImageListener()}</li> * <li>callback interface when getting image is null, can set by * {@link #setOnImageSDCallbackListener(cn.trinea.android.common.service.impl.ImageSDCardCache.OnImageSDCallbackListener)}</li> * <li>Elements of the cache will not invalid</li> * <li>Remove type is {@link RemoveTypeUsedCountSmall} when cache is full</li> * </ul> * * @param maxSize maximum size of the cache * @param threadPoolSize getting data thread pool size * @see PreloadDataCache#PreloadDataCache(int, int) */ public ImageSDCardCache(int maxSize, int threadPoolSize) { super(maxSize, threadPoolSize); super.setOnGetDataListener(getDefaultOnGetImageListener()); super.setCacheFullRemoveType(new RemoveTypeUsedCountSmall<String>()); this.viewMap = new ConcurrentHashMap<String, View>(); this.viewSetMap = new HashMap<String, HashSet<View>>(); this.handler = new MyHandler(); if (Looper.myLooper() == null) { Looper.prepare(); } } /** * callback interface when getting image * * @author <a href="http://www.trinea.cn" target="_blank">Trinea</a> 2012-4-5 */ public interface OnImageSDCallbackListener { /** * callback function before get image, run on ui thread * * @param imageUrl imageUrl * @param view view need the image */ public void onPreGet(String imageUrl, View view); /** * callback function when get image but image not in cache, run on ui thread.<br/> * Will be called after {@link #onPreGet(String, android.view.View)}, before * {@link #onGetSuccess(String, String, android.view.View, boolean)} and * {@link #onGetFailed(String, String, android.view.View, cn.trinea.android.common.entity.FailedReason)} * * @param imageUrl imageUrl * @param view view need the image */ public void onGetNotInCache(String imageUrl, View view); /** * callback function after get image successfully, run on ui thread * * @param imageUrl imageUrl * @param imagePath image path * @param view view need the image * @param isInCache whether already in cache or got realtime */ public void onGetSuccess(String imageUrl, String imagePath, View view, boolean isInCache); /** * callback function after get image failed, run on ui thread * * @param imageUrl imageUrl * @param imagePath image path * @param view view need the image * @param failedReason failed reason for get image */ public void onGetFailed(String imageUrl, String imagePath, View view, FailedReason failedReason); } /** * @see java.util.concurrent.ExecutorService#shutdown() */ protected void shutdown() { threadPool.shutdown(); super.shutdown(); } /** * @see java.util.concurrent.ExecutorService#shutdownNow() */ public List<Runnable> shutdownNow() { threadPool.shutdownNow(); return super.shutdownNow(); } /** * My handler * * @author <a href="http://www.trinea.cn" target="_blank">Trinea</a> 2012-11-20 */ private class MyHandler extends Handler { public void handleMessage(Message message) { switch (message.what) { case WHAT_GET_IMAGE_SUCCESS: case WHAT_GET_IMAGE_FAILED: MessageObject object = (MessageObject)message.obj; if (object == null) { break; } String imageUrl = object.imageUrl; String imagePath = object.imagePath; if (onImageSDCallbackListener != null) { if (isOpenWaitingQueue) { synchronized (viewSetMap) { HashSet<View> viewSet = viewSetMap.get(imageUrl); if (viewSet != null) { for (View view : viewSet) { if (view != null) { if (WHAT_GET_IMAGE_SUCCESS == message.what) { onGetSuccess(imageUrl, imagePath, view, false); } else { onImageSDCallbackListener.onGetFailed(imageUrl, imagePath, view, object.failedReason); } } } } } } else { View view = viewMap.get(imageUrl); if (view != null) { if (WHAT_GET_IMAGE_SUCCESS == message.what) { onGetSuccess(imageUrl, imagePath, view, false); } else { onImageSDCallbackListener.onGetFailed(imageUrl, imagePath, view, object.failedReason); } } } } if (isOpenWaitingQueue) { synchronized (viewSetMap) { viewSetMap.remove(imageUrl); } } else { viewMap.remove(imageUrl); } break; } } } private void onGetSuccess(String imageUrl, String imagePath, View view, boolean isInCache) { if (onImageSDCallbackListener == null) { return; } try { onImageSDCallbackListener.onGetSuccess(imageUrl, imagePath, view, isInCache); } catch (OutOfMemoryError e) { onImageSDCallbackListener.onGetFailed(imageUrl, imagePath, view, new FailedReason( FailedType.ERROR_OUT_OF_MEMORY, e)); } } /** * message object * * @author <a href="http://www.trinea.cn" target="_blank">Trinea</a> 2013-1-14 */ private class MessageObject { String imageUrl; String imagePath; FailedReason failedReason; public MessageObject(String imageUrl, String imagePath) { this.imageUrl = imageUrl; this.imagePath = imagePath; } public MessageObject(String imageUrl, String imagePath, FailedReason failedReason) { this.imageUrl = imageUrl; this.imagePath = imagePath; this.failedReason = failedReason; } } /** * start thread to wait for image get * * @param imageUrl * @param urlList url list, if is null, not preload, else preload forward by * {@link PreloadDataCache#preloadDataForward(Object, java.util.List, int)}, preload backward by * {@link PreloadDataCache#preloadDataBackward(Object, java.util.List, int)} */ private void startGetImageThread(final String imageUrl, final List<String> urlList) { // wait for image be got success and send message threadPool.execute(new Runnable() { @Override public void run() { try { CacheObject<String> object = get(imageUrl, urlList); String imagePath = (object == null ? null : object.getData()); if (StringUtils.isEmpty(imagePath) || !FileUtils.isFileExist(imagePath)) { // if image get fail, remove it remove(imageUrl); String failedException = "get image from network or save image to sdcard error. please make sure you have added permission android.permission.WRITE_EXTERNAL_STORAGE and android.permission.ACCESS_NETWORK_STATE"; FailedReason failedReason = new FailedReason(FailedType.ERROR_IO, failedException); handler.sendMessage(handler.obtainMessage(WHAT_GET_IMAGE_FAILED, new MessageObject(imageUrl, imagePath, failedReason))); } else { handler.sendMessage(handler.obtainMessage(WHAT_GET_IMAGE_SUCCESS, new MessageObject(imageUrl, imagePath))); } } catch (OutOfMemoryError e) { MessageObject msg = new MessageObject(imageUrl, null, new FailedReason( FailedType.ERROR_OUT_OF_MEMORY, e)); handler.sendMessage(handler.obtainMessage(WHAT_GET_IMAGE_FAILED, msg)); } } }); } /** * delete file when full remove one */ @Override protected CacheObject<String> fullRemoveOne() { CacheObject<String> o = super.fullRemoveOne(); if (o != null) { deleteFile(o.getData()); } return o; } /** * delete file when remove */ @Override public CacheObject<String> remove(String key) { CacheObject<String> o = super.remove(key); if (o != null) { deleteFile(o.getData()); } return o; } /** * delete file when clear cache */ @Override public void clear() { for (CacheObject<String> value : values()) { if (value != null) { deleteFile(value.getData()); } } super.clear(); } /** * delete unused file in {@link #getCacheFolder()}, you can use it after {@link #loadDataFromDb(android.content.Context, String)} at * first time */ public void deleteUnusedFiles() { int size = getSize(); final HashSet<String> filePathSet = new HashSet<String>(size > 16 ? size : 16); for (CacheObject<String> value : values()) { if (value != null) { filePathSet.add(value.getData()); } } threadPool.execute(new Runnable() { @Override public void run() { try { File file = new File(getCacheFolder()); if (file != null && file.exists() && file.isDirectory()) { for (File f : file.listFiles()) { if (f.isFile() && !filePathSet.contains(f.getPath())) { f.delete(); } } } } catch (Exception e) { e.printStackTrace(); Log.e(TAG, "delete unused files fail."); } } }); } /** * 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) { ImageSDCardCache.loadDataFromDb(context, this, tag); deleteUnusedFiles(); } /** * load all data in db whose tag is same to tag to imageSDCardCache. 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 #loadDataFromDb(android.content.Context, cn.trinea.android.common.service.impl.ImageSDCardCache, String) */ public boolean loadDataFromDb(Context context, String tag) { return ImageSDCardCache.loadDataFromDb(context, this, tag); } /** * delete all rows in db whose tag is same to tag at first, and insert all data in imageSDCardCache 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 #saveDataToDb(android.content.Context, cn.trinea.android.common.service.impl.ImageSDCardCache, String) */ public boolean saveDataToDb(Context context, String tag) { return ImageSDCardCache.saveDataToDb(context, this, tag); } /** * load all data in db whose tag is same to tag to imageSDCardCache. just put, do not affect the original data * <ul> * <strong>Attentions:</strong> * <li>If imageSDCardCache is null, throws exception</li> * <li>If tag is null or empty, throws exception</li> * <li>You should use {@link #saveDataToDb(android.content.Context, cn.trinea.android.common.service.impl.ImageSDCardCache, String)} to save data when app exit</li> * </ul> * * @param context * @param imageSDCardCache * @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 */ public static boolean loadDataFromDb(Context context, ImageSDCardCache imageSDCardCache, String tag) { if (context == null || imageSDCardCache == null) { throw new IllegalArgumentException("The context and cache both can not be null."); } if (StringUtils.isEmpty(tag)) { throw new IllegalArgumentException("The tag can not be null or empty."); } return new ImageSDCardCacheDaoImpl(SqliteUtils.getInstance(context)).putIntoImageSDCardCache(imageSDCardCache, tag); } /** * delete all rows in db whose tag is same to tag at first, and insert all data in imageSDCardCache to db * <ul> * <strong>Attentions:</strong> * <li>If imageSDCardCache is null, throws exception</li> * <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, cn.trinea.android.common.service.impl.ImageSDCardCache, String)} * to init data when app start</li> * </ul> * * @param context * @param imageSDCardCache * @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 */ public static boolean saveDataToDb(Context context, ImageSDCardCache imageSDCardCache, String tag) { if (context == null || imageSDCardCache == null) { throw new IllegalArgumentException("The context and cache both can not be null."); } if (StringUtils.isEmpty(tag)) { throw new IllegalArgumentException("The tag can not be null or empty."); } return new ImageSDCardCacheDaoImpl(SqliteUtils.getInstance(context)).deleteAndInsertImageSDCardCache( imageSDCardCache, tag); } /** * get image file path * * @param imageUrl * @return if not in cache return null, else return full path. */ public String getImagePath(String imageUrl) { return (this.containsKey(imageUrl)) ? new StringBuilder(cacheFolder).append(File.separator) .append(fileNameRule.getFileName(imageUrl)).toString() : null; } /** * delete file * * @param path * @return */ private boolean deleteFile(String path) { if (!StringUtils.isEmpty(path)) { if (!FileUtils.deleteFile(path)) { Log.e(TAG, new StringBuilder().append("delete file fail, path is ").append(path).toString()); return false; } } return true; } /** * default get image listener * * @return */ public OnGetDataListener<String, String> getDefaultOnGetImageListener() { return new OnGetDataListener<String, String>() { private static final long serialVersionUID = 1L; @Override public CacheObject<String> onGetData(String key) { String savePath = null; InputStream stream = null; try { stream = ImageUtils.getInputStreamFromUrl(key, httpReadTimeOut, requestProperties); } catch (Exception e) { Log.e(TAG, new StringBuilder().append("get image exception, imageUrl is:").append(key).toString(), e); } if (stream != null) { savePath = cacheFolder + File.separator + fileNameRule.getFileName(key); try { FileUtils.writeFile(savePath, stream); } catch (Exception e1) { try { if (e1.getCause() instanceof FileNotFoundException) { FileUtils.makeFolders(savePath); FileUtils.writeFile(savePath, stream); } else { Log.e(TAG, new StringBuilder() .append("get image exception while write to file, imageUrl is: ") .append(key).append(", savePath is ").append(savePath).toString(), e1); } } catch (Exception e2) { Log.e(TAG, new StringBuilder() .append("get image exception while write to file, imageUrl is: ") .append(key).append(", savePath is ").append(savePath).toString(), e2); } } } return (StringUtils.isEmpty(savePath) ? null : new CacheObject<String>(savePath)); } }; } /** * get recommend default max cache size according to dalvik max memory * * @return */ static int getDefaultMaxSize() { long maxMemory = Runtime.getRuntime().maxMemory(); if (maxMemory > SizeUtils.GB_2_BYTE) { return 256; } int mb = (int)(maxMemory / SizeUtils.MB_2_BYTE); return mb > 8 ? mb : 8; } }