Volley 源碼解析之圖片請求

1、前言

上篇文章咱們分析了網絡請求,這篇文章分析對圖片的處理操做,若是沒看上一篇,能夠先看上一篇文章Volley 源碼解析之網絡請求。Volley 不只僅對請求網絡數據做了良好的封裝,還封裝了對圖片的加載等操做,雖然比不上glidefresco ,不過能夠知足咱們的平常使用,從學習者的角度看看是怎麼封裝的。android

2、簡單使用

  1. 使用ImageRequest加載圖片,用法跟請求網絡的用法差很少,只是構造request的參數不太同樣:
    String imageUrl = "https://pic1.zhimg.com/80/1a60ca062a1fe2f6d091cdd9749e9c68_hd.jpg";
     RequestQueue queue = Volley.newRequestQueue(this);
     ImageRequest imageRequest = new ImageRequest(imageUrl,
                        response -> imageView.setImageBitmap(response),
                        0, 0, ImageView.ScaleType.CENTER_CROP, Bitmap.Config.ARGB_8888,
                        error -> {});
     queue.add(imageRequest);
    複製代碼
    • 第一個參數是圖片地址沒啥說的
    • 第二個參數是成功的回調,返回一個bitmap
    • 第三和第四個參數則是圖片的最大的高度和寬度,0爲默認圖片大小,若是填寫的圖片最大的高度和寬度小於圖片的實際尺寸則會進行壓縮
    • 第五個值就是對圖片進行邊界縮放
    • 第六個參數是圖片的格式,經常使用的就是RGB_565ARGB_8888,前者每一個像素佔2個字節,後者每一個像素佔4個字節,後者成像質量高,有alpha通道,若是使用的是jpg,不須要alpha通道則可使用前者; 還有個ARGB_4444,不過已經廢棄了,在4.4之後默認轉成ARGB_8888ALPHA_8只有透明度,沒有顏色值,通常不多使用
    • 最後個參數就是錯誤的回調
  2. 使用ImageLoader加載圖片
    String imageUrl = "https://pic1.zhimg.com/80/1a60ca062a1fe2f6d091cdd9749e9c68_hd.jpg";
    RequestQueue queue = Volley.newRequestQueue(this);
    ImageLoader imageLoader = new ImageLoader(queue, new BitmapCache());
    ImageLoader.ImageListener imageListener = ImageLoader.getImageListener(imageView, 
         R.mipmap.ic_launcher, R.mipmap.ic_launcher_round);
    imageLoader.get(imageUrl,
         imageListener, 0, 0);
    private class BitmapCache implements ImageLoader.ImageCache{
         private LruCache<String, Bitmap> lruCache;
    
         public BitmapCache() {
             int maxSize = 10 * 1024 * 1024;
             lruCache = new LruCache<String, Bitmap>(maxSize) {
                 @Override
                 protected int sizeOf(String key, Bitmap bitmap) {
                     return bitmap.getRowBytes() * bitmap.getHeight();
                 }
             };
         }
    
         @Override
         public Bitmap getBitmap(String url) {
             return lruCache.get(url);
         }
    
         @Override
         public void putBitmap(String url, Bitmap bitmap) {
             lruCache.put(url, bitmap);
         }
    }
    複製代碼
  3. 使用NetworkImageView加載圖片
    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     tools:context=".MainActivity">
    
     <Button
         android:id="@+id/button"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginStart="8dp"
         android:layout_marginTop="88dp"
         android:layout_marginEnd="8dp"
         android:text="Button"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent" />
    
     <com.android.volley.toolbox.NetworkImageView
         android:id="@+id/imageView"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginStart="8dp"
         android:layout_marginTop="32dp"
         android:layout_marginEnd="8dp"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@+id/textView"
         tools:srcCompat="@tools:sample/avatars" />
    </android.support.constraint.ConstraintLayout>
    複製代碼
    String imageUrl = "https://pic1.zhimg.com/80/1a60ca062a1fe2f6d091cdd9749e9c68_hd.jpg";
     NetworkImageView networkImageView = findViewById(R.id.imageView);
     networkImageView.setDefaultImageResId(R.mipmap.ic_launcher);
     networkImageView.setErrorImageResId(R.mipmap.ic_launcher_round);
     networkImageView.setImageUrl(imageUrl, imageLoader);
    複製代碼

