概述
Volley是Google推出的一款比較輕巧的網路請求框架,並且可以對請求進行快取,同時可以實時取消請求,設定請求優先順序,內建了ImageRequest,JsonRequest,JsonObjectRequest,JsonArrayRequest,StringRequest等,並且還支援自定義Request,基本上能滿足日常的開發,當讓Volley原生並不支援檔案上傳,但是可以通過自定義Request來實現,Volley不僅僅支援網路請求,還支援圖片載入,這個主要是通過內建的ImageRequest來實現,Volley的工作原理大致如下:
大致流程就是,當新增一個Request的時候,首先會被新增到CachaQueue中,
正文
工作流程
Volley的快取跟常規的快取不太一致,它並不是直接去取快取,而是構造了一個快取佇列,存放Request,然後根據特有的key值去取快取,如果快取存在並且沒有過期,請求也沒有取消,那麼就直接解析快取資料,傳送到主執行緒,不然就直接加入到網路請求佇列,重新請求網路資料,Volley的原始碼比較多,下面主要是從Request,RequestQueue,Dispatcher,Cache,這幾個類分析一下Volley的一些實現細節,畢竟大部分框架,原理都是一兩句話都能說清楚,但是有很多細節讓自己實現其實還是挺困難的。
Request
繼承關係
Request是一個單獨的類,實現了Comparable介面,主要是用來對請求進行排序,如果設定了請求的優先順序,就會根據優先順序來進行排序,如果沒有優先順序就會按照請求加入的順序來排序。
成員變數
private final int mMethod;//請求型別,GET,POST
private final String mUrl;//請求的伺服器地址
private final Object mLock = new Object();//用來給物件上鎖
private Response.ErrorListener mErrorListener;//請求失敗的監聽
private Integer mSequence;//請求的序列號,按照請求的順序依次遞增
private RequestQueue mRequestQueue;//請求佇列
private boolean mShouldCache = true;//是否需要快取,預設開啟快取
private boolean mCanceled = false;//請求是否取消,預設為false
private boolean mResponseDelivered = false;//解析完的請求是否已經傳送
private boolean mShouldRetryServerErrors = false;//遇到伺服器異常是否需要重試
private RetryPolicy mRetryPolicy;//重試策略
private Cache.Entry mCacheEntry = null;//快取的 物件,裡面封裝了很多跟快取相關的資訊
private Object mTag;//請求的tag
private NetworkRequestCompleteListener mRequestCompleteListener;//網路請求完成回撥的結果
//Request的生命週期記錄工具
private final MarkerLog mEventLog = MarkerLog.ENABLED ? new MarkerLog() : null;
複製程式碼
Method
public interface Method {
int DEPRECATED_GET_OR_POST = -1;
int GET = 0;
int POST = 1;
int PUT = 2;
int DELETE = 3;
int HEAD = 4;
int OPTIONS = 5;
int TRACE = 6;
int PATCH = 7;
}
複製程式碼
由於方法的辨識度比較高,所以Volley沒有采用列舉,而是採用了介面內定義變數,節省開銷
Priority
public enum Priority {
LOW,
NORMAL,
HIGH,
IMMEDIATE
}
複製程式碼
優先順序更注重可讀性,所以Volley採用了列舉
構造方法
@Deprecated
public Request(String url, Response.ErrorListener listener) {
this(Method.DEPRECATED_GET_OR_POST, url, listener);
}
public Request(int method, String url, Response.ErrorListener listener) {
mMethod = method;
mUrl = url;
mErrorListener = listener;
setRetryPolicy(new DefaultRetryPolicy());
mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
}
複製程式碼
傳入method,url以及失敗的ErrorListener
關鍵方法
cancel
取消請求
public void cancel() {
synchronized (mLock) {
mCanceled = true;//改變請求標誌位
mErrorListener = null;//回撥介面置空
}
}
複製程式碼
compareTo
設定請求優先順序
@Override
public int compareTo(Request<T> other) {
//獲取請求優先順序
Priority left = this.getPriority();
Priority right = other.getPriority();
//請求優先順序預設為Normal
//1.先比較請求優先順序,如果相等再比較請求加入的順序
return left == right ?
this.mSequence - other.mSequence :
right.ordinal() - left.ordinal();
}
複製程式碼
finish
通知RequestQueue,這個請求已經結束
void finish(final String tag) {
if (mRequestQueue != null) {
//通知佇列移除當前請求
mRequestQueue.finish(this);
}
if (MarkerLog.ENABLED) {
final long threadId = Thread.currentThread().getId();
//判斷當前執行緒是否為主執行緒,不是的話切換到主執行緒
if (Looper.myLooper() != Looper.getMainLooper()) {
//通過PostRunnable的方式,保證請求結束的列印時間是有序的
Handler mainThread = new Handler(Looper.getMainLooper());
mainThread.post(new Runnable() {
@Override
public void run() {
mEventLog.add(tag, threadId);
mEventLog.finish(Request.this.toString());
}
});
return;
}
mEventLog.add(tag, threadId);
mEventLog.finish(this.toString());
}
}
複製程式碼
getCacheKey
預設作為請求的伺服器地址作為key,實際開發過程中需要通過MD5會比較好一點
public String getCacheKey() {
return getUrl();
}
複製程式碼
抽象方法
Rquest是一個抽象類,裡面還有很多抽象犯法需要子類去實現
//解析網路請求
abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
//傳遞網路請求結果
abstract protected void deliverResponse(T response);
//請求失敗的回撥,因為不同的Request需要的返回型別不一樣,需要子類實現,但是請求失敗確是共同的
//所以Volley做了一些封裝
public void deliverError(VolleyError error) {
Response.ErrorListener listener;
synchronized (mLock) {
listener = mErrorListener;
}
if (listener != null) {
listener.onErrorResponse(error);
}
}
複製程式碼
RequesQueue
RequesQueue實際上是所有佇列的一個管理類,包含正在進行中的佇列集合mCurrentRequests,快取佇列mCacheQueue,網路佇列mNetworkQueue等
成員變數
//Request新增進去後的序列號
private final AtomicInteger mSequenceGenerator = new AtomicInteger();
//正在進行中的請求集合,採用HashSet實現
private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();
//請求的快取佇列,採用PriorityBlockingQueue實現,可以根據優先順序來出隊
private final PriorityBlockingQueue<Request<?>> mCacheQueue =
new PriorityBlockingQueue<>();
//請求的網路佇列,採用PriorityBlockingQueue實現
private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
new PriorityBlockingQueue<>();
//網路請求分發器的數量,預設為4個
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
//資料的快取
private final Cache mCache;
//網路請求的實際執行者
private final Network mNetwork;
//網路請求返回結果的分發者
private final ResponseDelivery mDelivery;
//網路請求分發器陣列
private final NetworkDispatcher[] mDispatchers;
//快取分發器執行緒
private CacheDispatcher mCacheDispatcher;
//網路請求完成的監聽器集合
private final List<RequestFinishedListener> mFinishedListeners =new ArrayList<>();
複製程式碼
構造方法
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
public RequestQueue(Cache cache, Network network) {
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
//其它的構造方法最終還是間接呼叫了這個方法
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
複製程式碼
通過成員變數的註釋,比較清晰,就是預設初始化了一些變數
核心方法
start
public void start() {
stop(); //終止正在進行的分發器,包括快取的分發器以及網路分發器
// 建立快取分發器
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
//啟動快取分發器
mCacheDispatcher.start();
// 根據定義的Dispatcher陣列,建立網路分發器
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
//啟動網路分發器
networkDispatcher.start();
}
}
public void stop() {
if (mCacheDispatcher != null) {
mCacheDispatcher.quit();
}
for (final NetworkDispatcher mDispatcher : mDispatchers) {
if (mDispatcher != null) {
mDispatcher.quit();
}
}
}
複製程式碼
add
public <T> Request<T> add(Request<T> request) {
//將RequestQueue賦值給Request
request.setRequestQueue(this);
//同步新增到正在進行中的請求集合中去
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
//給請求設定序列號
request.setSequence(getSequenceNumber());
//新增Marker標記位
request.addMarker("add-to-queue");
if (!request.shouldCache()) {
//如果請求佇列不需要快取,那麼直接加入到網路對壘中
mNetworkQueue.add(request);
return request;
}
//新增進快取佇列
mCacheQueue.add(request);
return request;
}
複製程式碼
cancel
public void cancelAll(final Object tag) {
if (tag == null) {
throw new IllegalArgumentException("Cannot cancelAll with a null tag");
}
cancelAll(new RequestFilter() {
@Override
public boolean apply(Request<?> request) {
//通過tag來匹配需要取消的請求
return request.getTag() == tag;
}
});
}
//通過RequestFilter來過濾需要取消的請求
public void cancelAll(RequestFilter filter) {
synchronized (mCurrentRequests) {
for (Request<?> request : mCurrentRequests) {
if (filter.apply(request)) {
request.cancel();
}
}
}
}
複製程式碼
finish
<T> void finish(Request<T> request) {
// 從正在進行的請求中移除
synchronized (mCurrentRequests) {
mCurrentRequests.remove(request);
}
synchronized (mFinishedListeners) {
//移除回撥介面
for (RequestFinishedListener<T> listener : mFinishedListeners) {
listener.onRequestFinished(request);
}
}
}
複製程式碼
Dispatcher
Volley提供了兩個分發器,一個是CacheDispatcher,一個是NetworkDispatcher,實際上就是兩個執行緒,然後進行了死迴圈,不斷地從快取佇列跟網路佇列中進行取Request來進行分發。
CacheDispatcher
繼承關係
成員變數
//Debug模式的標誌
private static final boolean DEBUG = VolleyLog.DEBUG;
//快取佇列,採用BlockingQueue實現生產者消費者模式
private final BlockingQueue<Request<?>> mCacheQueue;
//網路佇列,採用BlockingQueue實現生產者消費者模式
private final BlockingQueue<Request<?>> mNetworkQueue;
//快取類
private final Cache mCache;
//網路請求結果分發類
private final ResponseDelivery mDelivery;
//CacheDispatcher是否退出的標誌
private volatile boolean mQuit = false;
//等待管理佇列管理器
private final WaitingRequestManager mWaitingRequestManager;
複製程式碼
構造方法
public CacheDispatcher(
BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,
Cache cache, ResponseDelivery delivery) {
mCacheQueue = cacheQueue;
mNetworkQueue = networkQueue;
mCache = cache;
mDelivery = delivery;
mWaitingRequestManager = new WaitingRequestManager(this);
}
複製程式碼
CacheDispatcher持有cacheQueue,networkQueue,cache,delivery這幾個類
run
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
//設定執行緒優先順序
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//快取初始化,待會在快取中具體分析
mCache.initialize();
while (true) {
try {
//死迴圈
processRequest();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
}
}
}
複製程式碼
processRequest
private void processRequest() throws InterruptedException {
//從快取佇列中取佇列
final Request<?> request = mCacheQueue.take();
//給取出的Requet打上標記
request.addMarker("cache-queue-take");
//如果請求已取消,結束掉這個請求
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
return;
}
//拿到快取的entry
Cache.Entry entry = mCache.get(request.getCacheKey());
//快取資料為空,就將Request新增進mNetworkQueue
if (entry == null) {
request.addMarker("cache-miss");
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mNetworkQueue.put(request);
}
return;
}
// 快取過期,直接加入到網路佇列
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mNetworkQueue.put(request);
}
return;
}
//快取有效,直接解析傳送給主執行緒
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
}
複製程式碼
quit
退出執行緒
public void quit() {
mQuit = true;
//中斷執行緒
interrupt();
}
複製程式碼
NetworkDispatcher
繼承關係
成員變數
//網路請求佇列
private final BlockingQueue<Request<?>> mQueue;
//網路請求的實際操作類
private final Network mNetwork;
//快取類
private final Cache mCache;
//請求響應的結果傳送者
private final ResponseDelivery mDelivery;
//執行緒是否傳送的標誌
private volatile boolean mQuit = false;
複製程式碼
構造方法
public NetworkDispatcher(BlockingQueue<Request<?>> queue,
Network network, Cache cache, ResponseDelivery delivery) {
mQueue = queue;
mNetwork = network;
mCache = cache;
mDelivery = delivery;
}
複製程式碼
對比CacheDispatcher,發現少了快取佇列,不過也很好理解,因為既然都到了網路這邊了,說明快取肯定GG了,所以只需要在獲取到網路請求結果之後,放入快取中就行了。
run
run方法其實跟CacheDispatcher是一樣的,只是processRequest有些區別
private void processRequest() throws InterruptedException {
long startTimeMs = SystemClock.elapsedRealtime();
//從佇列中取出一個佇列
Request<?> request = mQueue.take();
try {
request.addMarker("network-queue-take");
//請求取消,直接finished
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
request.notifyListenerResponseNotUsable();
return;
}
addTrafficStatsTag(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");
request.notifyListenerResponseNotUsable();
return;
}
// 解析網路請求資料
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
//如果請求結果需要快取,那麼快取請求的結果
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
// 將解析好的資料傳送給主執行緒
request.markDelivered();
mDelivery.postResponse(request, response);
request.notifyListenerResponseReceived(response);
} catch (VolleyError volleyError) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
request.notifyListenerResponseNotUsable();
} 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);
request.notifyListenerResponseNotUsable();
}
}
複製程式碼
quit
public void quit() {
mQuit = true;
//中斷執行緒
interrupt();
}
複製程式碼
Cache
Volley的快取主要是磁碟快取,首先Volley提供了一個Cache介面,然後DiskBasedCache實現了這個介面,下面說一下這兩個類
Cache
public interface Cache {
Entry get(String key);
void put(String key, Entry entry);
void initialize();
void invalidate(String key, boolean fullExpire);
void remove(String key);
void clear();
class Entry {
public byte[] data;
public String etag;
public long serverDate;
public long lastModified;
public long ttl;
public long softTtl;
public Map<String, String> responseHeaders = Collections.emptyMap();
public List<Header> allResponseHeaders;
public boolean isExpired() {
return this.ttl < System.currentTimeMillis();
}
public boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
}
複製程式碼
很常規的介面,只不過快取的value不是請求的結果,而是封裝了請求的資料的一個Entry,可以對快取做一些判斷。
DiskBaseCache
成員變數
//當前快取的容量
private long mTotalSize = 0;
//快取的路徑
private final File mRootDirectory;
//分配的最大快取容量
private final int mMaxCacheSizeInBytes;
//預設的最大快取容量
private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;
//快取的負載因子,到達這個點之後會自動進行快取清理
private static final float HYSTERESIS_FACTOR = 0.9f;
//底層採用LinkedHashMap實現Lru演算法,按照使用的順序進行排序
private final Map<String, CacheHeader> mEntries =
new LinkedHashMap<String, CacheHeader>(16, .75f, true);
複製程式碼
構造方法
public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
mRootDirectory = rootDirectory;
mMaxCacheSizeInBytes = maxCacheSizeInBytes;
}
public DiskBasedCache(File rootDirectory) {
this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
}
複製程式碼
通過快取的大小跟路徑初始化DiskBasedCache
put
*/
@Override
public synchronized void put(String key, Entry entry) {
//檢查容量是否合理,不合理就進行刪除
pruneIfNeeded(entry.data.length);
//獲取快取的檔案
File file = getFileForKey(key);
try {
BufferedOutputStream fos = new BufferedOutputStream(createOutputStream(file));
CacheHeader e = new CacheHeader(key, entry);
boolean success = e.writeHeader(fos);
if (!success) {
fos.close();
VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());
throw new IOException();
}
fos.write(entry.data);
fos.close();
//快取資料
putEntry(key, e);
return;
} catch (IOException e) {
}
boolean deleted = file.delete();
if (!deleted) {
VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
}
}
複製程式碼
pruneIfNeed
private void pruneIfNeeded(int neededSpace) {
//如果現有容量+即將儲存的容量小於最大容量,返回
if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
return;
}
long before = mTotalSize;
int prunedFiles = 0;
long startTime = SystemClock.elapsedRealtime();
Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();
//遍歷LinkedHashMap,刪除連結串列頭部的資料
while (iterator.hasNext()) {
Map.Entry<String, CacheHeader> entry = iterator.next();
CacheHeader e = entry.getValue();
boolean deleted = getFileForKey(e.key).delete();
if (deleted) {
mTotalSize -= e.size;
} else {
VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
e.key, getFilenameForKey(e.key));
}
iterator.remove();
prunedFiles++;
if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
break;
}
}
}
複製程式碼
get
@Override
public synchronized Entry get(String key) {
//通過key獲取快取的entry
CacheHeader entry = mEntries.get(key);
//如果entry為null的話直接返回
if (entry == null) {
return null;
}
//通過key獲取到file檔案
File file = getFileForKey(key);
try {
CountingInputStream cis = new CountingInputStream(
new BufferedInputStream(createInputStream(file)), file.length());
try {
CacheHeader entryOnDisk = CacheHeader.readHeader(cis);
if (!TextUtils.equals(key, entryOnDisk.key)) {
// File was shared by two keys and now holds data for a different entry!
VolleyLog.d("%s: key=%s, found=%s",
file.getAbsolutePath(), key, entryOnDisk.key);
// Remove key whose contents on disk have been replaced.
removeEntry(key);
return null;
}
byte[] data = streamToBytes(cis, cis.bytesRemaining());
//將解析好的資料返回
return entry.toCacheEntry(data);
} finally {
// Any IOException thrown here is handled by the below catch block by design.
//noinspection ThrowFromFinallyBlock
cis.close();
}
} catch (IOException e) {
VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
remove(key);
return null;
}
}
複製程式碼
RetryPolicy
成員變數
private int mCurrentTimeoutMs;//超時時間
private int mCurrentRetryCount;//已重試次數
private final int mMaxNumRetries;//最大重試次數
private final float mBackoffMultiplier;//失敗後重連的間隔因子
public static final int DEFAULT_TIMEOUT_MS = 2500;//預設超時時間
public static final int DEFAULT_MAX_RETRIES = 1;//預設重試次數
public static final float DEFAULT_BACKOFF_MULT = 1f;//預設的失敗之後重連的間隔因子為1
複製程式碼
構造方法
public DefaultRetryPolicy() {
this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
}
public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
mCurrentTimeoutMs = initialTimeoutMs;
mMaxNumRetries = maxNumRetries;
mBackoffMultiplier = backoffMultiplier;
}
複製程式碼
傳入超時時間,最大重試次數,重試間隔
retry
@Override
public void retry(VolleyError error) throws VolleyError {
mCurrentRetryCount++;
//計算重試時間
mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
if (!hasAttemptRemaining()) {
//如果到達最大次數,還是失敗就拋異常
throw error;
}
}
複製程式碼
Image
Volley不僅支援網路請求,還可以用來載入圖片,主要相關的兩個核心類是ImageLoader跟ImageRequest
ImageLoader
成員變數
private final RequestQueue mRequestQueue;//請求佇列
private int mBatchResponseDelayMs = 100;//請求響應結果傳送延時
private final ImageCache mCache;//圖片快取
//用HashMap來儲存延時的請求
private final HashMap<String, BatchedImageRequest> mInFlightRequests =
new HashMap<String, BatchedImageRequest>();
//HashMap來儲存延時的請求響應結果
private final HashMap<String, BatchedImageRequest> mBatchedResponses =
new HashMap<String, BatchedImageRequest>();
//切換執行緒的Handler
private final Handler mHandler = new Handler(Looper.getMainLooper());
複製程式碼
構造方法
public ImageLoader(RequestQueue queue, ImageCache imageCache) {
mRequestQueue = queue;
mCache = imageCache;
}
複製程式碼
get
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) {
// 檢測是否在主執行緒
throwIfNotOnMainThread();
//通過轉換得到快取的key
final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
// 從快取中查詢對應的bitmap
Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
if (cachedBitmap != null) {
//找到直接返回
ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
imageListener.onResponse(container, true);
return container;
}
// 快取失敗,初始化ImageContainer
ImageContainer imageContainer =
new ImageContainer(null, requestUrl, cacheKey, imageListener);
//回撥
imageListener.onResponse(imageContainer, true);
// 判斷當前的請求是否在mBatchedResponses中
BatchedImageRequest request = mInFlightRequests.get(cacheKey);
if (request != null) {
// If it is, add this request to the list of listeners.
request.addContainer(imageContainer);
return imageContainer;
}
// 傳達
Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType,cacheKey);
mRequestQueue.add(newRequest);
mInFlightRequests.put(cacheKey, new BatchedImageRequest(newRequest, imageContainer));
return imageContainer;
}
複製程式碼
get方法返回的是一個ImageContainer,裡面包好了很多跟Image相關的資訊,類似Cache,mCacheKey,mRequestUrl,mListener。
ImageRequest
繼承關係
ImageRequest繼承自Request,然後定義的泛型是Bitmap
成員變數
//超時時間
public static final int DEFAULT_IMAGE_TIMEOUT_MS = 1000;
//預設的重試次數
public static final int DEFAULT_IMAGE_MAX_RETRIES = 2;
//預設重試延遲因子
public static final float DEFAULT_IMAGE_BACKOFF_MULT = 2f;
private final Object mLock = new Object();//全域性物件鎖
private Response.Listener<Bitmap> mListener;//回撥監聽
private final Config mDecodeConfig;//解碼的配置資訊
private final int mMaxWidth;//ImageView傳入的最大寬度
private final int mMaxHeight;//ImageView傳入的最大高度
private final ScaleType mScaleType;//縮放型別
private static final Object sDecodeLock = new Object();//解碼的同步鎖
複製程式碼
構造方法
@Deprecated
public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight,
Config decodeConfig, Response.ErrorListener errorListener) {
this(url, listener, maxWidth, maxHeight,
ScaleType.CENTER_INSIDE, decodeConfig, errorListener);
}
public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight,
ScaleType scaleType, Config decodeConfig, Response.ErrorListener errorListener) {
super(Method.GET, url, errorListener);
setRetryPolicy(new DefaultRetryPolicy(DEFAULT_IMAGE_TIMEOUT_MS, DEFAULT_IMAGE_MAX_RETRIES,
DEFAULT_IMAGE_BACKOFF_MULT));
mListener = listener;
mDecodeConfig = decodeConfig;
mMaxWidth = maxWidth;
mMaxHeight = maxHeight;
mScaleType = scaleType;
}
複製程式碼
構造方法裡面都是一些配置資訊,沒什麼好說的
cancel
@Override
public void cancel() {
super.cancel();
synchronized (mLock) {
mListener = null;
}
}
複製程式碼
跟前面的一個套路,不解釋
doParse
網路請求回來之後,經過傳遞最終到了doParse方法
private Response<Bitmap> doParse(NetworkResponse response) {
//拿到位元組陣列
byte[] data = response.data;
BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
Bitmap bitmap = null;
if (mMaxWidth == 0 && mMaxHeight == 0) {
//傳入的寬高都為0,不縮放,直接返回原始尺寸
decodeOptions.inPreferredConfig = mDecodeConfig;
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
} else {
// If we have to resize this image, first get the natural bounds.
//先不載入進記憶體
decodeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
//獲取實際寬高
int actualWidth = decodeOptions.outWidth;
int actualHeight = decodeOptions.outHeight;
// 進行比例縮放,獲取時間寬高
int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
actualWidth, actualHeight, mScaleType);
int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
actualHeight, actualWidth, mScaleType);
// 進行縮放
decodeOptions.inJustDecodeBounds = false;
decodeOptions.inSampleSize =
findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
Bitmap tempBitmap =
BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
// 如果有必要的話,把得到的bitmap的最大邊進行壓縮來適應尺寸
if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
tempBitmap.getHeight() > desiredHeight)) {
bitmap = Bitmap.createScaledBitmap(tempBitmap,
desiredWidth, desiredHeight, true);
tempBitmap.recycle();
} else {
bitmap = tempBitmap;
}
}
if (bitmap == null) {
//解析失敗回撥
return Response.error(new ParseError(response));
} else {
//解析成功回撥
return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
}
}
複製程式碼
Volley載入圖片的大致流程就到了這裡,可能會有些奇怪,Volley並沒有採用Lrucache在記憶體中進行快取,是因為ImageRequest繼承自Request,所以就依賴於快取佇列,只有File的快取,可能這也是為什麼提到圖片載入大家可能會想到很多的Fresco,Glide,Picasso,但是很少人會想到Volley,提到Volley想到的還是網路請求,沒有LRUCache應該是最主要的原因了。
總結
Volley是一款擴充套件性很強的框架,抽取了Request基類,使用者可以自定義任意的Request,底層並沒有使用執行緒池,而是採用了四個網路執行緒從RequestQueue中取資料,如果是資料量較小的網路請求,使用起來比較靈活,如果網路請求比較耗時,那麼Volley的四個執行緒可能就不夠用了,我們可以建立更多的執行緒,但是執行緒的開銷會很高,而且對執行緒的利用率不大,這個時候就需要使用執行緒池了。Volley提供圖片載入的功能,但是沒有實現記憶體快取,所以效能不是很高。Volley原生沒有提供圖片上傳功能,不過由於他的擴充套件性很好,所以我們可以自己繼承Request類來實現這個功能。