Volley 源码解析之图片请求

一、前言

上篇文章我们分析了网络请求,这篇文章分析对图片的处理操作,如果没看上一篇,可以先看上一篇文章Volley 源码解析之网络请求。Volley 不仅仅对请求网络数据作了良好的封装,还封装了对图片的加载等操作,虽然比不上glidefresco ,不过可以满足我们的日常使用,从学习者的角度看看是怎么封装的。

二、简单使用

  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);
    复制代码

三、源码分析

一、 ImageRequest

首先我们分析ImageRequest,直接分析这个类,代码不多,直接继承Request,那么不用说跟上一篇我们分析的网络请求的request大体相同,不同的是这个是请求图片,如果我们需要自定义大小那么这里就对图片进行了裁剪以满足我们的大小:

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) {
            // 小于传入的次要最大值,则返回计算的最大值
            if ((resized * ratio) < maxSecondary) {
                resized = (int) (maxSecondary / ratio);
            }
            return resized;
        }
        //  其它scaleType值,要小于传入的值,则计算的次要值大于传入的次要最大值,则返回缩放的值
        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);

            // 生成的bitmap如果大于期望的宽或者高,那么缩小到可以接受的尺寸
            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返回,对于我们传入的宽高会按比例裁剪,不是直接使用裁剪到合适的值,不然会有拉伸,最后再回调给用户。

二、 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<>();
复制代码

接下来我们看看关键的一步

    @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中
接下来我们看看真正发起请求的地方:

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方法:

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中所有的请求。

三、 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

四、总结

三个图片加载,是一个套一个,后面利用前面的类辅助完成,我建议使用ImageLoader来加载图片,可以自己设置缓存,两级缓存,可扩展性强。总体来讲封装的不错,可以满足日常使用,对一些细节处理的比较好,比如相同的请求等,其中的裁剪和压缩都是常规操作,相信大家都很熟悉。

参考

Android Volley完全解析(二),使用Volley加载网络图片
Volley 源码解析

猜你喜欢

转载自juejin.im/post/5c1c84ade51d4535f05a192c