3、源碼分析

1、 ImageRequest 分析

首先咱們分析ImageRequest,直接分析這個類,代碼很少,直接繼承Request,那麼不用說跟上一篇咱們分析的網絡請求的request大致相同,不一樣的是這個是請求圖片,若是咱們須要自定義大小那麼這裏就對圖片進行了裁剪以知足咱們的大小:git

public class ImageRequest extends Request<Bitmap> {
    //圖片請求的超時時間,單位毫秒
    public static final int DEFAULT_IMAGE_TIMEOUT_MS = 1000;

    //圖片請求的默認重試次數
    public static final int DEFAULT_IMAGE_MAX_RETRIES = 2;

    //發生衝突時的默認重傳延遲增長數,和TCP協議有關係,退避算法,短期的重複請求失敗還會是失敗
    public static final float DEFAULT_IMAGE_BACKOFF_MULT = 2f;

    //對mListener加鎖,保證線程安全,避免取消的時候同時執行分發
    private final Object mLock = new Object();

    @GuardedBy("mLock")
    @Nullable
    private Response.Listener<Bitmap> mListener;

    private final Config mDecodeConfig;
    private final int mMaxWidth;
    private final int mMaxHeight;
    private final ScaleType mScaleType;

    //Bitmap 的同步解析鎖,保證一個時間內只有一個Bitmap被加載到內存進行解析,避免多個同時解析oom
    private static final Object sDecodeLock = new Object();

   
    public ImageRequest(
            String url,
            Response.Listener<Bitmap> listener,
            int maxWidth,
            int maxHeight,
            ScaleType scaleType,
            Config decodeConfig,
            @Nullable Response.ErrorListener errorListener) {
        super(Method.GET, url, errorListener);
        setRetryPolicy(
                new DefaultRetryPolicy(
                        DEFAULT_IMAGE_TIMEOUT_MS,
                        DEFAULT_IMAGE_MAX_RETRIES,
                        DEFAULT_IMAGE_BACKOFF_MULT));
        mListener = listener;
        mDecodeConfig = decodeConfig;
        mMaxWidth = maxWidth;
        mMaxHeight = maxHeight;
        mScaleType = scaleType;
    }

    @Deprecated
    public ImageRequest(
            String url,
            Response.Listener<Bitmap> listener,
            int maxWidth,
            int maxHeight,
            Config decodeConfig,
            Response.ErrorListener errorListener) {
        this(
                url,
                listener,
                maxWidth,
                maxHeight,
                ScaleType.CENTER_INSIDE,
                decodeConfig,
                errorListener);
    }

    @Override
    public Priority getPriority() {
        return Priority.LOW;
    }

