Volley介紹
Volley 是 Google 在 2013 Google I/O 大會(huì)上發(fā)布推出的 Android 異步網(wǎng)絡(luò)請(qǐng)求框架和圖片加載框架,其最明顯的一個(gè)優(yōu)點(diǎn)就是特別適合數(shù)據(jù)量不大但是通信頻繁的場(chǎng)景,最明顯的缺點(diǎn)就是大數(shù)據(jù)傳輸表現(xiàn)的很糟糕。(適合于數(shù)據(jù)量小,通信頻繁的網(wǎng)絡(luò)操作)
Volley提供了以下的便利功能
- JSON數(shù)據(jù)和圖像等的異步下載;
- 網(wǎng)絡(luò)請(qǐng)求排序(scheduling);
- 網(wǎng)絡(luò)請(qǐng)求優(yōu)先級(jí)處理;
- 緩存;
- 多級(jí)別取消請(qǐng)求;
- 與Activity生命周期聯(lián)動(dòng)(Activity結(jié)束時(shí)同時(shí)取消所有網(wǎng)絡(luò)請(qǐng)求);
newRequestQueue的使用
Volley的用法很簡(jiǎn)單,發(fā)起一條HTTP GET請(qǐng)求,然后接收HTTP響應(yīng)。首先需要獲取到一個(gè)RequestQueue對(duì)象,可以調(diào)用如下方法獲取到:
RequestQueue newQueue = Volley.newRequestQueue(context);
這里拿到的RequestQueue是一個(gè)請(qǐng)求隊(duì)列對(duì)象,它可以緩存所有的HTTP請(qǐng)求,然后按照一定的算法并發(fā)地發(fā)出這些請(qǐng)求。
RequestQueue內(nèi)部的設(shè)計(jì)就是非常合適高并發(fā)的,因此我們不必為每一次HTTP請(qǐng)求都創(chuàng)建一個(gè)RequestQueue對(duì)象,這是非常浪費(fèi)資源的,基本上在每一個(gè)需要和網(wǎng)絡(luò)交互的Activity中創(chuàng)建一個(gè)RequestQueue對(duì)象就足夠了。
Request的使用
Request是請(qǐng)求的基類,擁有以下子類:
- ClearCacheRequest
- JsonRequest
- ImageRequest
- StringRequest
在基類中定義了一個(gè)內(nèi)部接口類 Method 分別定義了集中常見的請(qǐng)求方式
public interface Method {
int DEPRECATED_GET_OR_POST = -1;
int GET = 0;
int POST = 1;
int PUT = 2;
int DELETE = 3;
int PATCH = 4;
}
同時(shí)還定義了一個(gè)枚舉 Priority 定義了一些優(yōu)先級(jí)的常量
public static enum Priority {
LOW,
NORMAL,
HIGH,
IMMEDIATE;
private Priority() {
}
}
StringRequestd的使用
StringRequest的構(gòu)造函數(shù)需要傳入四個(gè)參數(shù):
public StringRequest(int method, String url, Listener<String> listener, ErrorListener errorListener) {
super(method, url, errorListener);
this.mListener = listener;
}
- 第一個(gè)參數(shù)是表示請(qǐng)求的方式 默認(rèn)是0 也就是 GET 請(qǐng)求
- 第二個(gè)參數(shù)是目標(biāo)服務(wù)器的URL地址
- 第三個(gè)參數(shù)是服務(wù)器響應(yīng)成功的回調(diào)
- 第四個(gè)參數(shù)是服務(wù)器響應(yīng)失敗的回調(diào)
將這個(gè)StringRequest對(duì)象添加到RequestQueue里面就可以了。使用Volley時(shí),可以從任何線程開始請(qǐng)求,但響應(yīng)始終傳遞到了主線程上。
以下是閱讀Android Developer文檔中的VolleyDemo:
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() {
@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);
以上就用Volley發(fā)起了一個(gè)簡(jiǎn)單的HTTP 的 GET 請(qǐng)求。
Volley發(fā)出Request
Volley發(fā)出網(wǎng)絡(luò)請(qǐng)求,阻塞I/O和解析數(shù)據(jù)都是在子線程中完成的,您可以在任何線程添加網(wǎng)絡(luò)請(qǐng)求,但是響應(yīng)始終都是傳遞到主線程上。
分析一下 Android Developer 上的這幅圖:

