Volley框架中ImageLoader源码分析

研究完Volley框架的源码,继续来看看ImageLoader的内部是怎么实现的,据说是封装了ImageRequest,来探探究竟。

首先使用ImageLoader,两步

1、实例化ImageLoader

2、使用ImageLoader.get(String url,ImageLister listener)。


首先第一步很简单,构造参数为一个RequestQueue和一个ImageCache,RequestQueue在前一篇文章已经说明,那么ImageCache是什么呢:

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

很简单,一个缓存类的接口,只生命了get和put方法,让子类去定义如何读写缓存,Volley并没有提供默认的子类,用户可以使用LruCache去实现内存缓存,也可以使用LruCache+DiskLruCache去实现内存+磁盘缓存。靠用户定义,ImageLoader只需要从ImageCache中能够读写缓存就行。

ImageLoader构造方法:

public ImageLoader(RequestQueue queue, ImageCache imageCache) {
        mRequestQueue = queue;
        mCache = imageCache;
    }

只是给成员变量赋值而已。

第二步是使用get方法去加载图片,get方法有诸多重载,最后被调用的重载方法为:

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

可以看见参数有5个,不多解释意义,做过图片压缩的就知道那个maxWidth和maxHeight是用来做图片压缩时,计算inSampleSize的。如果不传这两个参数,那么默认都为0,即显示的图片不经过压缩。

这里需要解释几个变量和类:

CacheKey:标志请求的唯一性的Key,同时也是在Cache里面的Key,在ImageLoader中有一个getCacheKey方法:

private static String getCacheKey(String url, int maxWidth, int maxHeight, ScaleType scaleType) {
        return new StringBuilder(url.length() + 12).append("#W").append(maxWidth)
                .append("#H").append(maxHeight).append("#S").append(scaleType.ordinal()).append(url)
                .toString();
    }

可以看见参数为get方法中的5个参数,所以可以得知,一个url,maxWidth,maxHeight,scaleType都相同的Request被视为同一个Request,它们的CacheKey相同。CacheKey的计算不包括Listener,所以同一个请求可以传入不同的Listener。


ImageContainer:每次的get()方法被调用代表着一次请求,每一次的请求都用ImageContainer封装起来

 public class ImageContainer {
        /**
         * The most relevant bitmap for the container. If the image was in cache, the
         * Holder to use for the final bitmap (the one that pairs to the requested URL).
         */
        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;
        }

包装了这次请求的返回值BItmap,观察者Listener,CacheKey和URL。

BatchedImageRequest:由上述可知,CacheKey相同的请求被视为同一个 请求,因为url,maxWidth,maxHeight,scaleType都相同的请求,返回的Bitmap必是一样的,所以对于这些同样的请求,只需要做一次联网加载图片即可,所以就诞生了BatchedImageRequest,它将代表同一个请求的所有ImageContainer都收集起来,然后用一个Request去加载,返回的Bitmap就是所有ImageContainer所需要的Bitmap

private class BatchedImageRequest {
        /** 对这些请求的唯一一个Request对象*/
        private final Request<?> mRequest;

        /**请求的Bitmap */
        private Bitmap mResponseBitmap;

        /** 错误*/
        private VolleyError mError;

        /** 所有请求的封装集合 */
        private final LinkedList<ImageContainer> mContainers = new LinkedList<ImageContainer>();


        public BatchedImageRequest(Request<?> request, ImageContainer container) {
            mRequest = request;
            mContainers.add(container);
        }

        /**
         * Adds another ImageContainer to the list of those interested in the results of
         * the request.
         */
        public void addContainer(ImageContainer container) {
            mContainers.add(container);
        }
}
这样就可以继续去讲ImageLoader.get到底干了什么事:

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

        // 这里确保了get只能在主线程执行
        throwIfNotOnMainThread();
        //根据4个参数算出CacheKey
        final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);

        //检查该CacheKey对应的Bitmap是否在缓存中
        Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
        if (cachedBitmap != null) {
            // 如果在缓存中,直接读取,new 出一个ImageContainer,将Bitmap丢进去
            ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
            //通知Listener,然后返回
            imageListener.onResponse(container, true);
            return container;
        }

        // 如果缓存没有,new 出一个请求的封装,注意这里的Bitmap参数为空,等待加载
        ImageContainer imageContainer =
                new ImageContainer(null, requestUrl, cacheKey, imageListener);

        // 这里的意思是告诉调用者,imageContainer的Bitmap为空,需要让ImageView显示一个默认的图片然后等待加载
       //所以在调用端需要判断ImageContainer.bitmap是否为空
        imageListener.onResponse(imageContainer, true);

        //检查,这个chaceKey对应的请求是否是一个新的请求
        BatchedImageRequest request = mInFlightRequests.get(cacheKey);
        if (request != null) {
            // 如果此请求已经发生过,那么将ImageContainer放入BatchedImageRequest
            //说明这个请求的观察者又多了一个
            request.addContainer(imageContainer);
            return imageContainer;
        }

        // 如果这是一个新请求,则new出一个Request,放入RequestQueue,等待NetworkDispatcher加载
        Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType,
                cacheKey);
        mRequestQueue.add(newRequest);
        //然后把这个请求放入BatchedImageRequest,代表这个请求已经发起过了,以后再有同样的请求,不会再生成Request。
        mInFlightRequests.put(cacheKey,
                new BatchedImageRequest(newRequest, imageContainer));
        return imageContainer;
    }
上面的中文注释将过程已经说的很清楚了,唯一不清楚的可能就是makeImageRequest()发生了什么:

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);
            }
        });
    }
public class ImageRequest extends Request<Bitmap>

就是生成一个Request的实现类,ImageRequest而已。

然后的工作就是CacheDispatcher和NetworkDispatcher他们的了,无论从哪个方式获取到NetworkResponse,都会调用Request.parseNetworkResponse()来进行对返回值的加工:

   @Override
    protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
        // Serialize all decode on a global lock to reduce concurrent heap usage.
        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));
            }
        }
    }

doParse()主要做的事就是判断maxWidth和maxHeight是不是都为0,如果是就直接从NetworkResponse读取Bitmap返回,如果不是就说明要求做图片压缩,就进行图片压缩再返回压缩后的Bitmap。源码就不进去了

这个方法被调用后CacheDispatcher或者NetworkDispatcher会继续调用Delivery.postResponse(),从而使ImageRequest.deliverResponse()被调用

@Override
    protected void deliverResponse(Bitmap response) {
        mListener.onResponse(response);
    }

这个Listener是在哪里被传入的呢,还记得ImageLoader中,来了个新请求时,就创建一个新的ImageRequest并add进RequestQueue吗,就在上面的makeImageRequest方法中,可以看出listener.onResponse()方法调用了onGetImageSuccess:

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

这里干的事就是表示这个cacheKey对应的请求已经完成了,于是将BatchedImageRequest从队列中取出,然后进入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);
        }
    }

在这里就简单了,同一个请求有很多个发起者,就对该请求的所有ImageContainer.bitmap进行赋值,然后通知各自的listener。如果是error,调用的就是listener.onErrorResponse(),这样,对于该请求的所有发起者,都得到了结果。(该过程在被封装在一个Runnable中,被Handler送去主线程执行。)



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值