    //根據ScaleType設置圖片大小
    private static int getResizedDimension(
            int maxPrimary,
            int maxSecondary,
            int actualPrimary,
            int actualSecondary,
            ScaleType scaleType) {

        // 若是主要值和次要的值爲0,就返回實際值,若是咱們計算寬度的指望值,
        那麼主要值就是寬度,高度就是次要值,反之亦然
        if ((maxPrimary == 0) && (maxSecondary == 0)) {
            return actualPrimary;
        }

        // 若是爲ScaleType.FIT_XY,填充整個矩形,忽略比值;
        即若是主要的值爲0則返回實際值,不然返回傳入的值
        if (scaleType == ScaleType.FIT_XY) {
            if (maxPrimary == 0) {
                return actualPrimary;
            }
            return maxPrimary;
        }

        // 若是主要的值爲0,則經過比例值計算出主要的值返回
        if (maxPrimary == 0) {
            double ratio = (double) maxSecondary / (double) actualSecondary;
            return (int) (actualPrimary * ratio);
        }

        // 次要的值爲0,下面的比例調整就是多餘的,那麼直接返回主要的值,
        if (maxSecondary == 0) {
            return maxPrimary;
        }

        // 圖片真實尺寸大小的比例,經過這個比例咱們能夠計算出次要的最大值,通
        過計算出的值和咱們傳遞進來的值作比較
        double ratio = (double) actualSecondary / (double) actualPrimary;
        int resized = maxPrimary;

        // 若是是ScaleType.CENTER_CROP,填充整個矩形,保持長寬比,這裏的寬高值相等或者大於傳入的寬高尺寸
        if (scaleType == ScaleType.CENTER_CROP) {
            // 小於傳入的次要最大值,則返回經過比例計算的最大值,這裏至關於把resized 值增大
            if ((resized * ratio) < maxSecondary) {
                resized = (int) (maxSecondary / ratio);
            }
            return resized;
        }
        //  其它scaleType值,若是計算的值大於次要值,那麼resized 值減少
        if ((resized * ratio) > maxSecondary) {
            resized = (int) (maxSecondary / ratio);
        }
        return resized;
    }

    //解析response
    @Override
    protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
        synchronized (sDecodeLock) {
            try {
                return doParse(response);
            } catch (OutOfMemoryError e) {
                VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl());
                return Response.error(new ParseError(e));
            }
        }
    }

    //解析的地方
    private Response<Bitmap> doParse(NetworkResponse response) {
        byte[] data = response.data;
        BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
        Bitmap bitmap = null;
        //若是最大寬度和最大高度都傳入的爲0,直接解析成一個bitmap
        if (mMaxWidth == 0 && mMaxHeight == 0) {
            decodeOptions.inPreferredConfig = mDecodeConfig;
            bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
        } else {
            //若是要調整圖片的大小,首先獲取圖片真實的尺寸大小,首先設置inJustDecodeBounds爲true,不加載到內存可是能夠獲取圖像的寬高
            decodeOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
            int actualWidth = decodeOptions.outWidth;
            int actualHeight = decodeOptions.outHeight;

            // 計算咱們想要的尺寸
            int desiredWidth =
                    getResizedDimension(
                            mMaxWidth, mMaxHeight, actualWidth, actualHeight, mScaleType);
            int desiredHeight =
                    getResizedDimension(
                            mMaxHeight, mMaxWidth, actualHeight, actualWidth, mScaleType);

            // 計算出採樣值,2的倍數
            decodeOptions.inJustDecodeBounds = false;
            decodeOptions.inSampleSize =
                    findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
            Bitmap tempBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);

            // 若是採樣率計算出的值爲1的話,那麼就沒有尺寸壓縮,tempBitmap的寬高值就是圖片的
            真實值,那麼這裏就須要縮放到知足咱們上面計算出來的值
            if (tempBitmap != null
                    && (tempBitmap.getWidth() > desiredWidth
                            || tempBitmap.getHeight() > desiredHeight)) {
                bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth, desiredHeight, true);
                tempBitmap.recycle();
            } else {
                bitmap = tempBitmap;
            }
        }

        //回調給用戶
        if (bitmap == null) {
            return Response.error(new ParseError(response));
        } else {
            return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
        }
    }

    @Override
    public void cancel() {
        super.cancel();
        synchronized (mLock) {
            mListener = null;
        }
    }

    @Override
    protected void deliverResponse(Bitmap response) {
        Response.Listener<Bitmap> listener;
        synchronized (mLock) {
            listener = mListener;
        }
        if (listener != null) {
            listener.onResponse(response);
        }
    }

    //計算合適的採樣率
    @VisibleForTesting
    static int findBestSampleSize(
            int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
        double wr = (double) actualWidth / desiredWidth;
        double hr = (double) actualHeight / desiredHeight;
        double ratio = Math.min(wr, hr);
        float n = 1.0f;
        while ((n * 2) <= ratio) {
            n *= 2;
        }

        return (int) n;
    }
}

