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然后将这个请求加入到各种队列,主要包含mCurrentRequests
、mCacheQueue
。
然后,还记得我们初始化RequestQueue的时候,启动了几个转发线程吗?CacheDispatcher
和NetworkDispatcher
。
其实,网络请求就是在这几个线程中真正去加载的,我们分别看一下;
(四)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-327TR8aI-1715756111488)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!