转载请标明出处:

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

主要就是这一行了:

#Volley

public 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.html
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
} Network network = new BasicNetwork(stack); RequestQueue queue;
if (maxDiskCacheBytes <= -1)
{
// No maximum size specified
queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
}
else
{
// Disk cache size specified
queue = 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))
{
@Override
protected int sizeOf(String key, Bitmap value)
{
return value.getRowBytes() * value.getHeight();
}
}; @Override
public void putBitmap(String url, Bitmap bitmap)
{
mLruCache.put(url, bitmap);
} @Override
public Bitmap getBitmap(String url)
{
return mLruCache.get(url);
}
}); #ImageLoader public ImageLoader(RequestQueue queue, ImageCache imageCache) {
mRequestQueue = queue;
mCache = imageCache;
}

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


(三) 加载图片

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

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

接下来看get方法:

#ImageLoader
public 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

看一眼构造方法;


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

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

#CacheDispatcher

 @Override
public 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() {
@Override
public 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;
}
@Override
public 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. abp vnext2.0之核心组件模块加载系统源码解析与简单应用

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

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

    在本系列的上一篇文章中,我们学习了Glide的基本用法,体验了这个图片加载框架的强大功能,以及它非常简便的API.还没有看过上一篇文章的朋友,建议先去阅读 Android图片加载框架最全解析(一),G ...

  3. Android图片加载框架最全解析(六),探究Glide的自定义模块功能

    不知不觉中,我们的Glide系列教程已经到了第六篇了,距离第一篇Glide的基本用法发布已经过去了半年的时间.在这半年中,我们通过用法讲解和源码分析配合学习的方式,将Glide的方方面面都研究了个遍, ...

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

    本篇将是我们这个Glide系列的最后一篇文章. 其实在写这个系列第一篇文章的时候,Glide就推出4.0.0的RC版了.那个时候因为我一直研究的都是Glide 3.7.0版本,再加上RC版本还不太稳定 ...

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

    在本系列的上一篇文章中,我带着大家一起阅读了一遍Glide的源码,初步了解了这个强大的图片加载框架的基本执行流程. 不过,上一篇文章只能说是比较粗略地阅读了Glide整个执行流程方面的源码,搞明白了G ...

  6. Android图片加载框架最全解析(一),Glide的基本用法

    现在Android上的图片加载框架非常成熟,从最早的老牌图片加载框架UniversalImageLoader,到后来Google推出的Volley,再到后来的新兴军Glide和Picasso,当然还有 ...

  7. Android图片加载框架最全解析(七),实现带进度的Glide图片加载功能

    我们的Glide系列文章终于要进入收尾篇了.从我开始写这个系列的第一篇文章时,我就知道这会是一个很长的系列,只是没有想到竟然会写这么久. 在前面的六篇文章中,我们对Glide的方方面面都进行了学习,包 ...

  8. Android图片加载框架最全解析(五),Glide强大的图片变换功能

    大家好,又到了学习Glide的时间了.前段时间由于项目开发紧张,再加上后来又生病了,所以停更了一个月,不过现在终于又可以恢复正常更新了.今天是这个系列的第五篇文章,在前面四篇文章的当中,我们已经学习了 ...

  9. Android图片加载框架最全解析(四),玩转Glide的回调与监听

    大家好,今天我们继续学习Glide. 在上一篇文章当中,我带着大家一起深入探究了Glide的缓存机制,我们不光掌握了Glide缓存的使用方法,还通过源码分析对缓存的工作原理进行了了解.虽说上篇文章和本 ...

随机推荐

  1. CentOS 6.5安装MongoDB 2.6(多yum数据源)

    下面我们在CentOS 6.5 x64系统上安装最新的MongoDB 2.6.5版. 在MongoDB v2.6.5版的软件仓库一共有五个包: 1)mongodb-org此包是元数据包,它可以实现自动 ...

  2. mybatis源码之BaseStatementHandler

    /** * @author Clinton Begin */ public abstract class BaseStatementHandler implements StatementHandle ...

  3. 关于NSString和NSMutableString的相关用法和基本介绍

    Objective-C 中核心处理字符串的类是 NSString 与 NSMutableString ,这两个类最大的区别就是NSString 创建赋值以后该字符串的内容与长度不能在动态的更改,除非重 ...

  4. 关于ARC的介绍和ARC与MRC混编解决

    1. ARC & MRC 混合开发 在项目开发中,遇到使用MRC开发的第三方库怎么办? 例如:ASI 1> 尝试使用Xcode的转换工具(失败率比较高) 2> 在编译选项中,为MR ...

  5. javascript语言扩展:可迭代对象(5)

    文章1-4篇说的都是js中的可迭代对象,下面让我们看看ruby中的等价物. 不可否认,ruby中对于迭代器和生成器的语法都相当简洁:ruby从一开始就有一个简洁的基因,而js后来的不断扩充使得其有些语 ...

  6. java中List对象的操作方法

    List<String> list = new ArrayList<String>(); //增加 list.add("苹果"); list.add(&qu ...

  7. java的finalize方法使用

    1. finalize的作用 finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法. finalize()与C++中的析构函数 ...

  8. vs工具

    首页 精选版块 论坛帮助 论坛牛人 论坛地图 专家问答 CSDN > CSDN论坛 > .NET技术 > 非技术区 返回列表 管理菜单 结帖 发帖 回复 关注 [推荐] Visual ...

  9. DHCP的主要知识点

    首先,先写一遍配置 好几种安装方式,我这里用的最简单的yum源安装: mkdir /mnt/cdrom mount -r /dev/sr0 /mnt/cdrom       ##创建挂载点 vim / ...

  10. C# SqlBulkCopy数据批量入库

    准备条件:20万+数据 界面设计使用的WPF. 没有对比就没有伤害,以下是我两种方式导入数据案例. 运行 结果对比: 首先使用一般sql语句导入,因为时间原因,我就没有等待程序执行完,但是我记录了大约 ...