複製代碼

關鍵部分我都寫了註釋,下面咱們看主要流程,對 Bitmap的高效加載。首先咱們獲取到返回的response進行解析,而後根據傳遞的指望寬高以及圖片格式生成Bitmap返回,對於咱們傳入的寬高會按比例裁剪,不是直接使用裁剪到合適的值,否則會有拉伸,最後再回調給用戶。github

2、ImageLoader 分析

咱們直接先看構造方法,看全部的關鍵地方,不重要的就不分析了算法

public ImageLoader(RequestQueue queue, ImageCache imageCache) {
        mRequestQueue = queue;
        mCache = imageCache;
}
複製代碼

沒啥說的,就是賦值,一個是請求隊列,一個是圖片緩存的本身實現,這個是在內部把請求添加到請求隊列,因此直接傳遞進去,第二個參數緩存,咱們能夠本身實現,通常使用LruCache實現。 接下來咱們接着看getImageListener:緩存

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);
                }
            }
        };
}
複製代碼

這個方法比較簡單,就是傳入咱們的image view進行設置圖像,而後分別提供一個默認和請求失敗的佔位圖,剛開始設置的時候尚未請求到Bitmap,因此最開始設置的事默認圖。
首先看兩個變量,後面須要用到:安全

//相同URL正在請求中存儲的map
private final HashMap<String, BatchedImageRequest> mInFlightRequests = new HashMap<>();
//相同URL請求結果存儲的map
private final HashMap<String, BatchedImageRequest> mBatchedResponses = new HashMap<>();
複製代碼

接下來咱們看看關鍵的一步bash

@MainThread
    public ImageContainer get(
            String requestUrl,
            ImageListener imageListener,
            int maxWidth,
            int maxHeight,
            ScaleType scaleType) {

        // 檢查當前線程是否在主線程,只知足從主線程發起的請求
        Threads.throwIfNotOnMainThread();

        //根據url、width、height、scaleType拼接的緩存key
        final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);

        // 從緩存中查找bitmap
        Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
        if (cachedBitmap != null) {
            // 有相應的緩存那麼則返回一個ImageContainer,包括其中的bitmap
            ImageContainer container =
                    new ImageContainer(
                            cachedBitmap, requestUrl, /* cacheKey= */ null, /* listener= */ null);
            // 直接調用onResponse,把bitmap設置給imageView
            imageListener.onResponse(container, true);
            return container;
        }

        // 緩存中沒有查找到,那麼咱們直接獲取,首先new一個ImageContainer
        ImageContainer imageContainer =
                new ImageContainer(null, requestUrl, cacheKey, imageListener);

        // 更新調用的地方,使用默認的圖片先設置
        imageListener.onResponse(imageContainer, true);
        //檢查是否有相同的cacheKey請求正在運行
        BatchedImageRequest request = mInFlightRequests.get(cacheKey);
        if (request != null) {
            // 若是相同的請求正在運行,那麼不須要重複請求,只須要將這個實例化
            的imageContainer添加到BatchedImageRequest的mContainers中,而後請
            求結束後對全部添加到集合中的imageContainer依次回調
            request.addContainer(imageContainer);
            return imageContainer;
        }

        // 若是這個請求還沒有執行,發送一個新的請求到網絡上,這裏纔是執行請求的地方
        Request<Bitmap> newRequest =
                makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType, cacheKey);
        //添加到請求隊列
        mRequestQueue.add(newRequest);
        //添加到正在請求的集合中
        mInFlightRequests.put(cacheKey, new BatchedImageRequest(newRequest, imageContainer));
        return imageContainer;
}
複製代碼

