編輯:關於Android編程
上一篇我們分析了Volley框架的源碼,知道了使用Volley框架的三個步驟,如果你對此還不是很熟,建議能看看上一篇博文:android-----Volley框架源碼分析,這篇我們將首先使用Volley框架的ImageLoader來實現加載圖片的功能,接著從源碼角度來分析加載流程;
使用ImageLoader來加載圖片步驟:
(1)創建一個RequestQueue對象;
(2)創建一個ImageLoader對象;
(3)獲取一個ImageListener對象(通過ImageLoader的getImageListener方法來獲取);
(4)調用ImageLoader的get方法來加載圖片;
創建一個RequestQueue,很簡單,使用Volley的靜態方法newRequestQueue
//創建RequestQueue隊列 RequestQueue queue = Volley.newRequestQueue(this);創建一個ImageLoader對象
ImageLoader loader = new ImageLoader(queue,imageCache);其中imageCache是一個ImageCache類型對象,這裡我們定義一個實現ImageCache接口的類
class MyImageCache implements ImageCache { public LruCache他的構造函數會傳入一個LruCache對象,並定義了getBitmap和putBitmap充分使用了LruCache內存緩存lruCache; public MyImageCache(LruCache lruCache) { this.lruCache = lruCache; } @Override public Bitmap getBitmap(String url) { Bitmap bitmap = lruCache.get(url); if(bitmap != null) return bitmap; return null; } @Override public void putBitmap(String url, Bitmap bitmap) { if(lruCache.get(url) == null) lruCache.put(url, bitmap); } }
LruCache的定義:
//獲得可用內存的大小 int size = (int) (Runtime.getRuntime().maxMemory()/1024); //設置LruCache緩存的大小 int cacheSize = size/8; LruCache最後就是創建ImageListener對象了lruCache = new LruCache (cacheSize){ @Override protected int sizeOf(String key, Bitmap value) { //返回每個圖片占用的字節數(單位是KB) return value.getByteCount()/1024; } };
//創建ImageListener對象 ImageListener listener = ImageLoader.getImageListener(mImageView,R.drawable.before, R.drawable.now);getmageListener有三個參數,第一個參數指的是被加載圖片顯示的控件,第二個參數指的是默認顯示圖片的ID,第三個參數指的是加載圖片失敗的情況下所要顯示圖片的ID
之後就是調用ImageLoader的get方法來獲取圖片了
//調用ImageLoader的get方法獲取圖片 loader.get("http://.......", listener);至此,ImageLoader加載圖片流程已經走完,接下來我們從源碼角度看看具體流程中的細節:
(1)創建RequestQueue,這部分的源碼之前已經分析過了,在此不再贅述;
(2)創建ImageLoader對象:
public ImageLoader(RequestQueue queue, ImageCache imageCache) { mRequestQueue = queue; mCache = imageCache; }
這個構造函數第一個參數就是我們前面創建的RequestQueue隊列,第二個參數是一個實現了ImageCache接口的類的實例:
public interface ImageCache { public Bitmap getBitmap(String url); public void putBitmap(String url, Bitmap bitmap); }
可以看到ImageCache定義了兩個方法getBitmap和putBitmap,這兩個方法就是我們在定義實現ImageCache接口的類的時候需要實現的。具體可以用來將Bitmap添加至緩存或者從緩存中讀取Bitmap圖片了;
(3)接著就是調用ImageLoader的getImageListener獲得一個ImageListener對象了:
public interface ImageListener extends ErrorListener { public void onResponse(ImageContainer response, boolean isImmediate); }可以看到ImageListener是一個接口,並且同時他繼承自ErrorListener接口,這個接口位於Response類中:
public interface ErrorListener { public void onErrorResponse(VolleyError error); }那麼也就是說在我們定義ImageListener的時候需要實現:onResponse和onErrorResponse這兩個方法;
我們來看看getImageListener是怎麼返回ImageListener對象的:
public static ImageListener getImageListener(final ImageView view, final int defaultImageResId, final int errorImageResId) { return new ImageListener() { @Override public void onErrorResponse(VolleyError error) { if (errorImageResId != 0) { view.setImageResource(errorImageResId); } } @Override public void onResponse(ImageContainer response, boolean isImmediate) { if (response.getBitmap() != null) { view.setImageBitmap(response.getBitmap()); } else if (defaultImageResId != 0) { view.setImageResource(defaultImageResId); } } }; }
getImageListener有三個參數,第一個是我們想要顯示圖片的控件,第二個參數指的是默認加載的圖片的資源id,第三個參數是加載圖片失敗之後顯示的圖片的資源id,看到第3行開始創建了ImageListener對象,並且實現了onErrorResponse方法,這個方法會在圖片加載失敗之後調用,而onResponse方法就是在圖片加載成功之後調用了,那麼這兩個方法具體是由誰調用的呢?待會馬上就能看到啦!
有了ImageListener之後,接下來就是調用ImageLoader的get方法來獲取圖片,先來看看get方法,這個方法會傳入請求圖片所在的地址以及ImageListener對象:
public ImageContainer get(String requestUrl, final ImageListener listener) { return get(requestUrl, listener, 0, 0); }他會調用四個參數的get方法:
public ImageContainer get(String requestUrl, ImageListener imageListener, int maxWidth, int maxHeight) { // only fulfill requests that were initiated from the main thread. throwIfNotOnMainThread(); final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight); // Try to look up the request in the cache of remote images. Bitmap cachedBitmap = mCache.getBitmap(cacheKey); if (cachedBitmap != null) { // Return the cached bitmap. ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null); imageListener.onResponse(container, true); return container; } // The bitmap did not exist in the cache, fetch it! ImageContainer imageContainer = new ImageContainer(null, requestUrl, cacheKey, imageListener); // Update the caller to let them know that they should use the default bitmap. imageListener.onResponse(imageContainer, true); // Check to see if a request is already in-flight. BatchedImageRequest request = mInFlightRequests.get(cacheKey); if (request != null) { // If it is, add this request to the list of listeners. request.addContainer(imageContainer); return imageContainer; } // The request is not already in flight. Send the new request to the network and // track it. Request newRequest = new ImageRequest(requestUrl, new Listener() { @Override public void onResponse(Bitmap response) { onGetImageSuccess(cacheKey, response); } }, maxWidth, maxHeight, Config.RGB_565, new ErrorListener() { @Override public void onErrorResponse(VolleyError error) { onGetImageError(cacheKey, error); } }); mRequestQueue.add(newRequest); mInFlightRequests.put(cacheKey, new BatchedImageRequest(newRequest, imageContainer)); return imageContainer; }
get方法的四個參數分別表示:第一個參數是所請求的圖片地址,第二個參數是我們的ImageListener對象,第三個參數指的是返回圖片的最大寬度,設置為0表示寬度等於圖片本身寬度,不會對其采取任何壓縮措施,第四個參數指的是返回圖片的最大高度,設置為0表示高度等於圖片本身高度;
在正式的講解get方法源碼之前,我們需要首先了解一下ImageLoader裡面的ImageContainer和BatchedImageRequest這兩個類是用來做什麼的,因為這兩者在get方法中都用到了:
先來看ImageContainer:
public class ImageContainer { private Bitmap mBitmap; private final ImageListener mListener; private final String mCacheKey; private final String mRequestUrl; /** * Constructs a BitmapContainer object. * @param bitmap The final bitmap (if it exists). * @param requestUrl The requested URL for this container. * @param cacheKey The cache key that identifies the requested URL for this container. */ public ImageContainer(Bitmap bitmap, String requestUrl, String cacheKey, ImageListener listener) { mBitmap = bitmap; mRequestUrl = requestUrl; mCacheKey = cacheKey; mListener = listener; } public void cancelRequest() { if (mListener == null) { return; } BatchedImageRequest request = mInFlightRequests.get(mCacheKey); if (request != null) { boolean canceled = request.removeContainerAndCancelIfNecessary(this); if (canceled) { mInFlightRequests.remove(mCacheKey); } } else { // check to see if it is already batched for delivery. request = mBatchedResponses.get(mCacheKey); if (request != null) { request.removeContainerAndCancelIfNecessary(this); if (request.mContainers.size() == 0) { mBatchedResponses.remove(mCacheKey); } } } } public Bitmap getBitmap() { return mBitmap; } public String getRequestUrl() { return mRequestUrl; } }
這個類其實就是對圖片請求的相關數據進行了封裝而已,包括圖片對象、請求路徑url、圖片緩存key(這個key值裡面即包含請求圖片的地址以及顯示的這個圖片寬和高尺寸限制,是通過getCacheKey方法拼裝的)、圖片回調監聽器,還包括一個取消請求的方法,我們來看看cancelRequest做了些什麼事,第29行會首先從mInFlifhtRequests中取出key值為mCacheKey的BatchedImageRequest,mInFlifhtRequests是HashMap
public boolean removeContainerAndCancelIfNecessary(ImageContainer container) { mContainers.remove(container); if (mContainers.size() == 0) { mRequest.cancel(); return true; } return false; }可以看到首先他會從ImageContainer集合中刪除當前ImageContainer,接著判斷集合中是否還有元素,有的話返回false,沒有的話會直接將當前BatchedImageRequest暫停掉並且返回true,回到ImageContainer的cancelRequest方法,removeContainerAndCancelIfNecessary的返回值會賦給canceled,如果canceled為true的話表示在當前BatchedImageRequest下已經沒有ImageContainer請求了,那麼第33行直接將其從正在運行的圖片請求集合中移除當前請求;對應mCacheKey的請求本身就不存在於正在運行的圖片請求集合mInFlifhtRequests中的話,則會進入35行的else子句到mBatchedResponses從查看是否存在對應於mCacheKey的BatchedImageRequest對象存在,mBatchedResponses也是一個HashMap
前面分析的時候用到了BatchedImageRequest,那麼我們有必要來看看BatchedImageRequest到底是什麼了:
private class BatchedImageRequest { private final Request mRequest; //返回的圖片 private Bitmap mResponseBitmap; //返回的出錯信息 private VolleyError mError; //對於同一個url的request請求的有效ImageContainer列表 private final LinkedListBatchedImageRequest其實就是對網絡請求的封裝,跟ImageContainer不同的是,ImageContainer是對數據的封裝,BatchedImageRequest是對請求的封裝,假如我們有三個請求,但是他們請求同一地址的圖片的話,對應的會產生三個ImageContainer對象,但是BatchedImageRequest對象卻只有一個的,也即一個BatchedImageRequest請求可以被多個ImageContainer,這點做的目的是為了減少對同一url圖片的請求,達到了過濾掉重復url的目的,這裡面的removeContainerAndCancelIfNecessary在之前已經分析過了,而addContainer就是向當前BatchedImageRequest請求的mContainers集合中添加ImageContainer對象;mContainers = new LinkedList (); /** * Constructs a new BatchedImageRequest object * @param request The request being tracked * @param container The ImageContainer of the person who initiated the request. */ public BatchedImageRequest(Request request, ImageContainer container) { mRequest = request; mContainers.add(container); } public void setError(VolleyError error) { mError = error; } public VolleyError getError() { return mError; } public void addContainer(ImageContainer container) { mContainers.add(container); } /** * Detatches the bitmap container from the request and cancels the request if no one is * left listening. * @param container The container to remove from the list * @return True if the request was canceled, false otherwise. */ public boolean removeContainerAndCancelIfNecessary(ImageContainer container) { mContainers.remove(container); if (mContainers.size() == 0) { mRequest.cancel(); return true; } return false; } }
接下來,我們回到ImageLoader的get方法裡面繼續分析,第4行會查看是否是在主線程中運行的,第6行通過getCacheKey方法生成包含有requestUrl、maxWidth、maxHeight三個內容的對象,接著在第9行通過剛剛生成的cacheKey來查看我們的一級緩存中是否有對應於cacheKey的Bitmap存在,這裡的一級緩存就是我們在創建ImageLoader對象的時候傳遞進來的實現了ImageCache接口的對象,一般使用LruCache,在第10行判斷獲得的cacheBitmap是否存在,如果存在的話進入if語句塊,通過ImageContainer封裝一個ImageContainer對象,這個對象第一個參數攜帶有剛剛從緩存中取出的Bitmap對象以及請求的url地址,接著調用ImageListener對象的onResponse方法,而這個對象是通過getImageListener創建的,那麼相應的onResponse方法也應該調用的getImageListener裡面的,源碼如下:
@Override public void onResponse(ImageContainer response, boolean isImmediate) { if (response.getBitmap() != null) { view.setImageBitmap(response.getBitmap()); } else if (defaultImageResId != 0) { view.setImageResource(defaultImageResId); } }可以看出這種情況下我們調用的將是view.setImageBitmap(response.getBitmap());這一句,將從緩存獲取到的圖片顯示在了ImageView控件上,回到get方法中,當onResponse執行結束後,執行14行return語句返回;如果一級緩存中不存在對應cacheKey的Bitmap,則接著執行18行創建一個默認的ImageContainer對象,注意這個ImageContainer對象的Bitmap參數值等於null,並且同樣執行ImageListener的onRespose方法,同樣走到getImageListener的onRespose方法裡面,此時執行的將是view.setImageResource(defaultImageResId);這句代碼,顯示一張默認的圖片到ImageView上面;執行到25行會到當前正在執行的圖片請求集合中對應於cacheKey的請求,26行進行判斷如果當前正在執行的圖片請求集合中存在此cacheKey的請求,那麼只會將當前ImageLoader對象加入到當前請求中,這樣做的目的是過濾掉重復的url,讓對於同一url的請求位於一個各自的集合中,執行結束之後在第29行返回;如果一級緩存中不存在並且當前運行的請求中也不存在對應於cacheKey請求的話,那麼這時候只能創建新的Request請求出來了,也就是第34行創建ImageRequest的代碼了,ImageRequest會傳入6個參數,第一個是我們請求的url地址,第2個是請求成功的回調接口,第3個是我們所要顯示的圖片的最大寬度,如果圖片寬度大於這個值,則會進行壓縮,設置為0表示按照圖片的實際大小顯示,第4個參數是顯示圖片的最大高度,第5個參數是圖片的解碼編碼,在這裡使用的是Config.RGB_565,也就是每個像素點會占用2個字節,有時我們也會用ARGB_8888編碼,這種解碼圖片方法每個像素點占用4個字節,第6個參數是請求失敗所要回調的接口;有了ImageRequest對象之後會在第48行通過RequestQueue的add方法將其添加到請求隊列中去,這裡的源碼在上一篇博客已經分析過啦,同時在第49行將當前新生成的請求加入到了正在運行的圖片請求集合中去,防止以後新建請求可能出現的url重復;
那麼ImageRequest中請求成功和請求失敗的方法是怎麼回調的呢?別急,馬上來分析,get方法的第38行的onGetImageSuccess方法就是請求成功的回調方法,那麼我們應該來看看這個方法裡面做些什麼事了:
protected void onGetImageSuccess(String cacheKey, Bitmap response) { // cache the image that was fetched. mCache.putBitmap(cacheKey, response); // remove the request from the list of in-flight requests. BatchedImageRequest request = mInFlightRequests.remove(cacheKey); if (request != null) { // Update the response bitmap. request.mResponseBitmap = response; // Send the batched response batchResponse(cacheKey, request); } }第3行會首先將我們請求成功的圖片添加到一級緩存中,同時在第6行從運行的請求集合中移出當前cacheKey對應的請求,如果移出的請求不為空的話,則更新BatchedImageRequest的mResponseBitmap屬性,使其始終是最新的圖片,接著執行batchResponse方法,這個方法是用來分發同一時間被阻塞的相同的ImageRequest對應的ImageContainer:
private void batchResponse(String cacheKey, BatchedImageRequest request) { mBatchedResponses.put(cacheKey, request); // If we don't already have a batch delivery runnable in flight, make a new one. // Note that this will be used to deliver responses to all callers in mBatchedResponses. if (mRunnable == null) { mRunnable = new Runnable() { @Override public void run() { for (BatchedImageRequest bir : mBatchedResponses.values()) { for (ImageContainer container : bir.mContainers) { // If one of the callers in the batched request canceled the request // after the response was received but before it was delivered, // skip them. if (container.mListener == null) { continue; } if (bir.getError() == null) { container.mBitmap = bir.mResponseBitmap; container.mListener.onResponse(container, false); } else { container.mListener.onErrorResponse(bir.getError()); } } } mBatchedResponses.clear(); mRunnable = null; } }; // Post the runnable. mHandler.postDelayed(mRunnable, mBatchResponseDelayMs); } }首先將BatchedImageRequest添加到待處理的BatchedImageRequest的hashmap中,也即mBatchedResponses,接著開啟線程遍歷mBatchedResponses,依次獲得其中的BatchedImageRequest對象,接著遍歷BatchedImageRequest,獲得其中的ImageContainer對象,如果請求成功的話執行ImageContainer中的mListener屬性的onResponse方法,這個方法同樣也是之前getImageListener的onRespose,將圖片顯示在ImageView上面;如果請求失敗的話,會執行ImageContainer中的mListener屬性的onErrorResponse方法,這個方法就是調用之前getImageListener方法中的onErrorResponse:
@Override public void onErrorResponse(VolleyError error) { if (errorImageResId != 0) { view.setImageResource(errorImageResId); } }這個方法會在ImageView上面顯示加載失敗之後的默認圖片,最後通過Handler的post方法發送消息,至於這裡使用postDelayed的原因我在想可能是為了等待UI線程中的圖片顯示完成,即保證多個post發送消息順序,減少主線程的壓力;
至此,使用Volley來的ImageLoader加載圖片的源碼分析完畢了;
下面我們做一下總結:
(1)使用ImageLoader加載圖片,在其內部的話會用到ImageContainer和BatchedImageRequest這兩個類,其中BatchedImageRequest主要用於對請求進行封裝,ImageContainer主要用於對數據進行封裝;
(2)一個BatchedImageRequest請求可能對應於多個ImageContainer數組,因此BatchedImageRequest裡面有一個ImageContainer類型的LinkedList,這樣做的話很大程度上過濾掉了相同的url;
(3)ImageLoader中存在著兩個緩存請求的HashMap,分別是正在運行的請求緩存已經待處理的請求緩存,當正在運行的請求執行結束之後會進行分發操作來執行待處理的請求;
以上就是我個人對ImageLoader源碼的分析,希望大家能夠就不對的地方批評指正!!!!!
Context的方法getCacheDirgetFilesDirgetExternalCacheDirgetExternalFilesDir特點1:無需權限將assets
在項目中,難免會遇到這種需求,在程序運行時需要動態根據條件來決定顯示哪個View或某個布局,最通常的想法就是把需要動態顯示的View都先寫在布局中,然後把它們的可見性設為
一、繪制三角形在上一篇文章中,我們已經新建了虛擬方向鍵的自定義控件DirectionKeys類,下面我們繼續。本項目中的虛擬方向鍵的背景是4個三角形組成的矩形,其實是4個
POI搜索有三種方式,根據范圍和檢索詞發起范圍檢索poiSearchInbounds,城市poi檢索poiSearchInCity,周邊檢索poiSearchNearBy