Volley学习(三)ImageRequest、ImageLoader、NetworkImageView源码简读

#########################ImageRequest####ImageRequest####################################


对应ImageRequest跟StringRequest、JsonRequest是一样的,我们看下ImageRequest的源码

    /**
     * Creates a new image request, decoding to a maximum specified width and
     * height. If both width and height are zero, the image will be decoded to
     * its natural size. If one of the two is nonzero, that dimension will be
     * clamped and the other one will be set to preserve the image's aspect
     * ratio. If both width and height are nonzero, the image will be decoded to
     * be fit in the rectangle of dimensions width x height while keeping its
     * aspect ratio.
     *
     * @param url URL of the image
     * @param listener Listener to receive the decoded bitmap
     * @param maxWidth Maximum width to decode this bitmap to, or zero for none
     * @param maxHeight Maximum height to decode this bitmap to, or zero for
     *            none
     * @param decodeConfig Format to decode the bitmap to
     * @param errorListener Error listener, or null to ignore errors
     */
    public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight,
            Config decodeConfig, Response.ErrorListener errorListener) {
        super(Method.GET, url, errorListener);
        setRetryPolicy(new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES, IMAGE_BACKOFF_MULT));
        mListener = listener;
        mDecodeConfig = decodeConfig;
        mMaxWidth = maxWidth;
        mMaxHeight = maxHeight;
    }



构造器中的参数英文是如何解释的呢?以下是百度翻译给的解释:

创建一个新的图像请求,解码到一个最大指定的宽度和高度。如果两个宽度和高度都为零,图像将被解码为其自然大小。如果其中的一个是非零,则该维度将是钳位和另一个将被设置为保存图像的方面比。如果两个宽度和高度都是非零的,图像将被解码为在保持它的同时,在尺寸宽度x高度的矩形纵横比。


url、listener、errorListener不用解释了,跟StringRequest、JsonRequest含义一样

其他的参数为:图片的最大宽度,图片的最大高度,图片的颜色属性


构造器中还有一个请求失败重连的方法

 setRetryPolicy(new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES, IMAGE_BACKOFF_MULT));
    public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
        mCurrentTimeoutMs = initialTimeoutMs;
        mMaxNumRetries = maxNumRetries;
        mBackoffMultiplier = backoffMultiplier;
    }
  /** Socket timeout in milliseconds for image requests */
    private static final int IMAGE_TIMEOUT_MS = 1000;

    /** Default number of retries for image requests */
    private static final int IMAGE_MAX_RETRIES = 2;

    /** Default backoff multiplier for image requests */
    private static final float IMAGE_BACKOFF_MULT = 2f;


 设定超时时间:1000ms;
    最大的请求次数:2次;
    发生冲突时的重传延迟增加数:2f(这个应该和TCP协议有关,冲突时需要退避一段时间,然后再次请求);

以下就是返回 Response<Bitmap>的代码

    private Response<Bitmap> doParse(NetworkResponse response) {
        byte[] data = response.data;
        BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
        Bitmap bitmap = null;
        if (mMaxWidth == 0 && mMaxHeight == 0) {
            decodeOptions.inPreferredConfig = mDecodeConfig;
            bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
        } else {
            // If we have to resize this image, first get the natural bounds.
            decodeOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
            int actualWidth = decodeOptions.outWidth;
            int actualHeight = decodeOptions.outHeight;

            // Then compute the dimensions we would ideally like to decode to.
            int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
                    actualWidth, actualHeight);
            int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
                    actualHeight, actualWidth);

            // Decode to the nearest power of two scaling factor.
            decodeOptions.inJustDecodeBounds = false;
            // TODO(ficus): Do we need this or is it okay since API 8 doesn't support it?
            // decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;
            decodeOptions.inSampleSize =
                findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
            Bitmap tempBitmap =
                BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);

            // If necessary, scale down to the maximal acceptable size.
            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));
        }
    }


如果mMaxWidth == 0 && mMaxHeight == 0就返回原尺寸

如果不满足上述情况,就先获取图片的实际(注意是实际的)高度、宽度

int actualWidth = decodeOptions.outWidth; int actualHeight = decodeOptions.outHeight;


然后通过如下代码获取所需要的图片大小

 int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
                    actualWidth, actualHeight);
            int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
                    actualHeight, actualWidth);


我们就看看这个方法做了什么操作?

 private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,
            int actualSecondary) {
        // If no dominant value at all, just return the actual.
        if (maxPrimary == 0 && maxSecondary == 0) {
            return actualPrimary;
        }

        // If primary is unspecified, scale primary to match secondary's scaling ratio.
        if (maxPrimary == 0) {
            double ratio = (double) maxSecondary / (double) actualSecondary;
            return (int) (actualPrimary * ratio);
        }

        if (maxSecondary == 0) {
            return maxPrimary;
        }

        double ratio = (double) actualSecondary / (double) actualPrimary;
        int resized = maxPrimary;
        if (resized * ratio > maxSecondary) {
            resized = (int) (maxSecondary / ratio);
        }
        return resized;
    }


