Volley 是 Google 官方推出的一套 Android 網(wǎng)絡(luò)請(qǐng)求庫(kù)蜒蕾,特別適用于通信頻繁薇芝、數(shù)據(jù)量較小的網(wǎng)絡(luò)請(qǐng)求茫死。Volley 能夠根據(jù)當(dāng)前手機(jī)版本選擇 HttpClient (2.3 以下) 或者 HttpUrlConnection路翻。
除了 Volley割捅,Android 常用的網(wǎng)絡(luò)加載庫(kù)還有 OkHttp,Retrofit 等帚桩,關(guān)于這幾個(gè)的區(qū)別請(qǐng)移步 stormzhong 的ANDROID開(kāi)源項(xiàng)目推薦之「網(wǎng)絡(luò)請(qǐng)求哪家強(qiáng)]
Volley 框架擴(kuò)展性很強(qiáng),其源碼值得我們好好學(xué)習(xí)嘹黔。
先了解一些 Http 協(xié)議
Http 協(xié)議規(guī)范了客戶端和服務(wù)端數(shù)據(jù)請(qǐng)求的一套規(guī)則账嚎。Http 協(xié)議規(guī)范了 請(qǐng)求(Request)和響應(yīng)(Response)。
請(qǐng)求包含請(qǐng)求行儡蔓,請(qǐng)求頭(Header)郭蕉,body(可選)。如下圖所示(圖片來(lái)源于網(wǎng)絡(luò)):
請(qǐng)求行必須指定請(qǐng)求方式喂江,常見(jiàn)的有 GET召锈,POST等。
響應(yīng)和請(qǐng)求類似获询。也包含三部分涨岁,狀態(tài)行(含響應(yīng)碼)拐袜,響應(yīng)頭(Header),body(可選)梢薪。如下圖所示(圖片來(lái)源于網(wǎng)絡(luò)):
關(guān)于 Http 協(xié)議大致了解這些即可蹬铺。
Volley 對(duì) Http Request 和 Response 的封裝。
請(qǐng)求封裝
Http 請(qǐng)求包含三部分秉撇,請(qǐng)求行甜攀,請(qǐng)求頭,body琐馆。Volley 將其封裝成 Request规阀,Request 有幾個(gè)實(shí)現(xiàn)類,如 StringRequest瘦麸,JsonObjectRequest 等谁撼。Request 部分源碼如下:
public abstract class Request<T> implements Comparable<Request<T>> {
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;
}
// 對(duì)應(yīng)請(qǐng)求頭中的 Method
private final int mMethod;
// 對(duì)應(yīng)請(qǐng)求頭中的 URL
private final String mUrl;
// 對(duì)應(yīng)請(qǐng)求頭
public Map<String, String> getHeaders() throws AuthFailureError {
return Collections.emptyMap();
}
// 對(duì)應(yīng)請(qǐng)求實(shí)體
public byte[] getBody() throws AuthFailureError {
Map<String, String> params = getParams();
if (params != null && params.size() > 0) {
return encodeParameters(params, getParamsEncoding());
}
return null;
}
}
響應(yīng)封裝
Http 響應(yīng)包含三部分,狀態(tài)行瞎暑,響應(yīng)頭彤敛,body。
Volley 對(duì) Http 響應(yīng)用 NetworkResponse 封裝了赌,NetworkResponse 部分源碼如下:
public class NetworkResponse {
/** 狀態(tài)行中的響應(yīng)碼 */
public final int statusCode;
/** body */
public final byte[] data;
/** 響應(yīng)頭 */
public final Map<String, String> headers;
/** True if the server returned a 304 (Not Modified). */
public final boolean notModified;
/** Network roundtrip time in milliseconds. */
public final long networkTimeMs;
}
NetworkResponse 直觀的表示 Http 響應(yīng)返回的數(shù)據(jù)墨榄。但是我們的應(yīng)用程序不能直接使用其中的 body(字節(jié)數(shù)組),需要解析成某個(gè) Bean 對(duì)象勿她,這樣程序就可以直接使用 Bean 對(duì)象袄秩。因此還需要對(duì)響應(yīng)做進(jìn)一步的封裝。顯然這個(gè) Bean 的數(shù)據(jù)類型不可知逢并,可以使用 java 中的泛型之剧。
Volley 對(duì)響應(yīng)進(jìn)一步封裝成 Response,Response 部分源碼碼如下:
public class Response<T> {
/** Parsed response, or null in the case of error. */
public final T result;
/** Cache metadata for this response, or null in the case of error. */
public final Cache.Entry cacheEntry;
/** Detailed error information if <code>errorCode != OK</code>. */
public final VolleyError error;
}
由 NetworkResponse 到 Response 需要一個(gè)方法將 body 解析成某個(gè)對(duì)象砍聊。Volley 將這個(gè)方法定義在 Request 中背稼,而且是一個(gè)抽象方法〔r颍看下 StringRequest 對(duì)這個(gè)方法的實(shí)現(xiàn):
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
parsed = new String(response.data);
}
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
其實(shí)就是對(duì)返回的字節(jié)數(shù)組(body)解析成某個(gè)對(duì)象蟹肘,StringRequest 則解析成 String,JsonObjectRequest 則解析成 JsonObject 等俯树。我們可以定義一個(gè)自己的 Request帘腹,在 parseNetworkResponse 方法中將字節(jié)數(shù)組轉(zhuǎn)成 String,再用 Gson 解析成我們的對(duì)象许饿。
發(fā)起一個(gè)請(qǐng)求
Volley 的簡(jiǎn)單使用如下:
RequestQueue mQueue = Volley.newRequestQueue(context);
StringRequest stringRequest = new StringRequest("http://www.baidu.com",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.d("TAG", response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
mQueue.add(stringRequest);
Volley.newRequestQueue(context) 會(huì)創(chuàng)建一個(gè) RequestQueue阳欲,并調(diào)用RequestQueue #start()
。
RequestQueue 表示請(qǐng)求隊(duì)列,是 Volley 框架的核心類球化。RequestQueue 包含一個(gè)網(wǎng)絡(luò)隊(duì)列 mNetworkQueue 和一個(gè)緩存隊(duì)列 mCacheQueue 秽晚,作為其成員變量。
RequestQueue 部分源碼如下:
public class RequestQueue {
/** 緩存隊(duì)列 */
private final PriorityBlockingQueue<Request<?>> mCacheQueue =
new PriorityBlockingQueue<Request<?>>();
/** 網(wǎng)絡(luò)隊(duì)列 */
private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
new PriorityBlockingQueue<Request<?>>();
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();
}
}
}
在 start 方法中赊窥,開(kāi)啟一個(gè)緩存調(diào)度線程 CacheDispatcher爆惧,用來(lái)處理緩存。默認(rèn)開(kāi)啟四個(gè)網(wǎng)絡(luò)調(diào)度線程 NetworkDispatcher锨能,用來(lái)處理網(wǎng)絡(luò)請(qǐng)求扯再。CacheDispatcher 不斷的從 mCacheQueue 中取走 Request 并進(jìn)行分發(fā)處理。NetworkDispatcher 不斷的從 mNetworkQueue 取走 Request 并進(jìn)行分發(fā)處理址遇。如果隊(duì)列為空熄阻,則阻塞等等,這些線程都是常駐內(nèi)存隨時(shí)待命的倔约。顯然一個(gè)程序中最好只能有一個(gè) RequestQueue秃殉,如果采用多個(gè),應(yīng)該在適當(dāng)?shù)臅r(shí)候調(diào)用 RequestQueue#stop()
銷毀線程釋放內(nèi)存浸剩。
當(dāng)我們向 RequestQueue 添加一個(gè) Request 時(shí)钾军,如果 Request 可緩存則添加到 mCacheQueue ,否則添加到 mNetworkQueue 绢要。CacheDispatcher 拿到這個(gè) Request吏恭,如果緩存存在并且還沒(méi)過(guò)期,則解析數(shù)據(jù)并提及給主線程重罪,否則添加到 mNetworkQueue 交給 NetworkDispatcher 處理樱哼。流程如下:
緩存如何處理?
app 網(wǎng)絡(luò)數(shù)據(jù)緩存
Cache 的實(shí)現(xiàn)類為 DiskBasedCache剿配。DiskBasedCache 采用磁盤(pán)緩存和內(nèi)存緩存搅幅,但兩者緩存的數(shù)據(jù)不一樣。內(nèi)存緩存只緩存 CacheHeader呼胚,而磁盤(pán)緩存的是 Entry茄唐,只不過(guò)是將 Entry 中的數(shù)據(jù)按一定規(guī)則寫(xiě)到文件中,讀取緩存時(shí)再按照同樣的規(guī)則讀取到 Entry 中蝇更。另外沪编,Entry 比 CacheHeader 多了一個(gè)字節(jié)數(shù)組,顯然這是比較占內(nèi)存的簿寂,因此內(nèi)存緩存并沒(méi)有緩存 Entry。
當(dāng)緩存滿了之后如何處理呢宿亡?
DiskBasedCache 中有個(gè)方法是pruneIfNeeded(int neededSpace)
,每次執(zhí)行 put 的時(shí)都會(huì)先調(diào)用該方法常遂。這個(gè)方法就會(huì)刪除較早的緩存。內(nèi)存緩存保存在 mEntries 中挽荠。我們看下這個(gè)成員變量:
/** Map of the Key, CacheHeader pairs */
private final Map<String, CacheHeader> mEntries =
new LinkedHashMap<String, CacheHeader>(16, .75f, true);
LinkedHashMap 有個(gè)構(gòu)造函數(shù)為 LinkedHashMap( int initialCapacity, float loadFactor, boolean accessOrder)
最后一個(gè)參數(shù) accessOrder 就表示是否按照訪問(wèn)順序排列克胳。當(dāng) accessOrder 為 true平绩,最后執(zhí)行 get 或者 put 的元素會(huì)在 LinkedHashMap 的尾部。
這樣 pruneIfNeeded 方法就很容易找到較早的緩存并將其刪除漠另。
服務(wù)端緩存
當(dāng)客戶端發(fā)起一個(gè) Http 請(qǐng)求捏雌,如果服務(wù)端返回 304,表示請(qǐng)求的資源緩存仍能有效笆搓。這樣就能減少數(shù)據(jù)傳輸性湿。當(dāng)然,這種方式需要客戶端攜帶額外的頭信息满败,Volley 已經(jīng)幫我們做了這部分肤频。直接看相應(yīng)的源碼:
public class BasicNetwork implements Network {
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
// 省略...
Map<String, String> headers = new HashMap<String, String>();
addCacheHeaders(headers, request.getCacheEntry());
// before request
httpResponse = mHttpStack.performRequest(request, headers);
StatusLine statusLine = httpResponse.getStatusLine();
int statusCode = statusLine.getStatusCode();
responseHeaders = convertHeaders(httpResponse.getAllHeaders());
// Http 304
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
}
}
private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) {
// If there's no cache entry, we're done.
if (entry == null) {
return;
}
if (entry.etag != null) {
headers.put("If-None-Match", entry.etag);
}
if (entry.lastModified > 0) {
Date refTime = new Date(entry.lastModified);
headers.put("If-Modified-Since", DateUtils.formatDate(refTime));
}
}
}
從上可以看到 Volley 在請(qǐng)求頭添加了一個(gè) etag 和 lastModified,這些數(shù)據(jù)來(lái)自上次請(qǐng)求的響應(yīng)頭中算墨,Volley對(duì)其做了緩存宵荒,并且在下一次請(qǐng)求時(shí)添加到請(qǐng)求頭中。這樣服務(wù)端就能比較客戶端發(fā)送的 etag 和自己的 etag净嘀,如果相等报咳,說(shuō)明請(qǐng)求的資源未發(fā)生變化,服務(wù)端返回304挖藏∈钊校客戶端則對(duì)響應(yīng)碼做判斷,如果為 304熬苍,說(shuō)明本地緩存有效稍走。
主線程回調(diào)
CacheDispatcher 和 NetworkDispatcher 都是運(yùn)行在非主線程當(dāng)中的,而我們的 UI 必須在主線程中更新柴底。Volley 采用的是 Handler 來(lái)通知主線程更新 UI婿脸。
Request 中有一個(gè) deliverResponse 和 deliverError,一個(gè)是成功回調(diào)柄驻,另一個(gè)是失敗回調(diào)狐树。那么這兩個(gè)方法是什么時(shí)候被執(zhí)行的呢?
看下 NetworkDispatcher 部分源碼(省略部分代碼)
public class NetworkDispatcher extends Thread {
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
try {
request = mQueue.take();
} catch (InterruptedException e) {
}
try{
// 發(fā)起網(wǎng)絡(luò)請(qǐng)求
NetworkResponse networkResponse = mNetwork.performRequest(request);
// 解析
Response<?> response = request.parseNetworkResponse(networkResponse);
// 寫(xiě)緩存
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
}
// 分發(fā)結(jié)果鸿脓,通知主線程
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
// 分發(fā)失敗
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyError volleyError = new VolleyError(e);
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
// 分發(fā)失敗
mDelivery.postError(request, volleyError);
}
}
}
}
和主線程相關(guān)的就是 mDelivery.postResponse(request, response)
; 和 mDelivery.postError(request, volleyError)
抑钟。
我們看下 NetworkDispatcher 中 mDelivery 是如何創(chuàng)建的∫翱蓿看下 NetworkDispatcher 源碼發(fā)現(xiàn)是在構(gòu)造函數(shù)在塔,于是去 RequestQueue 找 NetworkDispatcher 對(duì)象的創(chuàng)建過(guò)程。在 start 方法中會(huì)創(chuàng)建 NetworkDispatcher 拨黔,并傳入一個(gè) mDelivery 對(duì)象蛔溃,而 mDelivery 在 RequestQueue 的構(gòu)造函數(shù)中已經(jīng)完成了初始化,看下相關(guān)源碼:
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;
}
由此可見(jiàn)在 NetworkDispatcher 中 mDelivery 的實(shí)際類型是 ExecutorDelivery。ExecutorDelivery 的構(gòu)造函數(shù)接收一個(gè) Handler 用來(lái)往主線程發(fā)消息贺待。
看下 ExecutorDelivery 部分源碼:
public class ExecutorDelivery implements ResponseDelivery {
/** Used for posting responses, typically to the main thread. */
private final Executor mResponsePoster;
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);
}
};
}
@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");
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));
}
}
postResponse 和 postError 最終都會(huì)提交 一個(gè) Runnable 給 mResponsePoster 執(zhí)行徽曲,而 mResponsePoster 則將這個(gè) Runnable 提交給 Handler 去執(zhí)行。Handler 接收到 Runnable 之后最終會(huì)執(zhí)行 mRequest.deliverResponse(mResponse.result)
或者 mRequest.deliverError(mResponse.error)
完成主線程的回調(diào)麸塞。
小結(jié)
關(guān)于 Volley 的源碼大致先解讀這些秃臣,重點(diǎn)在于理順整個(gè)邏輯,其他的像 HttpClient哪工,HttpUrlConnection 的網(wǎng)絡(luò)操作建議直接閱讀源碼奥此。