編輯:關於Android編程
通過前面淺析(一)和淺析(二)的分析,相信大家對於Volley有了初步的認識,但是如果想更深入的理解,還需要靠大家多多看源碼。
這篇文章中我們主要來研究一下使用Volley框架請求大量圖片的原理,在Android的應用中,通過http請求獲取的數據主要有三類:
1、json
2、xml
3、Image
其中json和xml的獲取其實原理很簡單,使用Volley獲取感覺有點大財小用了,了解Volley獲取圖片的原理才是比較有意義的,因為裡面涉及到很多知識點,比如獲取大量圖片如何防止OOM。
那麼我們就開始研究源碼吧。
(1) ImageLoader.java
通過它的名字我們就知道是用來加載Image的工具類
/** 通過調用ImageLoader的get方法就可以獲取到圖片,然後通過一個Listener回調,將圖片設置到ImgeView中(這個方法務必在主線程中調用) */ public class ImageLoader { /** 前面已經接觸過,請求隊列(其實不是真實的隊列,裡面包含了本地隊列和網絡隊列) */ private final RequestQueue mRequestQueue; /** 圖片緩沖,這個緩存不是前面提到的磁盤緩存,這個是內存緩存,我們可以通過LruCache實現這個接口 */ private final ImageCache mCache; /** * 用於存放具有相同cacheKey的請求 */ private final HashMapmInFlightRequests = new HashMap (); /** 用於存放具有相同Key,並且返回了數據的請求*/ private final HashMap mBatchedResponses = new HashMap (); /** Handler to the main thread. */ private final Handler mHandler = new Handler(Looper.getMainLooper()); /** Runnable for in-flight response delivery. */ private Runnable mRunnable; /** * Simple cache adapter interface. If provided to the ImageLoader, it * will be used as an L1 cache before dispatch to Volley. Implementations * must not block. Implementation with an LruCache is recommended. */ public interface ImageCache { public Bitmap getBitmap(String url); public void putBitmap(String url, Bitmap bitmap); } /** * 構造函數需要傳入一個RequestQueue對象和一個內存緩存對象 * @param queue The RequestQueue to use for making image requests. * @param imageCache The cache to use as an L1 cache. */ public ImageLoader(RequestQueue queue, ImageCache imageCache) { mRequestQueue = queue; mCache = imageCache; } /** * 用於圖片獲取成功或者失敗的回調 * @param imageView 需要設置圖片的ImageView. * @param defaultImageResId 默認顯示圖片. * @param errorImageResId 出錯時顯示的圖片. */ 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); } } }; } /** * 判斷圖片是否已經緩存,不同尺寸的圖片的cacheKey是不一樣的 * @param requestUrl 圖片的url * @param maxWidth 請求圖片的寬度. * @param maxHeight 請求圖片的高度. * @return 返回true則緩存. */ public boolean isCached(String requestUrl, int maxWidth, int maxHeight) { throwIfNotOnMainThread(); String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight); return mCache.getBitmap(cacheKey) != null; } /** * 這個方法時個核心方法,我們主要通過它來獲取圖片 * * @param requestUrl The URL of the image to be loaded. * @param defaultImage Optional default image to return until the actual image is loaded. */ public ImageContainer get(String requestUrl, final ImageListener listener) { return get(requestUrl, listener, 0, 0); } /** * 這個方法比上面方法多了兩個參數,如果傳入則圖片大小會做相應處理,如果不傳默認為0,圖片大小不做處理 * @param requestUrl The url of the remote image * @param imageListener The listener to call when the remote image is loaded * @param maxWidth The maximum width of the returned image. * @param maxHeight The maximum height of the returned image. * @return A container object that contains all of the properties of the request, as well as * the currently available image (default if remote is not loaded). */ public ImageContainer get(String requestUrl, ImageListener imageListener, int maxWidth, int maxHeight) { // only fulfill requests that were initiated from the main thread. throwIfNotOnMainThread(); //獲取key,其實就是url,width,height按照某種格式拼接 final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight); // 首先從緩存裡面取圖片 Bitmap cachedBitmap = mCache.getBitmap(cacheKey); if (cachedBitmap != null) { // 如果緩存命中,則直接放回 ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null); imageListener.onResponse(container, true); return container; } // 沒有命中,則創建一個ImageContainer,注意此時圖片數據傳入的null, ImageContainer imageContainer = new ImageContainer(null, requestUrl, cacheKey, imageListener); // 這就是為什麼在onResponse中我們需要判斷圖片數據是否為空,此時就是為空的 imageListener.onResponse(imageContainer, true); // 判斷同一個key的請求是否已經存在 BatchedImageRequest request = mInFlightRequests.get(cacheKey); if (request != null) { // 如果存在,則直接加入request中,沒有必要對一個key發送多個請求 request.addContainer(imageContainer); return imageContainer; } // 發送一個請求,並加入RequestQueue 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); VolleyLog.e(-------------->+newRequest.getSequence()); //加入到HashMap中,表明這個key已經存在一個請求 mInFlightRequests.put(cacheKey, new BatchedImageRequest(newRequest, imageContainer)); return imageContainer; } /** * Handler for when an image was successfully loaded. * @param cacheKey The cache key that is associated with the image request. * @param response The bitmap that was returned from the network. */ private void onGetImageSuccess(String cacheKey, Bitmap response) { // 獲取圖片成功,放入緩存 mCache.putBitmap(cacheKey, response); // 將cacheKey對應的請求從mInFlightRequests中移除 BatchedImageRequest request = mInFlightRequests.remove(cacheKey); if (request != null) { // Update the response bitmap. request.mResponseBitmap = response; // Send the batched response batchResponse(cacheKey, request); } } /** * Handler for when an image failed to load. * @param cacheKey The cache key that is associated with the image request. */ private void onGetImageError(String cacheKey, VolleyError error) { // Notify the requesters that something failed via a null result. // Remove this request from the list of in-flight requests. BatchedImageRequest request = mInFlightRequests.remove(cacheKey); if (request != null) { // Set the error for this request request.setError(error); // Send the batched response batchResponse(cacheKey, request); } } /** * Container object for all of the data surrounding an image request. */ public class ImageContainer { /** * 保存從網絡獲取的圖片 */ private Bitmap mBitmap; private final ImageListener mListener; /** The cache key that was associated with the request */ private final String mCacheKey; /** The request URL that was specified */ 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; } //判斷此key對應的請求有沒有 BatchedImageRequest request = mInFlightRequests.get(mCacheKey); if (request != null) { /**如果存在,request中mContainers中的這個Container,如果mContainers的size為0,那麼 removeContainerAndCancelIfNecessary返回true */ boolean canceled = request.removeContainerAndCancelIfNecessary(this); if (canceled) { //如果返回true,那麼說明沒有任何一個ImageView對這個請求感興趣,需要移除它 mInFlightRequests.remove(mCacheKey); } } else { // 判斷是否這個request已經成功返回了 request = mBatchedResponses.get(mCacheKey); if (request != null) { request.removeContainerAndCancelIfNecessary(this); if (request.mContainers.size() == 0) { //如果已經成功返回,並且沒有ImageView對他感興趣,那麼刪除它 mBatchedResponses.remove(mCacheKey); } } } } /** * Returns the bitmap associated with the request URL if it has been loaded, null otherwise. */ public Bitmap getBitmap() { return mBitmap; } /** * Returns the requested URL for this container. */ public String getRequestUrl() { return mRequestUrl; } } /** * 對Request的一個包裝,將所有有共同key的請求放入一個LinkedList中 */ private class BatchedImageRequest { /** The request being tracked */ private final Request mRequest; /** The result of the request being tracked by this item */ private Bitmap mResponseBitmap; /** Error if one occurred for this response */ private VolleyError mError; /** 存放具有共同key的ImageContainer*/ private final LinkedList 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); } /** * Set the error for this response */ public void setError(VolleyError error) { mError = error; } /** * Get the error for this response */ public VolleyError getError() { return mError; } /** * Adds another ImageContainer to the list of those interested in the results of * the request. */ public void addContainer(ImageContainer container) { mContainers.add(container); } /** * 移除一個ImageContainer,如果此時size==0,那麼需要從mInFlightRequests中移除該BatchedImageRequest * @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; } } /** * 當請求返回後,將BatchedImageRequest放入到mBatchedResponses,然後將結果發送給所有具有相同key的ImageContainer,ImageContainer通過裡面的Listener發送到ImageView,從而顯示出來 * @param cacheKey The cacheKey of the response being delivered. * @param request The BatchedImageRequest to be delivered. * @param error The volley error associated with the request (if applicable). */ 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); } } /** * 獲取一個請求的key,拼接規則就是使用#講幾個連接起來 * @param url The URL of the request. * @param maxWidth The max-width of the output. * @param maxHeight The max-height of the output. */ private static String getCacheKey(String url, int maxWidth, int maxHeight) { return new StringBuilder(url.length() + 12).append(#W).append(maxWidth) .append(#H).append(maxHeight).append(url).toString(); } }
ImageLoader的代碼還是比較復雜的,但是思路還是比較清晰的,總結如下:
如果我們僅僅是獲取少量圖片,Volley框架為我們提供了一個NetworkImageView,這個類繼承自ImageView,使用時,我們只需要調用setImageUrl即可,下面就來看其實現機制
(2) NetworkImageView.java
public class NetworkImageView extends ImageView { /** 需要加載圖片的url */ private String mUrl; /** * 默認顯示圖片的id */ private int mDefaultImageId; /** * 錯誤圖片的id */ private int mErrorImageId; /** ImageLoader對象,其實就是用該對象去獲取圖片,所以了解了ImageLoader後,這個類很好理解 */ private ImageLoader mImageLoader; /**把這個對象當成url和Listener的封裝即可 */ private ImageContainer mImageContainer; public NetworkImageView(Context context) { this(context, null); } public NetworkImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public NetworkImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } /** * 設置Url * * @param url The URL that should be loaded into this ImageView. * @param imageLoader ImageLoader that will be used to make the request. */ public void setImageUrl(String url, ImageLoader imageLoader) { mUrl = url; mImageLoader = imageLoader; // 這個方法我們後面分析 loadImageIfNecessary(false); } /** * Sets the default image resource ID to be used for this view until the attempt to load it * completes. */ public void setDefaultImageResId(int defaultImage) { mDefaultImageId = defaultImage; } /** * Sets the error image resource ID to be used for this view in the event that the image * requested fails to load. */ public void setErrorImageResId(int errorImage) { mErrorImageId = errorImage; } /** * 這個方法在onLayout方法中傳入true,其他地方傳入false * @param isInLayoutPass True if this was invoked from a layout pass, false otherwise. */ void loadImageIfNecessary(final boolean isInLayoutPass) { int width = getWidth(); int height = getHeight(); boolean wrapWidth = false, wrapHeight = false; if (getLayoutParams() != null) { wrapWidth = getLayoutParams().width == LayoutParams.WRAP_CONTENT; wrapHeight = getLayoutParams().height == LayoutParams.WRAP_CONTENT; } // if the view's bounds aren't known yet, and this is not a wrap-content/wrap-content // view, hold off on loading the image. boolean isFullyWrapContent = wrapWidth && wrapHeight; if (width == 0 && height == 0 && !isFullyWrapContent) { return; } // if the URL to be loaded in this view is empty, cancel any old requests and clear the // currently loaded image. if (TextUtils.isEmpty(mUrl)) { if (mImageContainer != null) { mImageContainer.cancelRequest(); mImageContainer = null; } setDefaultImageOrNull(); return; } // if there was an old request in this view, check if it needs to be canceled. if (mImageContainer != null && mImageContainer.getRequestUrl() != null) { if (mImageContainer.getRequestUrl().equals(mUrl)) { //如果請求url相同,則直接return return; } else { // 請求url不同,則cancel,並顯示默認圖片或者不顯示圖片 mImageContainer.cancelRequest(); setDefaultImageOrNull(); } } // Calculate the max image width / height to use while ignoring WRAP_CONTENT dimens. int maxWidth = wrapWidth ? 0 : width; int maxHeight = wrapHeight ? 0 : height; //調用了get方法 ImageContainer newContainer = mImageLoader.get(mUrl, new ImageListener() { @Override public void onErrorResponse(VolleyError error) { if (mErrorImageId != 0) { setImageResource(mErrorImageId); } } @Override public void onResponse(final ImageContainer response, boolean isImmediate) { // If this was an immediate response that was delivered inside of a layout // pass do not set the image immediately as it will trigger a requestLayout // inside of a layout. Instead, defer setting the image by posting back to // the main thread. if (isImmediate && isInLayoutPass) { post(new Runnable() { @Override public void run() { onResponse(response, false); } }); return; } if (response.getBitmap() != null) { setImageBitmap(response.getBitmap()); } else if (mDefaultImageId != 0) { setImageResource(mDefaultImageId); } } }, maxWidth, maxHeight); // update the ImageContainer to be the new bitmap container. mImageContainer = newContainer; } private void setDefaultImageOrNull() { if(mDefaultImageId != 0) { setImageResource(mDefaultImageId); } else { setImageBitmap(null); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); loadImageIfNecessary(true); } @Override protected void onDetachedFromWindow() { if (mImageContainer != null) { // If the view was bound to an image request, cancel it and clear // out the image from the view. mImageContainer.cancelRequest(); setImageBitmap(null); // also clear out the container so we can reload the image if necessary. mImageContainer = null; } super.onDetachedFromWindow(); } @Override protected void drawableStateChanged() { super.drawableStateChanged(); invalidate(); } }
最近項目有一個需求,需要多層可滑動控件的嵌套展示,demo效果如下,demo的下載地址在最後 咋一看好像挺簡單啊,不就是一個ScrollView + ViewP
小米edge什麼時候上市?相信很多米粉對於小米edge手機很是期待和關注,紛紛上網咨詢,下文介紹小米edge上市時間,一起和小編來了解下吧! 小米edge
簡介 引入OpenCV4Android的目標是在Raknet框架下解決視頻通訊的問題,目前在ubuntu下已成功實現,現在把它引用到Android平台下。 OpenCV是
說在前面的話:這篇文章是看了如何優雅地使用NDK後,對原博客功能的補充。為了方便大家的閱讀順序,直接添加到原博客之中,如有侵犯版權,請聯系我。這篇博客有轉載,有原創,只是