package com.krislq.cache.manager; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Handler; import android.widget.ImageView; import com.krislq.cache.Constants; import com.krislq.cache.util.ImageUtil; import com.krislq.cache.util.L; import com.krislq.cache.util.ThreadPoolUtil; import com.krislq.cache.util.Util; /** * * @author <a href="mailto:kris1987@qq.com">Kris.lee</a> * @website www.krislq.com * @date Nov 20, 2012 * @version 1.0.0 * */ public class DownloadManager { /** * max download image thread. */ public static final int MAX_THREAD_LENGTH = 5; public static final int TYPE_ARTILE_THUMB = 1; public static final int TYPE_USER_THUMB_ICON = 2; public static final int TYPE_USER_ICON = 3; public static final int TYPE_GAME_ICON = 4; public static final int TYPE_GIFT_ICON = 5; public static final int TYPE_FRIEND_ICON = 6; public static final int TYPE_ACHIEVEMENT_ICON = 7; public static final int TYPE_FRIEND_REQUESTS_ICON = 8; public static final int TYPE_NOTIFICATION_ICON = 9; public static final int TYPE_FEED_PHOTO_ICON = 10; public static final int TYPE_GAME_SCREENSHOT = 11; public static Map<Integer, ImageSize> TYPEMAP; private static final int STATUS_NORMAL =0; private static final int STATUS_PAUSE =1; private static final int STATUS_STOP =2; private int status = STATUS_NORMAL; private Context context; private Handler handler ; /** * downloading or will be downloaded url map . */ private Map<String,Set<ImageView>> downloadMaps = null; /** * downloadind thread map */ private Map<String,Thread> threadMap = null; /** * save the width/height of each url image.<br> * maybe each activity just init a doanlodManager instance , <br> * but need to download a variety of image */ private Map<String,ImageSize> sizeMap = null; /** * pending url list */ private List<String> peddingList = null; private Map<Object,SoftReference<Bitmap>> bitmapCache = null; /** * image type.<br> * the app will get the width/height according the type, if the activity is not specified the size .<br> * */ private int type; public DownloadManager(Context context, Handler handler,int type) { downloadMaps = new HashMap<String, Set<ImageView>>(); threadMap = new HashMap<String,Thread>(); peddingList = new ArrayList<String>(); sizeMap = new HashMap<String, ImageSize>(); bitmapCache = new HashMap<Object, SoftReference<Bitmap>>(); this.type = type; this.context =context; if(handler ==null) { throw new IllegalArgumentException("handler is null"); } this.handler = handler; init(); } private void init() { if(TYPEMAP==null) { TYPEMAP = new HashMap<Integer, ImageSize>(); TYPEMAP.put(TYPE_ARTILE_THUMB, new ImageSize(Util.dip2px(context, 70), Util.dip2px(context, 70))); TYPEMAP.put(TYPE_USER_ICON, new ImageSize(Util.dip2px(context, 150), Util.dip2px(context, 150))); TYPEMAP.put(TYPE_GAME_ICON, new ImageSize(Util.dip2px(context, 70), Util.dip2px(context, 70))); TYPEMAP.put(TYPE_GIFT_ICON, new ImageSize(Util.dip2px(context, 70), Util.dip2px(context, 70))); TYPEMAP.put(TYPE_USER_THUMB_ICON, new ImageSize(Util.dip2px(context, 70), Util.dip2px(context, 70))); TYPEMAP.put(TYPE_FRIEND_ICON, new ImageSize(Util.dip2px(context, 70), Util.dip2px(context, 70))); TYPEMAP.put(TYPE_ACHIEVEMENT_ICON, new ImageSize(Util.dip2px(context, 70), Util.dip2px(context, 70))); TYPEMAP.put(TYPE_FRIEND_REQUESTS_ICON, new ImageSize(Util.dip2px(context, 70), Util.dip2px(context, 70))); TYPEMAP.put(TYPE_NOTIFICATION_ICON, new ImageSize(Util.dip2px(context, 70), Util.dip2px(context, 70))); TYPEMAP.put(TYPE_GAME_SCREENSHOT, new ImageSize(Util.dip2px(context, 230), Util.dip2px(context, 380))); TYPEMAP.put(TYPE_FEED_PHOTO_ICON, new ImageSize(Util.dip2px(context, 100), Util.dip2px(context, 100))); } } /** * stop to start a new download thread. but not pause or interrupt the downloading thread. */ public void pause() { status = STATUS_PAUSE; L.i("DownloadManager#pending url size:"+peddingList.size()); } /** * resume the download manager.<br> * if there are pending urls , will begin new threads to download. or accept to be added the new url . */ public void resume() { status = STATUS_NORMAL; //if the current thread number is less than the max thread length, //so need to check pending list, if there are any pending urls,so need start new thread to download them L.i("DownloadManager#current thread size:"+threadMap.size()); if(threadMap.size() < MAX_THREAD_LENGTH) { int threadCount = MAX_THREAD_LENGTH - threadMap.size(); for(int i=0;i<threadCount;i++) { //if there are free thread ,so need to get one of the pending url. synchronized (peddingList) { if(peddingList.size() >0) { String purl = peddingList.get(0); DownLoadThread thread = new DownLoadThread(purl,getImageSizeByUrl(purl)); thread.start(); synchronized(threadMap) { threadMap.put(purl, thread); } //if begin a new thread to download the pending url, will remove the url from the pending list peddingList.remove(0); } } } } } public void destory() { status = STATUS_STOP; if(threadMap!=null) { Iterator<String> iterator = threadMap.keySet().iterator(); while (iterator.hasNext()) { String threadName = iterator.next(); Thread thread = threadMap.get(threadName); if(thread!=null) { try { thread.interrupt(); }catch(Exception e) { } } } threadMap.clear(); // threadMap = null; } if(downloadMaps!=null) { downloadMaps.clear(); // downloadMaps = null; } if(sizeMap!=null) { sizeMap.clear(); } if(peddingList!=null) { peddingList.clear(); // peddingList = null; } if(bitmapCache!=null) { //recycle the bitmap Iterator<Object> iterator = bitmapCache.keySet().iterator(); while (iterator.hasNext()) { Object key = iterator.next(); SoftReference<Bitmap> srBitmap = bitmapCache.get(key); if(srBitmap!=null) { Bitmap bitmap = srBitmap.get(); if(bitmap!=null) { if(!bitmap.isRecycled()) { bitmap.recycle(); } } } } bitmapCache.clear(); // bitmapCache = null; } } /** * if the url is downloaded , need to remove from the download map and size map . * @param url */ public void removeUrl(String url) { synchronized (downloadMaps) { downloadMaps.remove(url); } synchronized (sizeMap) { sizeMap.remove(url); } } /** * get the image size according the type * @param type * @return */ private ImageSize getImageSizeByType(int type) { return TYPEMAP.get(type); } public int getType() { return type; } public void setType(int type) { this.type = type; } public Context getContext() { return context; } public void setContext(Context context) { this.context = context; } public Handler getHandler() { return handler; } public void setHandler(Handler handler) { this.handler = handler; } /** * return the total url size, maybe this url is downloading or pending . * @return */ public int getUrlSize() { return downloadMaps.size(); } /** * return the current thread size * @return */ public int getThreadSize() { return threadMap.size(); } /** * return the pending url size * @return */ public int getPendingSize() { return peddingList.size(); } /** * add a url to the manager .and the image size will get from the type * @param url need to download url * @param imageView the imageview need to be paint after the url have downloaded * @param defaultRes default image resource */ public void add(String url,ImageView imageView,int defaultRes) { add(url, imageView, defaultRes, type); } /** * add a url to the manager * @param url need to download url * @param imageView the imageview need to be paint after the url have downloaded * @param defaultRes default image resource * @param type the type of the url,just used for the current url. */ public synchronized void add(final String url,final ImageView imageView,final int defaultRes,final int type) { // ThreadPoolUtil.execute(new Runnable() { // @Override // public void run() { //if the url is empty, so set the default resource if(Util.isEmpty(url)) { setDefaultImage(imageView, defaultRes); return; } //if the image view is empty, return directly .no download value if(imageView == null) { return; } //search in the cache, and search the local cache file, at the last to download from web if(containsCache(url)) { Bitmap bitmap = getCahceBitmap(url); if(bitmap!=null) { L.i("DownloadManager#have the cache image:"+url); setImage(imageView, bitmap); return; } } File cacheFile = new File(Constants.CACHE_DIR + File.separator+Util.toCharString(url)); //if exist in the local cache file,get it directly if (cacheFile.exists()) { L.v("DownloadManager#local image exist"); //decode the file decodeFromFile(imageView, cacheFile.getAbsolutePath(), url); } else { //begin download, need to set the default image resource . setDefaultImage(imageView, defaultRes); //if this url is existed in the download urls.just save the image view, and wait. //will no the start a new thread. if(downloadMaps.containsKey(url)) { L.v("DownloadManager#exist download ,only wait:"+imageView.toString()); Set<ImageView> set = downloadMaps.get(url); if(set == null) { set = new HashSet<ImageView>(); } synchronized(set) { //if the imageview have existed in the set, the set will remove the extra set.add(imageView); } } else//if the url is a new url . will add to the download map.and start a new thread if there is a free thread. { L.v("DownloadManager#start a thread to download image"); Set<ImageView> set = new HashSet<ImageView>(); set.add(imageView); synchronized (downloadMaps) { downloadMaps.put(url, set); } synchronized (sizeMap) { sizeMap.put(url, getImageSizeByType(type)); } addThread(url); } } // } // }); } private void setDefaultImage(ImageView imageView, int defaultRes) { if(defaultRes >0) { if(containsCache(defaultRes) ) { Bitmap bitmap = getCahceBitmap(defaultRes); if(bitmap!=null) { setImage(imageView, bitmap); } else { decodeFromResource(imageView, defaultRes); } } else { //encode the bitmap decodeFromResource(imageView, defaultRes); } } } /** * add a thread. <br> * if the download thread size is less than MAX_THREAD_LENGTH and the manager is normal(not pause, or destroy),<br> * will start a new thread to download the url; * @param url */ private void addThread(String url) { if(threadMap.size() < MAX_THREAD_LENGTH && status == STATUS_NORMAL) { L.i("DownloadManager#downloading thread is less than MAX_THREAD:"+url); DownLoadThread thread = new DownLoadThread(url,getImageSizeByUrl(url)); thread.start(); synchronized(threadMap) { threadMap.put(url, thread); } } else { synchronized(peddingList) { L.i("DownloadManager#Have enough download thread.only pending"); peddingList.add(url); } } } private ImageSize getImageSizeByUrl(String url) { ImageSize size = sizeMap.get(url); if(size==null) { size = getImageSizeByType(type); } return size; } /** * when one of the url have downloaded.will remove thread thread, and remove the url from the cache.<br> * then start a new thread if the manager status is normal * @param url */ private void removeThread(String url) { L.e("removeThread:"+url); threadMap.remove(url); removeUrl(url); synchronized(peddingList) { if(peddingList.size() >0 && status == STATUS_NORMAL)//如果有等待的线程,则开始 { L.v("DownloadManager#pending size:"+peddingList.size()); String pUrl = peddingList.get(0); DownLoadThread thread = new DownLoadThread(pUrl,getImageSizeByUrl(pUrl)); thread.start(); synchronized(threadMap) { threadMap.put(pUrl, thread); } //remove the url from the pending list peddingList.remove(0); } else { L.v("DownloadManager#no pending list"); } } } /** * decode the bitmap from the resource and put it to the cache * @param imageView * @param resource */ private void decodeFromResource(final ImageView imageView,final int resource) { ThreadPoolUtil.execute(new Runnable() { @Override public void run() { final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resource); addCacheBitmap(resource, bitmap); setImage(imageView, bitmap); } }); } private void setImage(final ImageView imageView,final Bitmap bitmap) { if(handler!=null) { handler.post(new Runnable() { @Override public void run() { imageView.setImageBitmap(bitmap); } }); } } private void decodeFromFile(final ImageView imageView,final String path,final String url) { ThreadPoolUtil.execute(new Runnable() { @Override public void run() { final Bitmap bitmap = BitmapFactory.decodeFile(path); addCacheBitmap(url, bitmap); if(handler!=null) { setImage(imageView, bitmap); } } }); } /** * check whether contained the url in the cache or not * @param key * @return */ public boolean containsCache(Object key) { return bitmapCache.containsKey(key); } /** * get the cache bitmap from the bitmap cache map * @param key * @return */ public Bitmap getCahceBitmap(Object key) { SoftReference<Bitmap> srBitmap = bitmapCache.get(key); if(srBitmap!=null) { Bitmap bitmap = srBitmap.get(); if(bitmap!=null) { return bitmap; } } return null; } /** * add a cache bitmap to the cache map * @param key * @param bitmap */ public void addCacheBitmap(Object key,Bitmap bitmap) { //if not contains this key , so add to the hashmap if(!bitmapCache.containsKey(key)) { SoftReference<Bitmap> srBitmap = new SoftReference<Bitmap>(bitmap); bitmapCache.put(key, srBitmap); } } class DownLoadThread extends Thread { private String url; private int width = 0; private int height = 0; public DownLoadThread(String url,ImageSize size) { super(url); this.url = url; if(size!=null) { this.width = size.width; this.height = size.height; } } private String generateImageUrl(String url) { // url = url.replace("/", "%2F").replace(":", "%3A").replace(".", "%2E").replace("-", "%2D").replace("_", "%5F"); // return WebUrls.API_IMAGE_PROXY_URL+"?url="+URLDecoder.decode(url)+"&width="+width+"&height="+height; return url; } public void run() { HttpEntity entity = null; InputStream is = null; try { HttpClient client = new DefaultHttpClient(); String proxyUrl = generateImageUrl(url); L.i("DownloadManager#"+proxyUrl); HttpGet get = new HttpGet(proxyUrl); HttpResponse response = client.execute(get); if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { entity = response.getEntity(); is = entity.getContent(); Bitmap bitmap = BitmapFactory.decodeStream(is); if(bitmap!=null) { L.i("DownloadManager#success#"+proxyUrl); //add to the bitmap cache map addCacheBitmap(url, bitmap); //get the sounded corner bitmap File cacheFile = new File(Constants.CACHE_DIR + File.separator+Util.toCharString(url)); ImageUtil.writeBitmap2File(bitmap, cacheFile); //notify the imageview notifyView(url, bitmap); return; } L.i("DownloadManager#failture#"+proxyUrl); } else { L.e("DownloadManager#error Code:"+response.getStatusLine().getStatusCode()); } } catch (ClientProtocolException e) { L.e("DownloadManager#ClientProtocolException url:"+url, e); } catch (Exception e) { L.e("DownloadManager#exception url:"+url, e); } finally { //下载完成了后,就把当前的线程移除了 removeThread(url); if (entity != null) { try { entity.consumeContent(); } catch (IOException e) { } } if (is != null) { try { is.close(); } catch (IOException e) { } } } } private void notifyView(final String url,final Bitmap bitmap) { Set<ImageView> set = downloadMaps.get(url); if(set == null) { L.v("DownloadManager#通知的ImageView尽然是空!!!"+url); return; } L.e("##########通知:"+set.size()); Iterator<ImageView> iterator = set.iterator(); while (iterator.hasNext()) { ImageView view = iterator.next(); if(view == null) continue; view.post(new PostRunnable(view) { @Override public void run() { imageView.setImageBitmap(bitmap); } }); } } } abstract class PostRunnable implements Runnable{ ImageView imageView; PostRunnable(ImageView view) { this.imageView = view; } } class ImageSize { int width; int height; public ImageSize(int width,int height) { this.width = width; this.height = height; } } }