Volley源码分析(四)——ImageLoader

来源:互联网 发布:4.5铅弹规格尺寸数据 编辑:程序博客网 时间:2024/06/05 16:48

Volley框架中有一个ImageLoader类,用于加载图片,其使用方法如下:

 RequestQueue requestQueue = Volley.newRequestQueue(this);        ImageLoader imageLoader = new ImageLoader(requestQueue, new ImageLoader.ImageCache() {            LruCache<String, Bitmap> cache = new LruCache<>(20);            @Override            public Bitmap getBitmap(String url) {                return cache.get(url);            }            @Override            public void putBitmap(String url, Bitmap bitmap) {                cache.put(url, bitmap);            }        });        imageLoader.get("url", new ImageLoader.ImageListener() {            @Override            public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {                new ImageView(MainActivity.this).setImageBitmap(response.getBitmap());            }            @Override            public void onErrorResponse(VolleyError error) {            }        });

可以看到如果要初始化一个ImageLoader,那么需要提供RequestQueue用于存放请求,还要一个内存缓存缓存图片。下面具体分一下ImageLoader的用法。

ImageLoader#构造方法

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

ImageCache是一个接口,用于提供一级缓存,其定义如下:

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

ImageLoader#get()方法

 public ImageContainer get(String requestUrl, ImageListener imageListener,            int maxWidth, int maxHeight) {        // 如果不在主线程,那么直接抛出异常        throwIfNotOnMainThread();        //得到URL+width+height的缓存键值        final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);        // 首先尝试从内存缓存中得到Bitmap        Bitmap cachedBitmap = mCache.getBitmap(cacheKey);        //如果缓存命中        if (cachedBitmap != null) {            // 返回缓存的图片            ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);            //接口回调            imageListener.onResponse(container, true);            //返回            return container;        }        // Bitmap不在内存中,那么需要从网络上获取        ImageContainer imageContainer =                new ImageContainer(null, requestUrl, cacheKey, imageListener);        // 让用户知道应该使用默认的Bitmap        imageListener.onResponse(imageContainer, true);        // 检查是否已经已经发送过该请求        BatchedImageRequest request = mInFlightRequests.get(cacheKey);        //如果已经发送了该请求        if (request != null) {            //返回            request.addContainer(imageContainer);            return imageContainer;        }        // 该请求不在执行,那么创建一个ImageRequest请求图片,设置回调接口        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);                }            });        //添加进ReqeustQueue中        mRequestQueue.add(newRequest);        //将该请求保存进mInFlightRequests        mInFlightRequests.put(cacheKey,                new BatchedImageRequest(newRequest, imageContainer));        return imageContainer;    }

上面的代码中,大体流程还是比较清晰的:
1. 首先尝试从内存缓存中获取图片,如果缓存命中,那么直接返回图片;
2. 如果内存缓存没有命中,那么判断该请求是否正在被执行,如果正在被执行,也直接返回;
3. 如果请求没有被执行,那么创建ImageRequest请求图片。

下面就整个方法中几个细节方法进行说明。

ImageLoader#getCacheKey()

getCacheKey()用于获取L1缓存的键值,其实现如下:

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

上面的比较抽象,举个例子。如果请求的URL是“http://www.baidu.com”,宽度和高度都是0,那么得到的键值将会是“32#W0#W0http://www.baidu.com”。该键值只是用作L1缓存的键值。
这样得到的键值就会存在一个问题,就是同一个图片URL,但是两次存入的宽高不同,那么得到的键值将会不同,将会走两次其他流程。

ImageContainer类

ImageContainer类保存一个图片请求中的相关对象,其主要字段如下:

//图片private Bitmap mBitmap;//图片成功的回调          private final ImageListener mListener;//缓存键值private final String mCacheKey;//请求的URLprivate final String mRequestUrl;

ImageContainer的主要字段就是这些,另外提供获取Bitmap、URL的方法,除了这两个方法还有一个cancelRequest()取消请求的方法,其实现如下:

ImageContainer#cancelRequest()

 public void cancelRequest() {            //如果没有监听器,直接返回。没有监听器的话,就算获得了用户也不知道            if (mListener == null) {                return;            }            //判断该请求是否正在执行            BatchedImageRequest request = mInFlightRequests.get(mCacheKey);            //如果请求正在执行,那么尝试取消            if (request != null) {                //移除与Request关联的ImageContainer和取消                boolean canceled = request.removeContainerAndCancelIfNecessary(this);                if (canceled) {                    mInFlightRequests.remove(mCacheKey);                }            }            //如果请求还没有执行            else {                //检查一下是否已经分发给了                request = mBatchedResponses.get(mCacheKey);                //如果还没有分发                if (request != null) {                    //删除ImageContainer                    request.removeContainerAndCancelIfNecessary(this);                    //如果BatchedIamgeRequest不与ImageContainer关联了,那么该请求已经没有意义了,从BatchedResponse中删除。                    if (request.mContainers.size() == 0) {                        mBatchedResponses.remove(mCacheKey);                    }                }            }        }

BatchedImageRequest类

ImageLoader中有一个叫做mInFlightRequests的HashMap,用于保存正在执行的请求,其定义如下:

private final HashMap<String, BatchedImageRequest> mInFlightRequests =            new HashMap<String, BatchedImageRequest>();

BatchedImageRequest类用于封装一个请求和多个ImageContainer的关系,可以想象这种一种场景,同一个图片请求,得到的该图片可能需要在多个IamgeView中设置,每一个Bitmap使用ImageContainer封装,BatchedImageRequest封装的是1个Request对应多个ImageContainer,其实现如下:

private class BatchedImageRequest {private final Request<?> mRequest;private Bitmap mResponseBitmap;private VolleyError mError;private final LinkedList<ImageContainer> mContainers = new LinkedList<ImageContainer>();}

因为BatchedImageRequest是Request和ImageContainer的一对多关系,而上面提到了ImageContainer的cancelRequest()方法中,会调用BatchedImageRequest的removeContainerAndCancelifNecessary()方法,可以想象一下,一旦BatchedImageRequest中删除一个关联的ImageContainer,如果BatchedImageRequest一旦没有ImageContainer关联了,那么这个请求就没有意义,也就应该取消了。让我们看一下这个方法是不是这样实现的:

public boolean removeContainerAndCancelIfNecessary(ImageContainer container) {            //移除本ImageContainer            mContainers.remove(container);            //如果没有IamgeContainer关联了,那么取消请求,返回true            if (mContainers.size() == 0) {                mRequest.cancel();                return true;            }            //只要还有ImageContainer与之关联,那么返回false            return false;        }

可以看到,该方法的实现果然如上面的猜想。下图是Request、ImageContainer、BatchedImageRequest之间的关系。
BatchedImageRequest关系

ImageLoader#onGetImageSuccess()

当创建ImageRequest并成功被Volley执行成功后,将会进入回调,而回调中调用了onGetImageSuccess()方法,其实现如下:

private void onGetImageSuccess(String cacheKey, Bitmap response) {        //放进L1缓存        mCache.putBitmap(cacheKey, response);        // 从正在执行的HashMap中移除该请求        BatchedImageRequest request = mInFlightRequests.remove(cacheKey);        //如果请求不为null        if (request != null) {            // 更新图片            request.mResponseBitmap = response;            // 发送到监听器            batchResponse(cacheKey, request);        }    }

可以看到onGetImageSuccess中完成了将图片放进L1缓存的操作,然后需要将BatchedImageRequest移除,接下来看一下batchResonse()方法是做什么的。

ImageLoader#batchResponse()

private void batchResponse(String cacheKey, BatchedImageRequest request) {        //放进已执行完的HashMap中        mBatchedResponses.put(cacheKey, request);        //初始时为null,创建一个Runnable,用于发送到UI线程        if (mRunnable == null) {            mRunnable = new Runnable() {                @Override                public void run() {                    //遍历所有已执行完的BatchImageRequest                    for (BatchedImageRequest bir : mBatchedResponses.values()) {                        //分发给所有的ImageContainer                        for (ImageContainer container : bir.mContainers) {                            //如果不存在监听器,则无所谓                            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();                    //重置为null                    mRunnable = null;                }            };            // 将Runnbale发送给主线程的Handler            mHandler.postDelayed(mRunnable, mBatchResponseDelayMs);        }    }

由于Handler发送采取了一个延时,那么一旦有一个请求完成了,需要让BatchedResoinse中所有的请求全部都回调掉。需要注意的是batchResponse方法已经在主线程执行了,然后再在主线程中使用Handler发送了一个消息。
经过上面的分析,可以发现ImageLoader内部使用两个HashMap保存请求,一个用于保存正在执行的BatchedImageRequest,一个用于保存已经执行完成的BatchedImageRequest。一旦请求完成了,就会从正在执行的HashMap中删除,而如果一旦执行好的请求被处理了,那么会从后一个HashMap中删除。两个HashMap保存了Request的三种状态:正在执行,执行完,分发完。
onGetImageSuccess方法已经在主线程中执行了,为什么不直接交给监听器回调,而是通过一个Handler又发了一个消息?难道是因为Handler采取了延迟发送,这样可以一下子处理多个请求,而有时会休眠,提高性能?这块不太想得通

阅读全文
0 0
原创粉丝点击