這個方法是一個重點,主要流程是首先看看緩存裏面有沒有緩存的Bitmap,來源於咱們本身實現的緩存策略,咱們使用的是內存緩存的話這裏就是一級緩存;若是有直接調用onResponse方法設置圖片,若是沒有,首先實例化ImageContainer,涉及到了幾個類,接下來就看是否有相同的請求,若是有則添加到一個集合中,請求下來統一處理;若是沒有那麼則構造一個Request,經過RequestQueue去獲取網絡圖片,多是網絡請求也有多是磁盤緩存的,這裏就是二級緩存,而後添加到正在請求的集合中。
接下來看一看ImageContainer這個類,這個類就是圖像請求的一個容器對象網絡

public class ImageContainer {
        //imageview加載的Bitmap
        private Bitmap mBitmap;
        
        //圖片加載成功和失敗的監聽
        private final ImageListener mListener;

        //緩存的key
        private final String mCacheKey;

        //請求指定的URL
        private final String mRequestUrl;

        public ImageContainer(
                Bitmap bitmap, String requestUrl, String cacheKey, ImageListener listener) {
            mBitmap = bitmap;
            mRequestUrl = requestUrl;
            mCacheKey = cacheKey;
            mListener = listener;
        }

        //取消請求
        @MainThread
        public void cancelRequest() {
            Threads.throwIfNotOnMainThread();

            if (mListener == null) {
                return;
            }
            //從正在請求的集合獲取一個批處理request
            BatchedImageRequest request = mInFlightRequests.get(mCacheKey);
            if (request != null) {
                //若是取到request,那麼首先從mContainers移除當前的這個ImageContainer,
                若是移除後集合爲空一個ImageContainer也沒有了,那麼則取消掉這個請求並返回 true
                boolean canceled = request.removeContainerAndCancelIfNecessary(this);
                if (canceled) {
                    //取消了請求則從正在請求的集合中移除BatchedImageRequest
                    mInFlightRequests.remove(mCacheKey);
                }
            } else {
                // 若是已經請求完成添加到批處理中準備處理分發
                request = mBatchedResponses.get(mCacheKey);
                if (request != null) {
                    //首先從mContainers移除當前的這個ImageContainer
                    request.removeContainerAndCancelIfNecessary(this);
                    if (request.mContainers.size() == 0) {
                        //若是集合中一個ImageContainer都沒有,則從等待處理的
                        response中移除掉這個BatchedImageRequest
                        mBatchedResponses.remove(mCacheKey);
                    }
                }
            }
        }

        public Bitmap getBitmap() {
            return mBitmap;
        }

        /** Returns the requested URL for this container. */
        public String getRequestUrl() {
            return mRequestUrl;
        }
    }
複製代碼

這個類主要包含是一個圖片的容器對象,裏面包括了bitmap、監聽器、緩存的key以及請求的URL,每一個請求都會先組裝這個類,而後添加到一個BatchedImageRequest的mContainers中
接下來咱們看看真正發起請求的地方:app

protected Request<Bitmap> makeImageRequest(
            String requestUrl,
            int maxWidth,
            int maxHeight,
            ScaleType scaleType,
            final String cacheKey) {
        return new ImageRequest(
                requestUrl,
                new Listener<Bitmap>() {
                    @Override
                    public void onResponse(Bitmap response) {
                        onGetImageSuccess(cacheKey, response);
                    }
                },
                maxWidth,
                maxHeight,
                scaleType,
                Config.RGB_565,
                new ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        onGetImageError(cacheKey, error);
                    }
                });
    }
    
    //圖片請求成功
    protected void onGetImageSuccess(String cacheKey, Bitmap response) {
        // 添加到以及緩存中
        mCache.putBitmap(cacheKey, response);

        // 從正在運行的請求列表中刪除這個請求
        BatchedImageRequest request = mInFlightRequests.remove(cacheKey);

        if (request != null) {
            //更新BatchedImageRequest的bitmap
            request.mResponseBitmap = response;
            //發送一個批處理請求,將多個相同的請求進行分發
            batchResponse(cacheKey, request);
        }
    }

    //圖片請求失敗,跟上面成功處理大體相似
    protected void onGetImageError(String cacheKey, VolleyError error) {
        BatchedImageRequest request = mInFlightRequests.remove(cacheKey);

        if (request != null) {
            //設置這個請求的錯誤
            request.setError(error);

            batchResponse(cacheKey, request);
        }
    }