- RequestQueue會(huì)維護(hù)一個(gè)緩存調(diào)度線程(CacheDispatcher)
- 同時(shí)還會(huì)維護(hù)一網(wǎng)絡(luò)調(diào)度線程池(NetworkDispatcher[])->線程池默認(rèn)大小為4
-
Volley.newRequestQueue(context);的代碼中在data/data/包名/cache/目錄下創(chuàng)建了一個(gè)volley的文件夾用于存放緩存(本地緩存) - 同時(shí)創(chuàng)建一個(gè) RequestQueue 類,并調(diào)用了其start() 方法
BasicNetwork network1 = new BasicNetwork((HttpStack)stack);//將在后文提到
RequestQueue queue1 = new RequestQueue(new DiskBasedCache(cacheDir), network1);
queue1.start();
- start()方法看上去像是線程開啟的方法,但是 RequestQueue 并不是一個(gè)Thread類,不過(guò)在當(dāng)中它干的就是開啟線程的事。
public void start() {
this.stop();
this.mCacheDispatcher = new CacheDispatcher(this.mCacheQueue, this.mNetworkQueue, this.mCache, this.mDelivery);
this.mCacheDispatcher.start();
for(int i = 0; i < this.mDispatchers.length; ++i) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery);
this.mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
沒(méi)錯(cuò),它逐一開啟了緩存調(diào)度線程和所有的網(wǎng)絡(luò)調(diào)度線程
每一條線程就開始不斷的循環(huán)讀取cache隊(duì)列或者net隊(duì)列的頭
當(dāng)一個(gè)Request被添加到隊(duì)列中的時(shí)候,cache線程會(huì)首先對(duì)這個(gè)請(qǐng)求進(jìn)行篩選,如果這個(gè)請(qǐng)求的內(nèi)筒可以在緩存中找到,并且沒(méi)有過(guò)期,cache線程會(huì)自己解析響應(yīng)的內(nèi)容,并分發(fā)到主線程(UI);
如果緩存中沒(méi)有,這個(gè)request就會(huì)被加入到另一個(gè)NetworkQueue,所有真正準(zhǔn)備進(jìn)行網(wǎng)絡(luò)通信的request都在這里,第一個(gè)可用的net線程會(huì)從NetworkQueue中拿出一個(gè)request扔向服務(wù)器。當(dāng)響應(yīng)獲得數(shù)據(jù)之后,這個(gè)net線程會(huì)解析原始響應(yīng)數(shù)據(jù),寫入緩存,并把解析后的結(jié)果返回給主線程。
取消一個(gè)Request
要取消一個(gè)請(qǐng)求,調(diào)用cancel()即可。一旦取消,Volley保證你的響應(yīng)處理程序?qū)⒂肋h(yuǎn)不會(huì)被調(diào)用。這意味著在實(shí)踐中,你可以取消所有待定的請(qǐng)求在你Activity的onStop()方法中,你不必亂拋垃圾的響應(yīng)處理程序或者檢查getActivity() == NULL,無(wú)論onSaveInstanceState()是否已經(jīng)被調(diào)用。
你發(fā)出去的請(qǐng)求必須要自己保證是可控的,在這里還有一個(gè)更簡(jiǎn)單的方法:你可以標(biāo)記發(fā)送的每個(gè)請(qǐng)求對(duì)象,然后你可以使用這個(gè)標(biāo)簽來(lái)提供請(qǐng)求取消的范圍。
下面是一個(gè)使用字符串標(biāo)簽的例子:
-
定義您的標(biāo)簽同時(shí)將其添加到Request中
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); -
在您的Activity的 onStop() 方法中,取消所有被標(biāo)記的Request
@Override protected void onStop () { super.onStop(); if (mRequestQueue != null) { mRequestQueue.cancelAll(TAG); } }
取消請(qǐng)求時(shí)要小心。如果你是根據(jù)你的響應(yīng)處理程序來(lái)推進(jìn)一個(gè)狀態(tài)或啟動(dòng)另一個(gè)進(jìn)程,你需要考慮到這個(gè)。同樣,響應(yīng)處理程序?qū)⒉粫?huì)被調(diào)用。
BasicNetwork
現(xiàn)在我們來(lái)說(shuō)說(shuō) BasicNetwork,前文在講 RequestQueue 的時(shí)候有所提到,其中 BasicNetwork 是創(chuàng)建 RequestQueue 對(duì)象時(shí)當(dāng)中的一個(gè)參數(shù)
-
BasicNetwork 是Volley的一個(gè)默認(rèn)網(wǎng)絡(luò)實(shí)現(xiàn),App連接HTTP客戶端必須要對(duì)其初始化。最主要的是實(shí)現(xiàn)了 Network 接口中的
NetworkResponse performRequest(Request<?> var1) throws VolleyError;方法,其實(shí)這個(gè)接口中就只有這一個(gè)方法。
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();while(true) { HttpResponse httpResponse = null; Object responseContents = null; HashMap responseHeaders = new HashMap(); try { HashMap e = new HashMap(); this.addCacheHeaders(e, request.getCacheEntry()); httpResponse = this.mHttpStack.performRequest(request, e); StatusLine statusCode2 = httpResponse.getStatusLine(); int networkResponse1 = statusCode2.getStatusCode(); Map responseHeaders1 = convertHeaders(httpResponse.getAllHeaders()); if(networkResponse1 == 304) { return new NetworkResponse(304, request.getCacheEntry().data, responseHeaders1, true); } byte[] responseContents1 = this.entityToBytes(httpResponse.getEntity()); long requestLifetime = SystemClock.elapsedRealtime() - requestStart; this.logSlowRequests(requestLifetime, request, responseContents1, statusCode2); if(networkResponse1 != 200 && networkResponse1 != 201 && networkResponse1 != 202 && networkResponse1 != 204) { throw new IOException(); } return new NetworkResponse(networkResponse1, responseContents1, responseHeaders1, false); } catch (SocketTimeoutException var12) { attemptRetryOnException("socket", request, new TimeoutError()); } catch (ConnectTimeoutException var13) { attemptRetryOnException("connection", request, new TimeoutError()); } catch (MalformedURLException var14) { throw new RuntimeException("Bad URL " + request.getUrl(), var14); } catch (IOException var15) { boolean statusCode = false; NetworkResponse networkResponse = null; if(httpResponse == null) { throw new NoConnectionError(var15); } int statusCode1 = httpResponse.getStatusLine().getStatusCode(); VolleyLog.e("Unexpected response code %d for %s", new Object[]{Integer.valueOf(statusCode1), request.getUrl()}); if(responseContents == null) { throw new NetworkError(networkResponse); } networkResponse = new NetworkResponse(statusCode1, (byte[])responseContents, responseHeaders, false); if(statusCode1 != 401 && statusCode1 != 403) { if(statusCode1 != 400 && statusCode1 != 404 && statusCode1 != 406 && statusCode1 != 501) { throw new ServerError(networkResponse); } throw new ClientError(networkResponse); } attemptRetryOnException("auth", request, new AuthFailureError(networkResponse)); } } } 這個(gè)方法是網(wǎng)絡(luò)請(qǐng)求的具體實(shí)現(xiàn),以一個(gè)while大循環(huán)包裹,其中
httpResponse = this.mHttpStack.performRequest(request, e);是主要的網(wǎng)絡(luò)請(qǐng)求代碼,由HttpStack發(fā)出。在創(chuàng)建對(duì)象的時(shí)候傳入的對(duì)象。以下具體說(shuō)明-
創(chuàng)建 BasicNetwork 對(duì)象的時(shí)候,需要傳入一個(gè)HttpStack的對(duì)象。
HttpStack stack;
...
if(stack == null) {
if(VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}BasicNetwork network1 = new BasicNetwork((HttpStack)stack); -
可以看出在 Froyo(2.2) 之前,采用基于 HttpClient 的 HttpClientStack,之后使用基于 HttpURLConnection 的 HurlStack,原因呢是因?yàn)镕royo(2.2) HttpURLConnection 有個(gè)重大 Bug,調(diào)用 close() 函數(shù)會(huì)影響連接池,導(dǎo)致連接復(fù)用失效,所以在 Froyo 之前使用 HttpURLConnection 需要關(guān)閉 keepAlive。
private void disableConnectionReuseIfNecessary() { // 這是一個(gè)2.2版本之前的bug if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) { System.setProperty("http.keepAlive", "false"); } } 另外 Gingerbread(2.3) HttpURLConnection 默認(rèn)開啟了 gzip 壓縮,提高了 HTTPS 的性能,Ice Cream Sandwich(4.0) HttpURLConnection 支持了請(qǐng)求結(jié)果緩存。再加上 HttpURLConnection 本身 API 相對(duì)簡(jiǎn)單,所以對(duì) Android 來(lái)說(shuō),在 2.3 之后建議使用 HttpURLConnection,之前建議使用 AndroidHttpClient。下面我們來(lái)具體說(shuō)說(shuō)網(wǎng)絡(luò)請(qǐng)求接口HttpStack。
HttpStack接口
HttpStack接口中也只有一個(gè)方法HttpResponse performRequest(Request<?> var1, Map<String, String> var2) throws IOException, AuthFailureError;
在前文我們提到了HttpStack的兩個(gè)實(shí)現(xiàn)類 HttpClientStack 和 HurlStack,在實(shí)現(xiàn)的具體方法中真正進(jìn)行了網(wǎng)絡(luò)請(qǐng)求。
-
HurlStack中封裝了HttpURLConnection
...
URL parsedUrl1 = new URL(url);
HttpURLConnection connection = this.openConnection(parsedUrl1, request);
Iterator responseCode = map.keySet().iterator();
... HttpClientStack中封裝了HttpClient,也就是傳進(jìn)來(lái)的AndroidHttpClient。
Request的parseNetworkResponse方法
以上步驟,在NetworkDispatcher中收到了NetworkResponse這個(gè)返回值后又會(huì)調(diào)用Request的parseNetworkResponse()方法來(lái)解析NetworkResponse中的數(shù)據(jù),同時(shí)將數(shù)據(jù)寫入到緩存,這個(gè)方法的實(shí)現(xiàn)是交給Request的子類來(lái)完成的,因?yàn)椴煌N類的Request解析的方式也肯定不同。
ResponseDelivery接口
在獲取了解析后的數(shù)據(jù)之后,NetworkDispatcher的run()方法最后調(diào)用了this.mDelivery.postResponse(request, response);將數(shù)據(jù)post到主線程(UI線程),其中的 mDelivery 就是 ResponseDelivery接口的具體實(shí)現(xiàn)類 ExecutorDelivery對(duì)象,具體的方法如下:
@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
ExecutorDelivery 的構(gòu)造方法如下:
public ExecutorDelivery(final Handler handler) {
this.mResponsePoster = new Executor() {
public void execute(Runnable command) {
handler.post(command);
}
};
}
在創(chuàng)建其實(shí)例的時(shí)候傳入了 RequestQueue 實(shí)例的 mDelivery :
NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery);
而 mDelivery 就是創(chuàng)建 RequestQueue 時(shí)創(chuàng)建的主線程Handler:
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
如此就保證了 ExecutorDelivery 對(duì)象中的 run() 方法在主線程中運(yùn)行。
而在 run() 方法中最核心的代碼就是:
if(this.mResponse.isSuccess()) {
this.mRequest.deliverResponse(this.mResponse.result);
} else {
this.mRequest.deliverError(this.mResponse.error);
}
調(diào)用了Request的 deliverResponse(this.mResponse.result) 和 mRequest.deliverError(this.mResponse.error) 方法,其中就調(diào)用了需要重寫的實(shí)現(xiàn)Listener接口方法,將反饋發(fā)送到回調(diào)到UI線程。
這是后來(lái)想起加的一張NetworkDispatcher的工作流程圖:

有什么不對(duì)還請(qǐng)指出,謝謝指教。