转载请标明出处:
http://blog.csdn.net/lmj623565791/article/details/47721631;
本文出自:【张鸿洋的博客】

一 概述

最近在完善图片加载方面的代码,于是就看看Volley的图片加载相关源码,取取经,顺便写篇博文作为笔记记录下。

在使用Volley作为图片加载库的时候,肯定需要做以下几件事:

  • Application中初始化Volley请求队列
  • 初始化ImageLoader,需要设置ImageCache
  • 需要的时候,调用 getInstance().getImageLoader().get(url, new ImageLoader.ImageListener())

二 源码分析

(一) 初始化Volley请求队列

mReqQueue = Volley.newRequestQueue(mCtx);

主要就是这一行了:

#Volleypublic static RequestQueue newRequestQueue(Context context) {return newRequestQueue(context, null);}public static RequestQueue newRequestQueue(Context context, HttpStack stack){return newRequestQueue(context, stack, -1);}
public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);String userAgent = "volley/0";try {String packageName = context.getPackageName();PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);userAgent = packageName + "/" + info.versionCode;} catch (NameNotFoundException e) {}if (stack == null) {if (Build.VERSION.SDK_INT >= 9) {stack = new HurlStack();} else {// Prior to Gingerbread, HttpUrlConnection was unreliable.// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.htmlstack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));}}Network network = new BasicNetwork(stack);RequestQueue queue;if (maxDiskCacheBytes <= -1){// No maximum size specifiedqueue = new RequestQueue(new DiskBasedCache(cacheDir), network);}else{// Disk cache size specifiedqueue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);}queue.start();return queue;}

这里主要就是初始化HttpStack,对于HttpStack在API大于等于9的时候选择HttpUrlConnetcion,反之则选择HttpClient,这里我们并不关注Http相关代码。

接下来初始化了RequestQueue,然后调用了start()方法。

接下来看RequestQueue的构造:

public RequestQueue(Cache cache, Network network) {this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);}
public RequestQueue(Cache cache, Network network, int threadPoolSize) {this(cache, network, threadPoolSize,new ExecutorDelivery(new Handler(Looper.getMainLooper())));}
public RequestQueue(Cache cache, Network network, int threadPoolSize,ResponseDelivery delivery) {mCache = cache;mNetwork = network;mDispatchers = new NetworkDispatcher[threadPoolSize];mDelivery = delivery;}

初始化主要就是4个参数:mCache、mNetwork、mDispatchers、mDelivery。第一个是硬盘缓存;第二个主要用于Http相关操作;第三个用于转发请求的;第四个参数用于把结果转发到UI线程(ps:你可以看到new Handler(Looper.getMainLooper()))。

接下来看start方法

#RequestQueue/*** Starts the dispatchers in this queue.*/public void start() {stop();  // Make sure any currently running dispatchers are stopped.// Create the cache dispatcher and start it.mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);mCacheDispatcher.start();// Create network dispatchers (and corresponding threads) up to the pool size.for (int i = 0; i < mDispatchers.length; i++) {NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,mCache, mDelivery);mDispatchers[i] = networkDispatcher;networkDispatcher.start();}}

首先是stop,确保转发器退出,其实就是内部的几个线程退出,这里大家如果有兴趣可以看眼源码,参考下Volley中是怎么处理线程退出的(几个线程都是while(true){//doSomething})。

接下来初始化CacheDispatcher,然后调用start();初始化NetworkDispatcher,然后调用start();

上面的转发器呢,都是线程,可以看到,这里开了几个线程在帮助我们工作,具体的源码,我们一会在看。

好了,到这里,就完成了Volley的初始化的相关代码,那么接下来看初始化ImageLoader相关源码。


(二) 初始化ImageLoader

#VolleyHelper
mImageLoader = new ImageLoader(mReqQueue, new ImageCache(){private final LruCache<String, Bitmap> mLruCache = new LruCache<String, Bitmap>((int) (Runtime.getRuntime().maxMemory() / 10)){@Overrideprotected int sizeOf(String key, Bitmap value){return value.getRowBytes() * value.getHeight();}};@Overridepublic void putBitmap(String url, Bitmap bitmap){mLruCache.put(url, bitmap);}@Overridepublic Bitmap getBitmap(String url){return mLruCache.get(url);}});#ImageLoaderpublic ImageLoader(RequestQueue queue, ImageCache imageCache) {mRequestQueue = queue;mCache = imageCache;}

很简单,就是根据我们初始化的RequestQueue和LruCache初始化了一个ImageLoader。


(三) 加载图片

我们在加载图片时,调用的是:

 # VolleyHelpergetInstance().getImageLoader().get(url, new ImageLoader.ImageListener());

接下来看get方法:

#ImageLoaderpublic ImageContainer get(String requestUrl, final ImageListener listener) {return get(requestUrl, listener, 0, 0);}
public ImageContainer get(String requestUrl, ImageListener imageListener,int maxWidth, int maxHeight) {return get(requestUrl, imageListener, maxWidth, maxHeight, ScaleType.CENTER_INSIDE);}
public ImageContainer get(String requestUrl, ImageListener imageListener,int maxWidth, int maxHeight, ScaleType scaleType) {// only fulfill requests that were initiated from the main thread.throwIfNotOnMainThread();final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);// 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<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType,cacheKey);mRequestQueue.add(newRequest);mInFlightRequests.put(cacheKey,new BatchedImageRequest(newRequest, imageContainer));return imageContainer;}

可以看到get方法,首先通过throwIfNotOnMainThread()方法限制必须在UI线程调用;

然后根据传入的参数计算cacheKey,获取cache;

=>如果cache存在,直接将返回结果封装为一个ImageContainer(cachedBitmap, requestUrl),然后直接回调imageListener.onResponse(container, true);我们就可以设置图片了。

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

接下来检查该url是否早已加入了请求对了,如果早已加入呢,则将刚初始化的ImageContainer加入BatchedImageRequest,返回结束。

如果是一个新的请求,则通过makeImageRequest创建一个新的请求,然后将这个请求分别加入mRequestQueue和mInFlightRequests,注意mInFlightRequests中会初始化一个BatchedImageRequest,存储相同的请求队列。

这里注意mRequestQueue是个对象,并不是队列数据结构,所以我们要看下add方法

#RequestQueue
public <T> Request<T> add(Request<T> request) {// Tag the request as belonging to this queue and add it to the set of current requests.request.setRequestQueue(this);synchronized (mCurrentRequests) {mCurrentRequests.add(request);}// Process requests in the order they are added.request.setSequence(getSequenceNumber());request.addMarker("add-to-queue");// If the request is uncacheable, skip the cache queue and go straight to the network.if (!request.shouldCache()) {mNetworkQueue.add(request);return request;}// Insert request into stage if there's already a request with the same cache key in flight.synchronized (mWaitingRequests) {String cacheKey = request.getCacheKey();if (mWaitingRequests.containsKey(cacheKey)) {// There is already a request in flight. Queue up.Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);if (stagedRequests == null) {stagedRequests = new LinkedList<Request<?>>();}stagedRequests.add(request);mWaitingRequests.put(cacheKey, stagedRequests);if (VolleyLog.DEBUG) {VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);}} else {// Insert 'null' queue for this cacheKey, indicating there is now a request in// flight.mWaitingRequests.put(cacheKey, null);mCacheQueue.add(request);}return request;}}

这里首先将请求加入mCurrentRequests,这个mCurrentRequests保存了所有需要处理的Request,主要为了提供cancel的入口。

如果该请求不应该被缓存则直接加入mNetworkQueue,然后返回。

然后判断该请求是否有相同的请求正在被处理,如果有则加入mWaitingRequests;如果没有,则
加入mWaitingRequests.put(cacheKey, null)和mCacheQueue.add(request)。

ok,到这里我们就分析完成了直观的代码,但是你可能会觉得,那么到底是在哪里触发的网络请求,加载图片呢?

那么,首先你应该知道,我们需要加载图片的时候,会makeImageRequest然后将这个请求加入到各种队列,主要包含mCurrentRequestsmCacheQueue

然后,还记得我们初始化RequestQueue的时候,启动了几个转发线程吗?CacheDispatcherNetworkDispatcher

其实,网络请求就是在这几个线程中真正去加载的,我们分别看一下;


(四)CacheDispatcher

看一眼构造方法;


#CacheDispatcherpublic CacheDispatcher(BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,Cache cache, ResponseDelivery delivery) {mCacheQueue = cacheQueue;mNetworkQueue = networkQueue;mCache = cache;mDelivery = delivery;}

这是一个线程,那么主要的代码肯定在run里面。

#CacheDispatcher@Overridepublic void run() {if (DEBUG) VolleyLog.v("start new dispatcher");Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);// Make a blocking call to initialize the cache.mCache.initialize();while (true) {try {// Get a request from the cache triage queue, blocking until// at least one is available.final Request<?> request = mCacheQueue.take();request.addMarker("cache-queue-take");// If the request has been canceled, don't bother dispatching it.if (request.isCanceled()) {request.finish("cache-discard-canceled");continue;}// Attempt to retrieve this item from cache.Cache.Entry entry = mCache.get(request.getCacheKey());if (entry == null) {request.addMarker("cache-miss");// Cache miss; send off to the network dispatcher.mNetworkQueue.put(request);continue;}// If it is completely expired, just send it to the network.if (entry.isExpired()) {request.addMarker("cache-hit-expired");request.setCacheEntry(entry);mNetworkQueue.put(request);continue;}// We have a cache hit; parse its data for delivery back to the request.request.addMarker("cache-hit");Response<?> response = request.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));request.addMarker("cache-hit-parsed");if (!entry.refreshNeeded()) {// Completely unexpired cache hit. Just deliver the response.mDelivery.postResponse(request, response);} else {// Soft-expired cache hit. We can deliver the cached response,// but we need to also send the request to the network for// refreshing.request.addMarker("cache-hit-refresh-needed");request.setCacheEntry(entry);// Mark the response as intermediate.response.intermediate = true;// Post the intermediate response back to the user and have// the delivery then forward the request along to the network.mDelivery.postResponse(request, response, new Runnable() {@Overridepublic void run() {try {mNetworkQueue.put(request);} catch (InterruptedException e) {// Not much we can do about this.}}});}} catch (InterruptedException e) {// We may have been interrupted because it was time to quit.if (mQuit) {return;}continue;}}}

ok,首先要明确这个缓存指的是硬盘缓存(目录为context.getCacheDir()/volley),内存缓存在ImageLoader那里已经判断过了。

可以看到这里是个无限循环,不断的从mCacheQueue去取出请求,如果请求已经被取消就直接结束;

接下来从缓存中获取:

=>如果没有取到,则加入mNetworkQueue

=>如果缓存过期,则加入mNetworkQueue

否则,就是取到了可用的缓存了;调用request.parseNetworkResponse解析从缓存中取出的data和responseHeaders;接下来判断TTL(主要还是判断是否过期),如果没有过期则直接通过mDelivery.postResponse转发,然后回调到UI线程;如果ttl不合法,回调完成后,还会将该请求加入mNetworkQueue。

好了,这里其实就是如果拿到合法的缓存,则直接转发到UI线程;反之,则加入到NetworkQueue.

接下来我们看NetworkDispatcher。


(五)NetworkDispatcher

与CacheDispatcher类似,依然是个线程,核心代码依然在run中;

# NetworkDispatcher
//new NetworkDispatcher(mNetworkQueue, mNetwork,mCache, mDelivery)public NetworkDispatcher(BlockingQueue<Request<?>> queue,Network network, Cache cache,ResponseDelivery delivery) {mQueue = queue;mNetwork = network;mCache = cache;mDelivery = delivery;}
@Overridepublic void run() {Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);while (true) {long startTimeMs = SystemClock.elapsedRealtime();Request<?> request;try {// Take a request from the queue.request = mQueue.take();} catch (InterruptedException e) {// We may have been interrupted because it was time to quit.if (mQuit) {return;}continue;}try {request.addMarker("network-queue-take");// If the request was cancelled already, do not perform the// network request.if (request.isCanceled()) {request.finish("network-discard-cancelled");continue;}addTrafficStatsTag(request);// Perform the network request.NetworkResponse networkResponse = mNetwork.performRequest(request);request.addMarker("network-http-complete");// If the server returned 304 AND we delivered a response already,// we're done -- don't deliver a second identical response.if (networkResponse.notModified && request.hasHadResponseDelivered()) {request.finish("not-modified");continue;}// Parse the response here on the worker thread.Response<?> response = request.parseNetworkResponse(networkResponse);request.addMarker("network-parse-complete");// Write to cache if applicable.// TODO: Only update cache metadata instead of entire record for 304s.if (request.shouldCache() && response.cacheEntry != null) {mCache.put(request.getCacheKey(), response.cacheEntry);request.addMarker("network-cache-written");}// Post the response back.request.markDelivered();mDelivery.postResponse(request, response);} catch (VolleyError volleyError) {volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);parseAndDeliverNetworkError(request, volleyError);} catch (Exception e) {VolleyLog.e(e, "Unhandled exception %s", e.toString());VolleyError volleyError = new VolleyError(e);volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);mDelivery.postError(request, volleyError);}}}

看代码前,我们首先想一下逻辑,正常情况下我们会取出请求,让network去请求处理我们的请求,处理完成以后呢:加入缓存,然后转发。

那么看下是不是:

首先取出请求;然后通过mNetwork.performRequest(request)处理我们的请求,拿到NetworkResponse;接下来,使用request去解析我们的NetworkResponse。

注:因为我们NetworkResponse返回都是服务器返回的一些数据,而不同的请求对应的返回结果肯定不同,比如我们的ImageRequest,解析出来是个bitmap,所以我们的解析代码是在Request里面;这里大家想想,如果我们自定义Request,该方法是不是会重写呢?详情请戳Android Volley 之自定义Request

拿到Response以后,判断是否应该缓存,如果需要,则缓存。

最后mDelivery.postResponse(request, response);转发;

ok,和我们的预期差不多。

这样的话,我们的Volley去加载图片的核心逻辑就分析完成了,简单总结下:

  • 首先初始化RequestQueue,主要就是开启几个Dispatcher线程,线程会不断读取请求(使用的阻塞队列,没有消息则阻塞)
  • 当我们发出请求以后,会根据url,ImageView属性等,构造出一个cacheKey,然后首先从LruCache中获取(这个缓存我们自己构建的,凡是实现ImageCache接口的都合法);如果没有取到,则判断是否存在硬盘缓存,这一步是从getCacheDir里面获取(默认5M);如果没有取到,则从网络请求;

不过,可以发现的是Volley的图片加载,并没有LIFO这种策略;貌似对于图片的下载,也是完整的加到内存,然后压缩,这么看,对于巨图、大文件这样的就废了;

看起来还是蛮简单的,不过看完以后,对于如何更好的时候该库以及如何去设计图片加载库还是有很大的帮助的;

如果有兴趣,大家还可以在看源码分析的同时,想想某些细节的实现,比如:

  • Dispatcher都是一些无限循环的线程,可以去看看Volley如何保证其关闭的。
  • 对于图片压缩的代码,可以在ImageRequest的parseNetworkResponse里面去看看,是如何压缩的。
  • so on…

最后贴个大概的流程图,方便记忆:


欢迎关注我的微博:
http://weibo.com/u/3165018720


群号:463081660,欢迎入群

微信公众号:hongyangAndroid
(欢迎关注,第一时间推送博文信息)

Volley 图片加载相关源码解析相关推荐

  1. FPGA - Zynq - 加载 - FSBL源码解析1

    FPGA - Zynq - 加载 - FSBL源码解析1 前文回顾 FSBL的数据段和代码段如何链接 建个Example工程,不要光顾着看,自己动动手掌握的更快. 查看链接文件,原来存储空间是这样有条 ...

  2. 图片加载 二维码 解析

    图片加载 二维码 解析 1. layout布局文件 (1)activity_category.xml <?xml version="1.0" encoding="u ...

  3. Android UIL图片加载缓存源码分析-内存缓存

    本篇文章我们来分析一下著名图片加载库Android-Universal-Image-Loader的图片缓存源码. 源码环境 版本:V1.9.5 GitHub链接地址:https://github.co ...

  4. 未能加载文件或程序集rsy3_abp vnext2.0之核心组件模块加载系统源码解析

    abp vnext是abp官方在abp的基础之上构建的微服务架构,说实话,看完核心组件源码的时候,很兴奋,整个框架将组件化的细想运用的很好,真的超级解耦.老版整个框架依赖Castle的问题,vnext ...

  5. abp vnext2.0之核心组件模块加载系统源码解析

    abp vnext是abp官方在abp的基础之上构建的微服务架构,说实话,看完核心组件源码的时候,很兴奋,整个框架将组件化的细想运用的很好,真的超级解耦.老版整个框架依赖Castle的问题,vnext ...

  6. Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/53939176 本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭 ...

  7. Android图片加载框架最全解析(八),带你全面了解Glide 4的用法

    本文转载自郭神的Glide分析系列:http://blog.csdn.net/guolin_blog/article/details/78582548 本文同步发表于我的微信公众号,扫一扫文章底部的二 ...

  8. Android图片加载框架最全解析(三),深入探究Glide的缓存机制

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/54895665 本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭 ...

  9. Android图片加载框架最全解析(五)

    由此我们可以得知,在没有明确指定的情况下,ImageView默认的scaleType是FIT_CENTER. 有了这个前提条件,我们就可以继续去分析Glide的源码了.当然,本文中的源码还是建在第二篇 ...

最新文章

  1. php7 swoole 扩展,PHP7.2加入swoole扩展
  2. IntelliJ IDEA设置默认换行符
  3. MYSQL 常用SQL
  4. 枚举与常量 java_java – 为什么两个枚举常量可以在同一行中使用?
  5. JavaWeb学习总结(三):Tomcat服务器学习和使用(二)
  6. ASP.NET Core 3.x控制IHostedService启动顺序浅探
  7. AtCoder Beginner Contest 179 总结
  8. 一线大厂青睐的前端人,90%满足这3个条件
  9. 学习云计算有什么用?企业怎么才能“上云”?
  10. thinkphp后台_前后端分离决方案|thinkphpvueadmin 后台接口
  11. 《LaTeX写作》——LaTeX编写环境的安装笔记
  12. python 生成随机数: 随机整数,随机小数,0-1之间的小数
  13. 随机森林分类算法python代码_随机森林的原理及Python代码实现
  14. fastText原理和文本分类实战,看这一篇就够了
  15. SpringBoot项目获取Spring容器中的bean
  16. 我的单片机固件被人给破解了
  17. [BJWC2008]雷涛的小猫 dp
  18. 玩客云添加到我的计算机,玩客云怎么备份?将电脑文件备份到玩客云教程
  19. 微信小程序业务好(做)跑吗?我想做小程序代理业务
  20. c语言实验设备管理系统设计作业,C语言课程设计实验设备管理系统设计

热门文章

  1. Tomcat部署静态项目(网站模板)
  2. Java如何对一个对象进行深拷贝?
  3. 快看,秋天的校园景色是多么美丽
  4. XDRender_ObjPass_ShaderMode_Cloth(1) 布料渲染模型 皮革
  5. 计算机模拟双缝干涉实验报告,杨氏双缝干涉实验报告.doc.pdf
  6. 根据用户输入编码,输出课程名称
  7. 【云原生系统故障自愈论文学习】—How to Fight Production Incidents? An Empirical Study on a Large-scale Cloud Service
  8. 中国冰鲜鸡市场经营和品牌竞争分析报告(2021-2026年)
  9. 如何将一个数组中的值(对象)变相放入另一个数组中
  10. 慕课java工程师2020版_中国大学mooc慕课_Java程序设计_2020章节测试答案