Volley
源碼分析三部曲
Volley 源碼解析之網絡請求
Volley 源碼解析之圖片請求
Volley 源碼解析之緩存機制java
Volley 是 Google 推出的一款網絡通訊框架,很是適合數據量小、通訊頻繁的網絡請求,支持併發、緩存和容易擴展、調試等;不過不太適合下載大文件、大量數據的網絡請求,由於volley在解析期間將響應放到內存中,咱們可使用okhttp或者系統提供的
DownloadManager
來下載文件。android
首先在工程引入volley的library:git
dependencies {
implementation 'com.android.volley:volley:1.1.1'
}
複製代碼
而後須要咱們打開網絡權限,我這裏直接貼出官網簡單請求的示例代碼:github
final TextView mTextView = (TextView) findViewById(R.id.text);
// ...
// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.google.com";
// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// Display the first 500 characters of the response string.
mTextView.setText("Response is: "+ response.substring(0,500));
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
mTextView.setText("That didn't work!");
}
});
// Add the request to the RequestQueue.
queue.add(stringRequest);
複製代碼
使用相對簡單,回調直接在主線程,咱們取消某個請求直接這樣操做:數組
定義一個標記添加到requests中瀏覽器
public static final String TAG = "MyTag";
StringRequest stringRequest; // Assume this exists.
RequestQueue mRequestQueue; // Assume this exists.
// Set the tag on the request.
stringRequest.setTag(TAG);
// Add the request to the RequestQueue.
mRequestQueue.add(stringRequest);
複製代碼
而後咱們能夠在 onStop() 中取消全部標記的請求緩存
@Override
protected void onStop () {
super.onStop();
if (mRequestQueue != null) {
mRequestQueue.cancelAll(TAG);
}
}
複製代碼
咱們先從Volley這個類入手:安全
public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {
BasicNetwork network;
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
network = new BasicNetwork(new HurlStack());
} else {
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info =
context.getPackageManager().getPackageInfo(packageName, /* flags= */ 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
network =
new BasicNetwork(
new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
}
} else {
network = new BasicNetwork(stack);
}
return newRequestQueue(context, network);
}
private static RequestQueue newRequestQueue(Context context, Network network) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, (BaseHttpStack) null);
}
複製代碼
當咱們傳遞一個Context
的時候,首先爲BaseHttpStack
爲null,會執行到建立BaseHttpStack
,BaseHttpStack
是一個網絡具體的處理請求,Volley
默認提供了基於HttpURLCollection
的HurlStack
和基於HttpClient
的HttpClientStack
。Android6.0移除了HttpClient
,Google官方推薦使用HttpURLCollection
類做爲替換。因此這裏在API大於9的版本是用的是HurlStack
,爲何這樣選擇,詳情可見這篇博客Android訪問網絡,使用HttpURLConnection仍是HttpClient?。咱們使用的是默認的構造,BaseHttpStack
傳入爲null,若是咱們想使用自定義的okhttp替換底層,咱們直接繼承HttpStack
重寫便可,也能夠自定義Network
和RequestQueue
,Volley
的高擴展性充分體現。接下來則建立一個Network
對象,而後實例化RequestQueue
,首先建立了一個用於緩存的文件夾,而後建立了一個磁盤緩存,將文件緩存到指定目錄的硬盤上,默認大小是5M,可是大小能夠配置。接下來調用RequestQueue
的start()
方法進行啓動,咱們進入這個方法查看一下:服務器
public void start() {
stop();
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher =
new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
複製代碼
開始啓動的時候先中止全部的請求線程和網絡緩存線程,而後實例化一個緩存線程並運行,而後一個循環開啓DEFAULT_NETWORK_THREAD_POOL_SIZE
(4)個網絡請求線程並運行,一共就是5個線程在後臺運行,不斷的等待網絡請求的到來。網絡
構造了RequestQueue
以後,咱們調用add()
方法將相應的Request
傳入就開始執行網絡請求了,咱們看看這個方法:
public <T> Request<T> add(Request<T> request) {
//將請求隊列和請求關聯起來
request.setRequestQueue(this);
//添加到正在請求中可是還未完成的集合中
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
//設置請求的一個序列號,經過原子變量的incrementAndGet方法,
//以原子方式給當前值加1並獲取新值實現請求的優先級
request.setSequence(getSequenceNumber());
//添加一個調試信息
request.addMarker("add-to-queue");
//若是不須要緩存則直接加到網絡的請求隊列,默認每個請求都是緩存的,
//若是不須要緩存須要調用Request的setShouldCache方法來修改
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
//加到緩存的請求隊列
mCacheQueue.add(request);
return request;
}
複製代碼
關鍵地方都寫了註釋,主要做用就是將請求加到請求隊列,執行網絡請求或者從緩存中獲取結果。網絡和緩存的請求都是一個優先級阻塞隊列,按照優先級出隊。上面幾個關鍵步驟,添加到請求集合裏面還有設置優先級以及添加到緩存和請求隊列都是線程安全的,要麼加鎖,要麼使用線程安全的隊列或者原子操做。
接下來咱們看看添加到CacheDispatcher
緩存請求隊列的run
方法:
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//初始化DiskBasedCache的緩存類
mCache.initialize();
while (true) {
try {
processRequest();
} catch (InterruptedException e) {
if (mQuit) {
Thread.currentThread().interrupt();
return;
}
VolleyLog.e(
"Ignoring spurious interrupt of CacheDispatcher thread; "
+ "use quit() to terminate it");
}
}
}
複製代碼
接下來的重點是看看processRequest()
這個方法:
private void processRequest() throws InterruptedException {
//從緩存隊列取出請求
final Request<?> request = mCacheQueue.take();
processRequest(request);
}
@VisibleForTesting
void processRequest(final Request<?> request) throws InterruptedException {
request.addMarker("cache-queue-take");
// 若是請求被取消,咱們能夠經過RequestQueue的回調接口來監聽
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
return;
}
// 從緩存中獲取Cache.Entry
Cache.Entry entry = mCache.get(request.getCacheKey());
//沒有取到緩存
if (entry == null) {
request.addMarker("cache-miss");
// 緩存未命中,對於可緩存的請求先去檢查是否有相同的請求是否已經在運行中,
//若是有的話先加入請求等待隊列,等待請求完成,返回true;若是返回false則表示第一次請求
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
//加入到網絡請求的阻塞隊列
mNetworkQueue.put(request);
}
return;
}
// 若是緩存徹底過時,處理過程跟上面相似
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
//設置請求緩存的entry到這個request中
request.setCacheEntry(entry);
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mNetworkQueue.put(request);
}
return;
}
//緩存命中,將數據解析並返回到request的抽象方法中
request.addMarker("cache-hit");
Response<?> response =
request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
//判斷請求結果是否須要刷新
if (!entry.refreshNeeded()) {
// 未過時的緩存命中,經過ExecutorDelivery回調給咱們的request子類的接口中,
// 咱們在使用的時候就能夠經過StringRequest、JsonRequest等拿到結果,
// 切換到主線程也是在這個類裏執行的
mDelivery.postResponse(request, response);
} else {
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// 將這個響應標記爲中間值,即這個響應是新鮮的,那麼第二個響應正在請求隨時到來
response.intermediate = true;
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
//發起網絡請求,這裏爲何直接調用上面的mNetworkQueue.put(request);呢,
//主要是爲了添加一個已經分發的標記,在響應分發的時候再也不回調給用戶,
//否則就回調了兩次
mDelivery.postResponse(
request,
response,
new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Restore the interrupted status
Thread.currentThread().interrupt();
}
}
});
} else {
//這裏第三個參數傳遞null,不用再去分發,由於已經有相同的請求已經在執行,
//直接添加到了等待請求的列表中,而後返回的時候從已經執行的請求收到響應
mDelivery.postResponse(request, response);
}
}
}
複製代碼
這部分主要是對請求的緩存判斷,是否過時以及須要刷新緩存。咱們調用取消全部請求或者取消某個請求實質上就是對mCanceled
這個變量賦值,而後在緩存線程或者網絡線程裏面都回去判斷這個值,就完成了取消。上面的isExpired
和refreshNeeded
,兩個區別就是,前者若是過時就直接請求最新的內容,後者就是還在新鮮的時間內,可是把內容返回給用戶仍是會發起請求,二者一個與ttl值相比,另外一個與softTtl相比。
其中有一個WaitingRequestManager,若是有相同的請求那麼就須要一個暫存的地方,這個類就是作的這個操做
private static class WaitingRequestManager implements Request.NetworkRequestCompleteListener {
//全部等待請求的集合,鍵是緩存的key
private final Map<String, List<Request<?>>> mWaitingRequests = new HashMap<>();
private final CacheDispatcher mCacheDispatcher;
WaitingRequestManager(CacheDispatcher cacheDispatcher) {
mCacheDispatcher = cacheDispatcher;
}
//請求接受到一個有效的響應,後面等待的相同請求就可使用這個響應
@Override
public void onResponseReceived(Request<?> request, Response<?> response) {
//若是緩存爲空或者已通過期,那麼就釋放等待的請求
if (response.cacheEntry == null || response.cacheEntry.isExpired()) {
onNoUsableResponseReceived(request);
return;
}
String cacheKey = request.getCacheKey();
//等待的請求的集合
List<Request<?>> waitingRequests;
synchronized (this) {
//從map裏面移除這個請求的集合
waitingRequests = mWaitingRequests.remove(cacheKey);
}
if (waitingRequests != null) {
if (VolleyLog.DEBUG) {
VolleyLog.v(
"Releasing %d waiting requests for cacheKey=%s.",
waitingRequests.size(), cacheKey);
}
// 裏面全部的請求都分發到相應的回調執行,下面會講解
for (Request<?> waiting : waitingRequests) {
mCacheDispatcher.mDelivery.postResponse(waiting, response);
}
}
}
//沒有收到相應,則須要釋放請求
@Override
public synchronized void onNoUsableResponseReceived(Request<?> request) {
String cacheKey = request.getCacheKey();
List<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
if (waitingRequests != null && !waitingRequests.isEmpty()) {
if (VolleyLog.DEBUG) {
VolleyLog.v(
"%d waiting requests for cacheKey=%s; resend to network",
waitingRequests.size(), cacheKey);
}
//下面這個請求執會從新執行,將這個移除添加到
Request<?> nextInLine = waitingRequests.remove(0);
//將剩下的請求放到等待請求的map中
mWaitingRequests.put(cacheKey, waitingRequests);
//在request裏面註冊一個回調接口,由於從新開始請求,須要從新註冊一個監聽,
//後面請求成功失敗以及取消均可以收到回調
nextInLine.setNetworkRequestCompleteListener(this);
try {
//從上面if判斷方法能夠得出:waitingRequests != null && !waitingRequests.isEmpty()
//排除了第一次請求失敗、取消的狀況,後面的那個條件則表示這個等待請求隊列必需要有一個請求,
//同時知足纔會執行這裏面的代碼,通常只要這裏面的請求執行成功一次後續全部的請求都會被移除,
//因此這裏對多個請求的狀況,失敗一次,那麼後續的請求會繼續執行
mCacheDispatcher.mNetworkQueue.put(nextInLine);
} catch (InterruptedException iex) {
VolleyLog.e("Couldn't add request to queue. %s", iex.toString());
// Restore the interrupted status of the calling thread (i.e. NetworkDispatcher)
Thread.currentThread().interrupt();
// Quit the current CacheDispatcher thread.
mCacheDispatcher.quit();
}
}
}
//對於能夠緩存的請求,相同緩存的請求已經在運行中就添加到一個發送隊列,
//等待運行中的隊列請求完成,返回true表示已經有請求在運行,false則是第一次執行
private synchronized boolean maybeAddToWaitingRequests(Request<?> request) {
String cacheKey = request.getCacheKey();
// 存在相同的請求則把請求加入到相同緩存鍵的集合中
if (mWaitingRequests.containsKey(cacheKey)) {
// There is already a request in flight. Queue up.
List<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
//若是包含相同的請求可是有多是第二次請求,前面第一次請求插入null了
if (stagedRequests == null) {
stagedRequests = new ArrayList<>();
}
request.addMarker("waiting-for-response");
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.d("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
}
return true;
} else {
//第一次請求那麼則插入一個null,表示當前有一個請求正在運行
mWaitingRequests.put(cacheKey, null);
//註冊一個接口監聽
request.setNetworkRequestCompleteListener(this);
if (VolleyLog.DEBUG) {
VolleyLog.d("new request, sending to network %s", cacheKey);
}
return false;
}
}
}
複製代碼
這個類主要是避免相同的請求屢次請求,並且在NetworkDispatcher
裏面也會經過這個接口回調相應的值在這裏執行,最終好比在網絡請求返回30四、請求取消或者異常那麼都會在這裏來處理,若是收到響應則會把值回調給用戶,後面的請求也不會再去請求,若是無效的響應則會作一些釋放等待的請求操做,請求完成也會將後面相同的請求回調給用戶,三個方法都在不一樣的地方發揮做用。
咱們接下來看看NetworkDispatcher
網絡請求隊列的run
方法中的processRequest
方法:
@VisibleForTesting
void processRequest(Request<?> request) {
long startTimeMs = SystemClock.elapsedRealtime();
try {
request.addMarker("network-queue-take");
// 請求被取消了,就不執行網絡請求,
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
request.notifyListenerResponseNotUsable();
return;
}
addTrafficStatsTag(request);
// 這裏就是執行網絡請求的地方
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// 若是服務器返回304響應,即沒有修改過,
//緩存依然是有效的而且是在須要刷新的有效期內,那麼則不須要解析響應
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) {
...
}
}
複製代碼
這裏纔是網絡請求的真正執行以及解析分發的地方,重點看兩個地方的代碼,執行和解析,咱們先看看執行網絡請求這個代碼,執行的地方是BasicNetwork.performRequest
,下面看看這個方法:
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();
while (true) {
HttpResponse httpResponse = null;
byte[] responseContents = null;
List<Header> responseHeaders = Collections.emptyList();
try {
// 構造緩存的頭部,添加If-None-Match和If-Modified-Since,都是http/1.1中控制協商緩存的兩個字段,
// If-None-Match:客服端再次發起請求時,攜帶上次請求返回的惟一標識Etag值,
//服務端用攜帶的值和最後修改的值做對比,最後修改時間大於攜帶的字段值則返回200,不然304;
// If-Modified-Since:客服端再次發起請求時,攜帶上次請求返回的Last-Modified值,
//服務端用攜帶的值和服務器的Etag值做對比,一致則返回304
Map<String, String> additionalRequestHeaders =
getCacheHeaders(request.getCacheEntry());
//由於如今通常的sdk都是大於9的,那麼這裏執行的就是HurlStack的executeRequest方法,
//執行網絡請求,和咱們平時使用HttpURLConnection請求網絡大體相同
httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);
int statusCode = httpResponse.getStatusCode();
responseHeaders = httpResponse.getHeaders();
// 服務端返回304時,那麼就表示資源無更新,能夠繼續使用緩存的值
if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
Entry entry = request.getCacheEntry();
if (entry == null) {
return new NetworkResponse(
HttpURLConnection.HTTP_NOT_MODIFIED,
/* data= */ null,
/* notModified= */ true,
SystemClock.elapsedRealtime() - requestStart,
responseHeaders);
}
// 將緩存頭和響應頭組合在一塊兒,一次響應就完成了
List<Header> combinedHeaders = combineHeaders(responseHeaders, entry);
return new NetworkResponse(
HttpURLConnection.HTTP_NOT_MODIFIED,
entry.data,
/* notModified= */ true,
SystemClock.elapsedRealtime() - requestStart,
combinedHeaders);
}
// 若是返回204,執行成功,沒有數據,這裏須要檢查
InputStream inputStream = httpResponse.getContent();
if (inputStream != null) {
responseContents =
inputStreamToBytes(inputStream, httpResponse.getContentLength());
} else {
//返回204,就返回一個空的byte數組
responseContents = new byte[0];
}
// if the request is slow, log it.
long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
logSlowRequests(requestLifetime, request, responseContents, statusCode);
if (statusCode < 200 || statusCode > 299) {
throw new IOException();
}
return new NetworkResponse(
statusCode,
responseContents,
/* notModified= */ false,
SystemClock.elapsedRealtime() - requestStart,
responseHeaders);
} catch (SocketTimeoutException e) {
//異常進行從新請求等...
}
}
}
複製代碼
這裏主要執行了添加緩存頭併發起網絡請求,而後將返回值組裝成一個NetworkResponse
值返回,接下來咱們看看是如何解析這個值的,解析是由Request
的子類去實現的,咱們就看系統提供的StringRequest
:
@Override
@SuppressWarnings("DefaultCharset")
protected Response<String> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
// Since minSdkVersion = 8, we can't call
// new String(response.data, Charset.defaultCharset())
// So suppress the warning instead.
parsed = new String(response.data);
}
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
複製代碼
咱們能夠看到將值組裝成一個String,而後組裝成一個Response
返回,接下來看看這裏如何將這個值回調給用戶的這個方法mDelivery.postResponse(request, response)
,這裏咱們先重點看看這個類ExecutorDelivery
:
public class ExecutorDelivery implements ResponseDelivery {
//構造執行已提交的Runnable任務對象
private final Executor mResponsePoster;
//這裏在RequestQueue構造參數中初始化,new ExecutorDelivery(new Handler(Looper.getMainLooper())),
//那麼這裏runnable就經過綁定主線程的Looper的Handler對象投遞到主線程中執行
public ExecutorDelivery(final Handler handler) {
// Make an Executor that just wraps the handler.
mResponsePoster =
new Executor() {
@Override
public void execute(Runnable command) {
handler.post(command);
}
};
}
public ExecutorDelivery(Executor executor) {
mResponsePoster = executor;
}
//這個方法就是咱們NetworkDispatcher裏面調用的方法,調用下面這個三個參數的構造方法
@Override
public void postResponse(Request<?> request, Response<?> response) {
postResponse(request, response, null);
}
@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
//構造了一個ResponseDeliveryRunnable類,傳入execute,如今這個runnable就是在主線程裏執行
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
@Override
public void postError(Request<?> request, VolleyError error) {
request.addMarker("post-error");
Response<?> response = Response.error(error);
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
}
/** A Runnable used for delivering network responses to a listener on the main thread. */
@SuppressWarnings("rawtypes")
private static class ResponseDeliveryRunnable implements Runnable {
private final Request mRequest;
private final Response mResponse;
private final Runnable mRunnable;
public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
mRequest = request;
mResponse = response;
mRunnable = runnable;
}
@SuppressWarnings("unchecked")
@Override
public void run() {
//請求取消,那麼就不分發給用戶
if (mRequest.isCanceled()) {
mRequest.finish("canceled-at-delivery");
return;
}
// 根據isSuccess這個值來提供相應的回調給用戶,調用Response會經過error的值是否爲null來肯定這個值,
//咱們調用VolleyError這個構造函數的時候就爲這個值就爲false
if (mResponse.isSuccess()) {
mRequest.deliverResponse(mResponse.result);
} else {
mRequest.deliverError(mResponse.error);
}
// 若是這是一個在新鮮的時間內的請求的響應,就添加一個標記,不然就結束
if (mResponse.intermediate) {
mRequest.addMarker("intermediate-response");
} else {
mRequest.finish("done");
}
// 在CacheDispatcher裏面當請求第一次請求時直接調用三個參數的構造方法,經過這個runnable就執行run方法
if (mRunnable != null) {
mRunnable.run();
}
}
}
}
複製代碼
上面方法主要是將值回調給用戶,那麼整個網絡請求大體就完成了,其中還涉及不少細節的東西,可是大體流程是走通了,不得不說這個庫有不少值得咱們學習的地方。
如今咱們看官網的一張圖,總結一下整個流程:
咱們能夠看到首先是請求添加到RequestQueue
裏,首先是添加到緩存隊列,而後查看是否已經緩存,若是有而且在有效期內的緩存直接回調給用戶,若是沒有查找到,那麼則須要添加到網絡請求隊列從新請求而且解析響應、寫入緩存在發送到主線程給用戶回調。