Volley之ImageLoader与NetworkImageView

简单使用

        ImageLoader是volley中用来加载图片的工具类。使用如下:

mImageLoader.get(IMAGE_URL, ImageLoader.getImageListener(mImageView,
         R.drawable.def_image, R.drawable.err_image))

        创建ImageLoader时,需要一个ImageCache对象,它用于图片的缓存。可以利用该对象实现内存以及本地文件缓存。

源码及注释

常用子类

    public class ImageContainer {
        /**
         * 请求获取到的Bitmap对象
         */
        private Bitmap mBitmap;

        private final ImageListener mListener;// 请求的回调监听

        /** 请求对应的key值 */
        private final String mCacheKey;

        /** 请求要访问的url */
        private final String mRequestUrl;
        它只是将发起的一个请求封装成一个类。这样能保证不同请求之间数据的准确性以及使用的方便性。
    private class BatchedImageRequest {
        /** 真正的要执行网络操作的Request */
        private final Request<?> mRequest;

        /** 网络请求结束后返回的Bitmap,这个bitmap是要展示在界面上的 */
        private Bitmap mResponseBitmap;

        /** Error if one occurred for this response */
        private VolleyError mError;

        /** 所有与mRequest相关的ImageContainer对象 */
        private final LinkedList<ImageContainer> mContainers = new LinkedList<ImageContainer>();
        同一个cacheKey对应的请求可能会被发起多次,因此将后发起的请求统计起来,只发起一次网络请求,并等网络请求结束后对所有的请求进行统一处理,有利于减少网络访问次数节约流量。Volley中对所有的网络请求的处理方式都是这样的!
        BatchedImageRequest对应的就是批请求,主要用于将多次发起的请求进行封装。之所以有一个Bitmap对象,是因为Request中并不会保存返回的Bitmap。

常用属性

    /**
     * HashMap of Cache keys -> BatchedImageRequest used to track in-flight requests so
     * that we can coalesce multiple requests to the same URL into a single network request.
     */
    private final HashMap<String, BatchedImageRequest> mInFlightRequests =
            new HashMap<String, BatchedImageRequest>();
        缓存请求。这样可以将多次相同URL的请求合并成一次网络访问,减少对流量的消耗以及节约时间。

get方法

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

        // 非主线程就扔一个异常,因此get方法必须在主线程中调用
        throwIfNotOnMainThread();

        final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);

        // 先在缓存中查找
        Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
        if (cachedBitmap != null) {
            // 缓存中有,则返回缓存中的数据
            ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
            imageListener.onResponse(container, true);
            return container;
        }

        // 缓存中没有,则去获取
        ImageContainer imageContainer =
                new ImageContainer(null, requestUrl, cacheKey, imageListener);

        // 通知listener展示默认图片。listener分析在下面。
        imageListener.onResponse(imageContainer, true);

        // 有可能同一个请求会发生多次,因此需要将请求进行缓存分类。某个请求成功后,则将通知所有发起过的请求进行更新。
        // 避免对同一个请求进行多次处理,造成流量浪费。
        BatchedImageRequest request = mInFlightRequests.get(cacheKey);
        if (request != null) { //已有该请求,则将本次请求存储进来,等到请求结束后统一通知处理。
            // 
            request.addContainer(imageContainer);
            return imageContainer;
        }

        // 缓存中没有,并且该请求也没有正在进行,则新建一个ImageRequest去请求。
        Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType,
                cacheKey);

        mRequestQueue.add(newRequest); // 与上步合在一起,执行网络请求获取数据。
        mInFlightRequests.put(cacheKey,
                new BatchedImageRequest(newRequest, imageContainer)); 
        return imageContainer;
    }

        get方法主要对一个请求进行了三次处理:1,缓存中有,直接获取;2,缓存中没有,但已有同样cacheKey的请求正在执行,则存储该ImageContainer,并等待执行的请求执行完毕后统一处理;3,上述都不满足,那就用ImageRequest进行网络处理。

        get并不是一个阻塞式的方法,它会很迅速地返回一个ImageContainer。而NetworkImageView正是利用这一点解决图片错位的。

        makeImageRequest方法只是返回一个ImageRequest对象,如:

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

onGetImageRequest()

    protected void onGetImageSuccess(String cacheKey, Bitmap response) {
        // 缓存得到的结果
        mCache.putBitmap(cacheKey, response);

        // 请求完成后,将请求从缓存中移除
        BatchedImageRequest request = mInFlightRequests.remove(cacheKey);

        if (request != null) {
            // 更新请求中存储的bitmap
            request.mResponseBitmap = response;

            // 把网络请求结果分发到各个请求中
            batchResponse(cacheKey, request);
        }
    }

batchResponse

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

        先用一个Map存储,然后隔mBatchResponseDelayMs后再执行runnable——不知道为啥这么做。

        在Runnable中,通过两次循环会取出所有的ImageContainer,然后调用其中封装的listener将结果返回。

getImageListener

        在get方法中需要一个ImageListener对象,可以通过getImageListener获取。该listener是得到要显示的Bitmap后进行显示操作的。

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

        通过上面的get请求走下来,imageContainer中的bitmap有可能为null,此时就显示默认的图片。如果正常网络请求成功的话,imageContainer中的bitmap不为null,就正常显示即可。

        ImageLoader并不能保证加载ListView时图片不错位。因为listview加载时,ImageView是复用的,这导致了getImageListener中的ImageView是对应多个请求的。解决办法参考

总结

        同一个URL的多次请求,必须进行缓存。

NetworkImageView

        使用ImageLoader加载图片时,ImageView复用会导致图片错位。可以使用上述的方法解决,也可以使用volley中提供的NetworkImageView。

使用

        通过实例调用setImageUrl即可,在ListView等存在imageView复用的控件中,需要不停地调用setImageUrl。

源码及注释

        setImageUrl内部核心方法就是调用loadImageIfNecessary,如下:

void loadImageIfNecessary(final boolean isInLayoutPass) {
        //关于宽高的判断,略

        // 新设置的url为空,但该imageview在本次调用setImageUrl之前已经开始加载别的url,所以需要取消原来的加载,显示成默认的。
        // 因为本次url为空,所以肯定加载不成功。
        if (TextUtils.isEmpty(mUrl)) {
            if (mImageContainer != null) {
                mImageContainer.cancelRequest();
                mImageContainer = null;
            }
            setDefaultImageOrNull();
            return;
        }

        // 本次setImageUrl之前已经开始加载过,就根据实际情况判断是否要取消。
        if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
            if (mImageContainer.getRequestUrl().equals(mUrl)) {
                // 本次Url与以前url相同,不进行取消,因为这不会导致错位。
                return;
            } else {
                // 本次Url与原来的不同,所以需要取消原来的加载——避免图片错位。
                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;

        // 执行本次的setImageUrl的加载——走到这步时已经取消了上次的加载,因此不会导致错位问题。
        // get方法不是阻塞式的,即调用第一次setImageUrl时就会有ImageContainer返回,并不会因为
        // 阻塞或者多线程导致上面关于mImageContainer非空判断失效,从而又导致错位问题。
        // 并且通过ImageLoader的get方法可以知道,get返回的ImageContainer
        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, scaleType);

        // update the ImageContainer to be the new bitmap container.
        mImageContainer = newContainer;
    }
        NetworkImageView给出了一种解决图片错位的思路:取消旧有的加载。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值