以上方法就是对图片的宽度、高度的实际大小根据需求的大小,进行宽度、高度的缩放,举个例子:

我的图片原本像素是:850x1200.

maxWidth = 0,maxHeight = 0时,最终得到的bitmap的宽高是850x1200

当maxWidth = 0,maxHeight = 600时,得到的bitmap是425x600.这就说明它会按照一个不为0的边的值,将图片进行等比缩放。

当maxWidth = 100,maxHeight = 600时,我们得到的bitmap竟然是100x141,是按照100进行等比缩小后的图片,而不是100x600.


缩放完毕,获取需求的宽高,然后通过findBestSampleSize,根据所需大小,实际大小,去获取一个最适宜的大小,然后decodeByteArray开始压缩,

decodeOptions.inSampleSize =
                findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
            Bitmap tempBitmap =
                BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);


压缩完毕后,如果不合适, 通过createScaledBitmap再次处理到合适的大小,并且返回一个图片即可

  if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
                    tempBitmap.getHeight() > desiredHeight)) {
                bitmap = Bitmap.createScaledBitmap(tempBitmap,
                        desiredWidth, desiredHeight, true);
                tempBitmap.recycle();
            } else {
                bitmap = tempBitmap;
            }



###########################ImageLoader####ImageLoader####################################


我们看完ImageRequest的代码后,我们在来看下Volley中的ImageLoader的源码,看看他们之间有什么区别和联系?

我们都用过Volley的ImageLoader加载图片,比如如下的代码:

ImageLoader imageLoader2 = new ImageLoader(  
                MyApplication.getHttpRequestQueue(), new BitMapCache());  
        // view视图,默认的图片,错误的图片  
        ImageListener listener = imageLoader1.getImageListener(imageview2,  
                R.drawable.ic_launcher, R.drawable.ic_launcher);  
        imageLoader2.get(url, listener);

那我们就先看ImageLoader的构造器是如何进行初始化的,都做了哪些工作?

  public ImageLoader(RequestQueue queue, ImageCache imageCache) {
        mRequestQueue = queue;
        mCache = imageCache;
<span style="font-size:14px;">    }</span>

参数为:请求队列、还有内存缓存ImageCache对象


首先简单看下ImageCache接口对象,大概意思就是一个url对应一个图片段,put、get就像是map键值对的大概意思。

  public interface ImageCache {
        public Bitmap getBitmap(String url);
        public void putBitmap(String url, Bitmap bitmap);
    }


 imageLoader2.get(url, listener); 我们就来看看get方法中的listener是个什么东东呢?

ImageListener listener = imageLoader1.getImageListener(imageview2,  
                R.drawable.ic_launcher, R.drawable.ic_launcher);  

我们就根据方法看看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);
                }
            }
        };
    }

直接返回一个接口对象如下:

  public interface ImageListener extends ErrorListener {
        public void onResponse(ImageContainer response, boolean isImmediate);
    }

这段代码的意思是:(百度翻译)

对imagelistener处理基本功能的默认实现,在接收到网络响应之前,显示一个默认图像,在哪个点,它将切换到实际的图像或错误图像。人工翻译的很清楚,通过参数也能知道个大概。


最后我们就可以看get方法的具体实现代码了如下:

    public ImageContainer get(String requestUrl, final ImageListener listener) {
        return get(requestUrl, listener, 0, 0);
    }

    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<Bitmap>() {
                @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;
    }

getCacheKey为一个图片的url生成一个String类型的标识cacheKey(这个应该是),然后mCache.getBitmap(cacheKey);去缓存获取有没有数据,如果有的话

if (cachedBitmap != null) {
            // Return the cached bitmap.
            ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
            imageListener.onResponse(container, true);
            return container;
        }


就在ImageListener 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);
                }
            }
        };
    }


mCache.getBitmap(cacheKey)如果缓存中没有对应的图片,就进行初始化一个ImageContainer(没有bitmap),然后直接回调,imageListener.onResponse(imageContainer, true);这里为了让大家在回调中判断,然后设置默认图片(所以,大家在自己实现listener的时候,别忘了判断resp.getBitmap()!=null)

 ImageContainer imageContainer =
                new ImageContainer(null, requestUrl, cacheKey, imageListener);
        imageListener.onResponse(imageContainer, true);

 else if (defaultImageResId != 0) {
                    view.setImageResource(defaultImageResId);
                }


 BatchedImageRequest request = mInFlightRequests.get(cacheKey);判断同一个key的请求是否已经存在,查看当前执行队列中是否有与之相同的请求,如果有,那么加入到批处理请求队列当中,由这个请求队列去处理这些相同的请求