複製代碼

這裏執行網絡請求仍是調用咱們上面分析的ImageRequest方法,並且在回調中分別對成功和失敗在進行了一次處理。
接下來咱們看看這個批量處理圖片的batchResponse方法:ide

private void batchResponse(String cacheKey, BatchedImageRequest request) {
        //首先添加到這個map中,代表如今進入了批處理中
        mBatchedResponses.put(cacheKey, request);
        // 若是尚未進行處理,那麼咱們則開始一個新的任務
        if (mRunnable == null) {
            mRunnable =
                    new Runnable() {
                        @Override
                        public void run() {
                            //循環mBatchedResponses的全部值
                            for (BatchedImageRequest bir : mBatchedResponses.values()) {
                                //循環BatchedImageRequest的mContainers的值
                                for (ImageContainer container : bir.mContainers) {
                                    //若是有的請求取消了,在收到請求的
                                    響應後尚未分發以前那麼跳過循環下一個
                                    if (container.mListener == null) {
                                        continue;
                                    }
                                    // 若是不是請求錯誤則調用onResponse
                                    if (bir.getError() == null) {
                                        container.mBitmap = bir.mResponseBitmap;
                                        container.mListener.onResponse(container, false);
                                    } else {
                                       //請求報錯則調用onErrorResponse設置一個錯誤的圖片展現 container.mListener.onErrorResponse(bir.getError());
                                    }
                                }
                            }
                            //清除全部響應的BatchedImageRequest
                            mBatchedResponses.clear();
                            //置爲null,經過是否爲null判斷當前是否正在處理
                            mRunnable = null;
                        }
                    };
            // 將這個post投遞到主線程去執行
            mHandler.postDelayed(mRunnable, mBatchResponseDelayMs);
        }
    }
複製代碼

這段代碼也很簡單,不過有個地方有個比較奇怪的地方,爲啥是使用雙層循環,爲啥不直接使用內層的循環;我的認爲有多是這樣,首先這個mBatchedResponses剛開始進來添加了相同的key的請求的BatchedImageRequest,那麼存在正在分發的時候又有不一樣的key的請求進來了,由於正在處理的時候runnable不爲null,則後續添加的有可能不能分發,因此要遍歷這個map中全部的請求。

3、 NetworkImageView 分析

這是一個繼承ImageView的自定義view

public class NetworkImageView extends ImageView {
    private String mUrl;
    //設置默認的圖片
    private int mDefaultImageId;
    //設置請求錯誤的時候顯示的圖片
    private int mErrorImageId;
    
    private ImageLoader mImageLoader;
    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
    @MainThread
    public void setImageUrl(String url, ImageLoader imageLoader) {
        Threads.throwIfNotOnMainThread();
        mUrl = url;
        mImageLoader = imageLoader;
        // 咱們的url可能已經更改,那麼咱們則須要判斷是否須要加載
        loadImageIfNecessary(/* isInLayoutPass= */ false);
    }

    public void setDefaultImageResId(int defaultImage) {
        mDefaultImageId = defaultImage;
    }

    public void setErrorImageResId(int errorImage) {
        mErrorImageId = errorImage;
    }

