Volley 图片加载相关源码解析(1)

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 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 Request add(Request 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

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

}

最后

有任何问题,欢迎广大网友一起来交流,分享高阶Android学习视频资料和面试资料包~

偷偷说一句:群里高手如云,欢迎大家加群和大佬们一起交流讨论啊!


《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
adResponseDelivered()) {

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

}

最后

有任何问题,欢迎广大网友一起来交流,分享高阶Android学习视频资料和面试资料包~

偷偷说一句:群里高手如云,欢迎大家加群和大佬们一起交流讨论啊!

[外链图片转存中…(img-g2q3skG3-1715189800371)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值