1. 簡介java
如今android應用中不可避免的要使用圖片,有些圖片是能夠變化的,須要每次啓動時從網絡拉取,這種場景在有廣告位的應用以及純圖片應用(好比百度美拍)中比較多。android
如今有一個問題:假如每次啓動的時候都從網絡拉取圖片的話,勢必會消耗不少流量。在當前的情況下,對於非wifi用戶來講,流量仍是很貴的,一個很耗流量的應用,其用戶數量級確定要受到影響。固然,我想,向百度美拍這樣的應用,必然也有其內部的圖片緩存策略。總之,圖片緩存是很重要並且是必須的。canvas
2.圖片緩存的原理緩存
實現圖片緩存也不難,須要有相應的cache策略。這裏我採用 內存-文件-網絡 三層cache機制,其中內存緩存包括強引用緩存和軟引用緩存(SoftReference),其實網絡不算cache,這裏姑且也把它劃到緩存的層次結構中。當根據url向網絡拉取圖片的時候,先從內存中找,若是內存中沒有,再從緩存文件中查找,若是緩存文件中也沒有,再從網絡上經過http請求拉取圖片。在鍵值對(key-value)中,這個圖片緩存的key是圖片url的hash值,value就是bitmap。因此,按照這個邏輯,只要一個url被下載過,其圖片就被緩存起來了。網絡
關於Java中對象的軟引用(SoftReference),若是一個對象具備軟引用,內存空間足夠,垃 圾回收器就不會回收它;若是內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就能夠被程序使用。軟引用可用來實現內存敏感的高 速緩存。使用軟引用能防止內存泄露,加強程序的健壯性。 多線程
從代碼上來講,採用一個ImageManager來負責圖片的管理和緩存,函數接口爲public void loadBitmap(String url, Handler handler) ;其中url爲要下載的圖片地址,handler爲圖片下載成功後的回調,在handler中處理message,而message中包含了圖片的信息以及bitmap對象。ImageManager中使用的ImageMemoryCache(內存緩存)、ImageFileCache(文件緩存)以及LruCache(最近最久未使用緩存)會在後續文章中介紹。異步
3.代碼ImageManager.javaide
/* * 圖片管理 * 異步獲取圖片,直接調用loadImage()函數,該函數本身判斷是從緩存仍是網絡加載 * 同步獲取圖片,直接調用getBitmap()函數,該函數本身判斷是從緩存仍是網絡加載 * 僅從本地獲取圖片,調用getBitmapFromNative() * 僅從網絡加載圖片,調用getBitmapFromHttp() * */ public class ImageManager implements IManager { private final static String TAG = "ImageManager"; private ImageMemoryCache imageMemoryCache; //內存緩存 private ImageFileCache imageFileCache; //文件緩存 //正在下載的image列表 public static HashMap<String, Handler> ongoingTaskMap = new HashMap<String, Handler>(); //等待下載的image列表 public static HashMap<String, Handler> waitingTaskMap = new HashMap<String, Handler>(); //同時下載圖片的線程個數 final static int MAX_DOWNLOAD_IMAGE_THREAD = 4; private final Handler downloadStatusHandler = new Handler(){ public void handleMessage(Message msg) { startDownloadNext(); } }; public ImageManager() { imageMemoryCache = new ImageMemoryCache(); imageFileCache = new ImageFileCache(); } /** * 獲取圖片,多線程的入口 */ public void loadBitmap(String url, Handler handler) { //先從內存緩存中獲取,取到直接加載 Bitmap bitmap = getBitmapFromNative(url); if (bitmap != null) { Logger.d(TAG, "loadBitmap:loaded from native"); Message msg = Message.obtain(); Bundle bundle = new Bundle(); bundle.putString("url", url); msg.obj = bitmap; msg.setData(bundle); handler.sendMessage(msg); } else { Logger.d(TAG, "loadBitmap:will load by network"); downloadBmpOnNewThread(url, handler); } } /** * 新起線程下載圖片 */ private void downloadBmpOnNewThread(final String url, final Handler handler) { Logger.d(TAG, "ongoingTaskMap'size=" + ongoingTaskMap.size()); if (ongoingTaskMap.size() >= MAX_DOWNLOAD_IMAGE_THREAD) { synchronized (waitingTaskMap) { waitingTaskMap.put(url, handler); } } else { synchronized (ongoingTaskMap) { ongoingTaskMap.put(url, handler); } new Thread() { public void run() { Bitmap bmp = getBitmapFromHttp(url); // 不論下載是否成功,都從下載隊列中移除,再由業務邏輯判斷是否從新下載 // 下載圖片使用了httpClientRequest,自己已經帶了重連機制 synchronized (ongoingTaskMap) { ongoingTaskMap.remove(url); } if(downloadStatusHandler != null) { downloadStatusHandler.sendEmptyMessage(0); } Message msg = Message.obtain(); msg.obj = bmp; Bundle bundle = new Bundle(); bundle.putString("url", url); msg.setData(bundle); if(handler != null) { handler.sendMessage(msg); } } }.start(); } } /** * 依次從內存,緩存文件,網絡上加載單個bitmap,不考慮線程的問題 */ public Bitmap getBitmap(String url) { // 從內存緩存中獲取圖片 Bitmap bitmap = imageMemoryCache.getBitmapFromMemory(url); if (bitmap == null) { // 文件緩存中獲取 bitmap = imageFileCache.getImageFromFile(url); if (bitmap != null) { // 添加到內存緩存 imageMemoryCache.addBitmapToMemory(url, bitmap); } else { // 從網絡獲取 bitmap = getBitmapFromHttp(url); } } return bitmap; } /** * 從內存或者緩存文件中獲取bitmap */ public Bitmap getBitmapFromNative(String url) { Bitmap bitmap = null; bitmap = imageMemoryCache.getBitmapFromMemory(url); if(bitmap == null) { bitmap = imageFileCache.getImageFromFile(url); if(bitmap != null) { // 添加到內存緩存 imageMemoryCache.addBitmapToMemory(url, bitmap); } } return bitmap; } /** * 經過網絡下載圖片,與線程無關 */ public Bitmap getBitmapFromHttp(String url) { Bitmap bmp = null; try { byte[] tmpPicByte = getImageBytes(url); if (tmpPicByte != null) { bmp = BitmapFactory.decodeByteArray(tmpPicByte, 0, tmpPicByte.length); } tmpPicByte = null; } catch(Exception e) { e.printStackTrace(); } if(bmp != null) { // 添加到文件緩存 imageFileCache.saveBitmapToFile(bmp, url); // 添加到內存緩存 imageMemoryCache.addBitmapToMemory(url, bmp); } return bmp; } /** * 下載連接的圖片資源 * * @param url * * @return 圖片 */ public byte[] getImageBytes(String url) { byte[] pic = null; if (url != null && !"".equals(url)) { Requester request = RequesterFactory.getRequester( Requester.REQUEST_REMOTE, RequesterFactory.IMPL_HC); // 執行請求 MyResponse myResponse = null; MyRequest mMyRequest; mMyRequest = new MyRequest(); mMyRequest.setUrl(url); mMyRequest.addHeader(HttpHeader.REQ.ACCEPT_ENCODING, "identity"); InputStream is = null; ByteArrayOutputStream baos = null; try { myResponse = request.execute(mMyRequest); is = myResponse.getInputStream().getImpl(); baos = new ByteArrayOutputStream(); byte[] b = new byte[512]; int len = 0; while ((len = is.read(b)) != -1) { baos.write(b, 0, len); baos.flush(); } pic = baos.toByteArray(); Logger.d(TAG, "icon bytes.length=" + pic.length); } catch (Exception e3) { e3.printStackTrace(); try { Logger.e(TAG, "download shortcut icon faild and responsecode=" + myResponse.getStatusCode()); } catch (Exception e4) { e4.printStackTrace(); } } finally { try { if (is != null) { is.close(); is = null; } } catch (Exception e2) { e2.printStackTrace(); } try { if (baos != null) { baos.close(); baos = null; } } catch (Exception e2) { e2.printStackTrace(); } try { request.close(); } catch (Exception e1) { e1.printStackTrace(); } } } return pic; } /** * 取出等待隊列第一個任務,開始下載 */ private void startDownloadNext() { synchronized(waitingTaskMap) { Logger.d(TAG, "begin start next"); Iterator iter = waitingTaskMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); Logger.d(TAG, "WaitingTaskMap isn't null,url=" + (String)entry.getKey()); if(entry != null) { waitingTaskMap.remove(entry.getKey()); downloadBmpOnNewThread((String)entry.getKey(), (Handler)entry.getValue()); } break; } } } public String startDownloadNext_ForUnitTest() { String urlString = null; synchronized(waitingTaskMap) { Logger.d(TAG, "begin start next"); Iterator iter = waitingTaskMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); urlString = (String)entry.getKey(); waitingTaskMap.remove(entry.getKey()); break; } } return urlString; } /** * 圖片變爲圓角 * @param bitmap:傳入的bitmap * @param pixels:圓角的度數,值越大,圓角越大 * @return bitmap:加入圓角的bitmap */ public static Bitmap toRoundCorner(Bitmap bitmap, int pixels) { if(bitmap == null) return null; Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888); Canvas canvas = new Canvas(output); final int color = 0xff424242; final Paint paint = new Paint(); final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); final RectF rectF = new RectF(rect); final float roundPx = pixels; paint.setAntiAlias(true); canvas.drawARGB(0, 0, 0, 0); paint.setColor(color); canvas.drawRoundRect(rectF, roundPx, roundPx, paint); paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); canvas.drawBitmap(bitmap, rect, rect, paint); return output; } public byte managerId() { return IMAGE_ID; } }