    //若是視圖還沒有加載圖像,那麼咱們則去加載它
    void loadImageIfNecessary(final boolean isInLayoutPass) {
        int width = getWidth();
        int height = getHeight();
        ScaleType scaleType = getScaleType();

        boolean wrapWidth = false, wrapHeight = false;
        if (getLayoutParams() != null) {
            wrapWidth = getLayoutParams().width == LayoutParams.WRAP_CONTENT;
            wrapHeight = getLayoutParams().height == LayoutParams.WRAP_CONTENT;
        }
        
        //若是不知道視圖的大小而且不是WRAP_CONTENT就暫停加載
        boolean isFullyWrapContent = wrapWidth && wrapHeight;
        if (width == 0 && height == 0 && !isFullyWrapContent) {
            return;
        }

        // 若是url爲空的話則請取消全部的請求,包括之前的請求,假如請求兩次
        最後次的url爲null,這時候還沒請求完成,確定以最後次爲準
        if (TextUtils.isEmpty(mUrl)) {
            if (mImageContainer != null) {
                mImageContainer.cancelRequest();
                mImageContainer = null;
            }
            //設置默認的圖片
            setDefaultImageOrNull();
            return;
        }

        // 檢查是否取消之前的請求
        if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
            if (mImageContainer.getRequestUrl().equals(mUrl)) {
                // 若是請求和之前相同則不必再次請求
                return;
            } else {
                // 若是存在正在請求的url而且請求url不一樣,那麼取消正在請求的url
                mImageContainer.cancelRequest();
                setDefaultImageOrNull();
            }
        }

        // 計算最大寬高,若是設置WRAP_CONTENT那麼則圖片是多大就是多大,其它
        狀況則直接使用佈局的寬高,若是設置了具體的值就有可能裁剪
        int maxWidth = wrapWidth ? 0 : width;
        int maxHeight = wrapHeight ? 0 : height;

        // 使用ImageLoader來請求圖像,上面已經分析了,最終返回一個ImageContainer
        mImageContainer =
                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) {
                                //isImmediate:在網絡請求過程當中調用的時候爲true,能夠用來
                                區分是不是取的緩存圖像仍是網絡圖像加載
                                isInLayoutPass:若是經過onLayout調用此函數,
                                則爲true,不然爲false
                                這個if的意思就是,若是是緩存圖像而且是在佈局中調用那麼則發送
                                到主線程並延遲設置圖像,由於可能屢次調用
                                if (isImmediate && isInLayoutPass) {
                                    post(
                                            new Runnable() {
                                                @Override
                                                public void run() {
                                                    onResponse(response, /* isImmediate= */ false);
                                                }
                                            });
                                    return;
                                }
                                //請求成功加載圖片
                                if (response.getBitmap() != null) {
                                    setImageBitmap(response.getBitmap());
                                } else if (mDefaultImageId != 0) {
                                    setImageResource(mDefaultImageId);
                                }
                            }
                        },
                        maxWidth,
                        maxHeight,
                        scaleType);
    }

    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(/* isInLayoutPass= */ true);
    }

    //當imageview銷燬的時候,取消請求而且清除ImageContainer以便從新加載圖像
    @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();
    }
}

複製代碼

這個類沒啥好分析的,就是利用前兩個類來完成請求,只不過方便的是直接在xml中使用,使用ImageLoader請求的Bitmap設置給NetworkImageView

4、總結

三種不一樣的方式均可以完成圖片的加載,不事後面的方式都比較依賴前面的ImageRequest,畢竟仍是要這個類去完成網絡請求操做;在使用中,根據不一樣的場景選擇不一樣的方式使用。不過我建議使用ImageLoader來加載圖片,能夠本身設置緩存,兩級緩存,一級內存緩存,一級volley請求時候的磁盤緩存。整體來說封裝的很不錯,對一些細節處理的比較好,好比相同的請求、圖片的裁剪等,值得咱們學習的地方不少。

參考

Android Volley徹底解析(二),使用Volley加載網絡圖片
Volley 源碼解析

相關文章
相關標籤/搜索