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之间的关系。
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采取了延迟发送,这样可以一下子处理多个请求,而有时会休眠,提高性能?这块不太想得通
- Volley源码分析(四)——ImageLoader
- 【进阶android】Volley源码分析——Volley的工具【ImageLoader】
- Volley框架中ImageLoader源码分析
- Volley简单学习使用四——源码分析二
- Android Volley详解(四) 源码分析
- volley(3)——源码分析
- ImageLoader源码分析(一)ImageLoader初始化
- android-----Volley框架使用ImageLoader加载图片源码分析
- 【进阶android】Volley源码分析——Volley的流程
- 【进阶android】Volley源码分析——Volley的线程
- 【进阶android】Volley源码分析——Volley的缓存
- [Android]Volley源码分析(四)请求队列
- Volley(四) Volley框架(从源码角度分析)
- Android——volley源码分析
- ImageLoader的简单分析(四)
- Volley 源码分析(一)
- Volley 源码分析(二)
- Volley源码分析(一)
- 质量AppScan(测试)安全性问题相关方法
- java 字符转截取方式
- elasticsearch安装
- 需不需要这只兔子?
- rhel6.6 telnet-server安装
- Volley源码分析(四)——ImageLoader
- Oracle procedure递归方法生成有规律唯一不重复且连续的流水号(处理字母数字)
- Python requests模块详解
- 赋能小微企业放大招,金蝶精斗云再次强化云服务领导者地位
- Repeater控件绑定数据为空的判断
- EF 数据库初始化 Database.SetInitializer Entity Framework数据库初始化四种策略
- Android2017Google IO
- kernel通过psci的smc让从cpu boot的过程
- NOR FLASH读、写、擦原理与实现(1)——性能简述与术语解释