1. 簡介
現在android應用中不可避免的要使用圖片,有些圖片是可以變化的,需要每次啟動時從網路拉取,這種情境在有廣告位的應用以及純圖片應用(比如百度美拍)中比較多。
現在有一個問題:假如每次啟動的時候都從網路拉取圖片的話,勢必會消耗很多流量。在當前的狀況下,對於非wifi使用者來說,流量還是很貴的,一個很耗流量的應用,其使用者數量級肯定要受到影響。當然,我想,向百度美拍這樣的應用,必然也有其內部的圖片緩衝策略。總之,圖片緩衝是很重要而且是必須的。
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.java
/* * 圖片管理 * 非同步擷取圖片,直接調用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;}}