如果key请求不存在,就创建一个请求,去请求图片

Request<?> newRequest =
            new ImageRequest(requestUrl, new Listener<Bitmap>() {
                @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));



 mInFlightRequests.put(cacheKey,new BatchedImageRequest(newRequest, imageContainer)); 请求创建后,加入到hashMap中,表明这个请求已经存在

我们就看看这个请求中的onGetImageSuccess和onGetImageError做了什么处理操作?

  private 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);
        }
    }



请求成功后,就存放到mCache中,方便下次取,(运用中我们可以结合LruCache),然后就remove掉cacheKey,remove方法返回那个请求,就进行判断,如果不为null,

   if (request != null) {
            request.setError(error);
            batchResponse(cacheKey, request);
        }

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);
        }
    }



将获取到的图片

container.mBitmap = bir.mResponseBitmap;
container.mListener.onResponse(container, false);

在去接口回调方法中去替换调那些请求前,显示的默认图片。


ImageLoader的代码还是比较复杂的,但是思路还是比较清晰的,总结如下:
1、通过ImageLoader的get方法获取图片,如果我们只想获取原始图片,不用关心大小,则只用传入url和Listen er,如果需要设置图片大小,那么传入你需要设置大大小
2、get方法中,先回去缓存中查找,如果命中,那么就直接放回,如果没有命中,那么就判断mInFlightRequests中是否有相同key的BatchedImageRequest,如果有则直接将 ImageConainer加入BatchedImageRequest的mContainres中,因为对于同一个key没有必要发送两次请求
3、如果在mInFlightRequest中没有此key,那么需要创建一个ImageRequest对象,并加入RequestQueue中,并使用ImageRequest创建一个BatchedImageRequest加入 mInFlightRequest
4、当请求返回后,将BatchedImageRequest从mInFlightRequest中移除,加入mBatchedResponses中,将返回结果返回给所有的ImageContainer
5、如果一个ImageContainer在收到返回结果之前就被cancel掉,那么需要将它从mInFlightRequest的mContainers中移除,如果移除后mContainers的size为0,说明这个请求只有

#############################NetworkImageView ########################################

我们看下public class NetworkImageView extends ImageView 跟ImageLoader有何联系?

我们学习demo时候是这样写的如下:

ImageLoader imageLoader1 = new ImageLoader(  
                MyApplication.getHttpRequestQueue(), new BitMapCache());  
        networkImageView.setDefaultImageResId(R.drawable.ic_launcher);  
        networkImageView.setErrorImageResId(R.drawable.ic_launcher);  
        networkImageView.setImageUrl(url, imageLoader1);  

它的构造函数里面,也是初始化一个ImageLoader,说明NetworkImageView也是用了ImageLoader的获取图片机制

    public void setImageUrl(String url, ImageLoader imageLoader) {
        mUrl = url;
        mImageLoader = imageLoader;
        // The URL has potentially changed. See if we need to load it.
        loadImageIfNecessary(false);
    }


既然是自定义的ImageView,所以说在控件的onLayout函数中布局完成,在调用这个函数

   @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        loadImageIfNecessary(true);
    }

那我们就看看这个方法(有注释,就不说明文字了)

    void loadImageIfNecessary(final boolean isInLayoutPass) {
    	 //获取imageView控件的长度和宽度
        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;
        }

        /**
         * 判断控件是否可用,比如宽度高度是否为0。。。
         */
        boolean isFullyWrapContent = wrapWidth && wrapHeight;
        if (width == 0 && height == 0 && !isFullyWrapContent) {
            return;
        }

        /**
         * 如果url为空,就默认加载一张图片
         *   private void setDefaultImageOrNull() {
        if(mDefaultImageId != 0) {
            setImageResource(mDefaultImageId);
        }
        else {
            setImageBitmap(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)) {
                // 如果请求是相同的url,那么直接return,数据的获取由缓存处理
                return;
            } else {
                //如果有一个预先存在的请求,请取消它,如果它是获取一个不同的网址
                mImageContainer.cancelRequest();
                setDefaultImageOrNull();
            }
        }

        // 计算最大图像宽度/高度使用而忽略wrap_content尺寸.
        int maxWidth = wrapWidth ? 0 : width;
        int maxHeight = wrapHeight ? 0 : height;

      /**
       * 接下来就是跟imageloader里面的请求代码思路一样了
       */
        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;
    }


到此Volley学习(三)ImageRequest、ImageLoader、NetworkImageView源码简读了一遍,也算是有个大概了解,

下一篇,就来封装下图片请求的缓存使用及其工具类(关于图片的)


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值