一、前言
Volley 是一個基于 HTTP 的網(wǎng)絡(luò)開源庫,讓 Android 應(yīng)用更快更容易地連接網(wǎng)絡(luò),在 GitHub 上可以找到它的源項目。Volley 具有以下優(yōu)點:
* 自動調(diào)度網(wǎng)絡(luò)請求。
* 支持多并發(fā)網(wǎng)絡(luò)連接。
* 支持緩存。
* 支持請求優(yōu)先級。
* 支持取消請求,可以取消單個請求,也可以取消包含多個請求的請求塊。
* 支持自定義。
* 支持異步數(shù)據(jù)排序功能。
* 支持調(diào)試和具備跟蹤工具。
Volley 適用于 RPC(遠程過程調(diào)用:Remote Procedure Call)類型操作,例如將搜索結(jié)果頁面作為結(jié)構(gòu)化數(shù)據(jù)獲取。Volley 可以很容易與所有協(xié)議集成,支持原始字符串,圖像和 JSON。
Volley 不適合大型下載或流媒體操作,因為 Volley 在解析期間將所有響應(yīng)保存在內(nèi)存中。對于大型下載操作,可以考慮使用 DownloadManager。
最簡單添加 Volley 的方法是將以下依賴項添加到應(yīng)用程序的 build.gradle 文件中:
dependencies {
...
compile 'com.android.volley:volley:1.1.1'
}
除此之外,你還可以克隆 Volley 項目庫并將其設(shè)置為庫項目:
- 通過在命令行鍵入以下內(nèi)容來克隆項目庫:
git clone https://github.com/google/volley
- 將下載的源作為 Android 庫模塊導(dǎo)入到應(yīng)用項目中。
二、使用 Volley 發(fā)送請求
使用 Volley 之前,必須將 android.permission.INTERNET 權(quán)限添加到應(yīng)用的清單中。不然,應(yīng)用無法連接到網(wǎng)絡(luò)。
1. 使用 newRequestQueue
Volley 提供了一個便捷的方法 Volley.newRequestQueue 為你設(shè)置并啟動 RequestQueue 隊列,該 RequestQueue 使用默認(rèn)值。例如:
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);
Volley 總是在主線程上傳遞已解析的響應(yīng),這樣可以很方便地使用接收到的數(shù)據(jù)填充 UI 控件。
2. 發(fā)送請求
要發(fā)送請求,只需構(gòu)建一個請求并使用 add() 將其添加到 RequestQueue,如上所示。添加請求后,請求將通過網(wǎng)絡(luò)獲取服務(wù),解析并傳遞其原始響應(yīng)。
當(dāng)調(diào)用 add() 時,Volley 運行一個高速緩存處理線程和一個網(wǎng)絡(luò)分派線程池。當(dāng)向隊列添加請求時,它會被緩存線程拾取并進行分類:如果請求可以從緩存中獲取服務(wù),則緩存響應(yīng)將在緩存線程上進行解析,并將解析后的響應(yīng)在主線程上傳遞。如果無法從緩存中為請求提供服務(wù),則將其置于網(wǎng)絡(luò)隊列中。第一個可用的網(wǎng)絡(luò)線程從隊列中獲取請求,執(zhí)行 HTTP 事務(wù),在子線程上解析響應(yīng),然后將響應(yīng)寫入緩存,并將解析的響應(yīng)發(fā)送回主線程來進行傳遞。
可以在任意線程中添加請求,但響應(yīng)始終在主線程上傳遞。

3. 取消請求
要取消請求,請對 Request對象調(diào)用 cancel()。一旦取消,Volley 保證你的響應(yīng)處理回調(diào)永遠不會被調(diào)用。一般可以在 Activity 的 onStop() 方法中取消所有待處理的請求。
但是,這樣的話你必須跟蹤所有正在進行的請求。有一種更簡單的方法:你可以使用一個標(biāo)記對象與每個請求進行關(guān)聯(lián)。然后,使用此標(biāo)記對象獲得取消請求的范圍。例如,使用 Activity 將所有由它發(fā)出的請求進行標(biāo)記,并從 onStop() 中調(diào)用 requestQueue.cancelAll(this)。同樣,在 ViewPager 選項卡中使用各自的選項卡標(biāo)記所有縮略圖圖像請求,并在滑動時取消,以確保新選項卡不會被另一個選項卡的請求持有。
以下是使用字符串標(biāo)記的示例:
- 定義標(biāo)記并將其添加到你的請求中。
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)記的請求。
@Override
protected void onStop () {
super.onStop();
if (mRequestQueue != null) {
mRequestQueue.cancelAll(TAG);
}
}
取消請求時要注意,該請求的響應(yīng)是否是必要的。
三、設(shè)置 RequestQueue
1. 設(shè)置網(wǎng)絡(luò)和緩存
RequestQueue 需要兩樣?xùn)|西都完成它的工作:一個是用于執(zhí)行請求傳輸?shù)木W(wǎng)絡(luò),一個是用于處理緩存的緩存。Volley 工具箱中已經(jīng)有標(biāo)準(zhǔn)的實現(xiàn):DiskBasedCache 提供帶有內(nèi)存索引的單文件響應(yīng)緩存,BasicNetwork 根據(jù)你首選的 HTTP 客戶端提供網(wǎng)絡(luò)傳輸。
BasicNetwork 是 Volley 的默認(rèn)網(wǎng)絡(luò)實現(xiàn)。BasicNetwork 必須被用來連接到網(wǎng)絡(luò)的 HTTP 客戶端初始化。這個客戶端通常是 HttpURLConnection。
下面代碼段顯示了初始化 BasicNetwork 的步驟包括設(shè)置 RequestQueue:
RequestQueue mRequestQueue;
// Instantiate the cache
Cache cache = new DiskBasedCache(getCacheDir(), 1024 * 1024); // 1MB cap
// Set up the network to use HttpURLConnection as the HTTP client.
Network network = new BasicNetwork(new HurlStack());
// Instantiate the RequestQueue with the cache and network.
mRequestQueue = new RequestQueue(cache, network);
// Start the queue
mRequestQueue.start();
String url ="http://www.example.com";
// Formulate the request and handle the response.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// Do something with the response
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// Handle error
}
});
// Add the request to the RequestQueue.
mRequestQueue.add(stringRequest);
// ...
你可以在任何時候創(chuàng)建 RequestQueue,并在得到響應(yīng)后調(diào)用 stop() 來實現(xiàn)單次請求。
但更常見的情況是創(chuàng)建一個 RequestQueue 的單例,使得它在應(yīng)用的生命周期內(nèi)保持運行。
2. 使用單例模式
設(shè)置一個 RequestQueue 的單例通常效率最高。推薦的方法是實現(xiàn)封裝 RequestQueue 和其他 Volley 功能的單例類。另一種方法是創(chuàng)建 Application 的子類并在 Application.onCreate() 方法中設(shè)置 RequestQueue,但是這種方法并不推薦,因為靜態(tài)單例可以以更模塊化的方式提供相同的功能。
一個關(guān)鍵概念是 RequestQueue 必須使用 Application 上下文進行實例化,而不是 Activity 上下文。這樣可以確保 RequestQueue在應(yīng)用的生命周期內(nèi)持續(xù)使用,而不是每次重新創(chuàng)建 Activtiy 時重新實例化(例如當(dāng)用戶旋轉(zhuǎn)設(shè)備時)。
這里是一個單類,它提供 RequestQueue 和 ImageLoader 功能:
public class MySingleton {
private static MySingleton mInstance;
private RequestQueue mRequestQueue;
private ImageLoader mImageLoader;
private static Context mCtx;
private MySingleton(Context context) {
mCtx = context;
mRequestQueue = getRequestQueue();
mImageLoader = new ImageLoader(mRequestQueue,
new ImageLoader.ImageCache() {
private final LruCache<String, Bitmap>
cache = new LruCache<String, Bitmap>(20);
@Override
public Bitmap getBitmap(String url) {
return cache.get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
cache.put(url, bitmap);
}
});
}
public static synchronized MySingleton getInstance(Context context) {
if (mInstance == null) {
mInstance = new MySingleton(context);
}
return mInstance;
}
public RequestQueue getRequestQueue() {
if (mRequestQueue == null) {
// getApplicationContext() is key, it keeps you from leaking the
// Activity or BroadcastReceiver if someone passes one in.
mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
}
return mRequestQueue;
}
public <T> void addToRequestQueue(Request<T> req) {
getRequestQueue().add(req);
}
public ImageLoader getImageLoader() {
return mImageLoader;
}
}
以下是使用單例類執(zhí)行 RequestQueue 操作的一些示例:
// Get a RequestQueue
RequestQueue queue = MySingleton.getInstance(this.getApplicationContext()).
getRequestQueue();
// ...
// Add a request (in this example, called stringRequest) to your RequestQueue.
MySingleton.getInstance(this).addToRequestQueue(stringRequest);
四、提出標(biāo)準(zhǔn)請求
Volley 支持的常見請求類型:
StringRequest。指定 URL 并接收原始字符串作為響應(yīng)。
JsonObjectRequest 和 JsonArrayRequest(兩個都是 JsonRequest 的子類)。指定 URL 并分別獲取 JSON 對象或數(shù)組作為響應(yīng)。
如果你的預(yù)期響應(yīng)是這些類型之一,則不必實現(xiàn)自定義請求。
1. 請求 JSON
Volley 為 JSON 請求提供以下類:
JsonArrayRequest- 使用給定的 URL 獲取 JSONArray 響應(yīng)體。JsonObjectRequest- 使用給定的 URL 獲取 JSONObject 響應(yīng)體,允許將 JSONObject 作為請求體的一部分傳入。
這兩個類都基于公共基類 JsonRequest。下面代碼段是提取 JSON 數(shù)據(jù)并在 UI 中將其顯示為文本:
String url = "http://my-json-feed";
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest
(Request.Method.GET, url, null, new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
mTextView.setText("Response: " + response.toString());
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// TODO: Handle error
}
});
// Access the RequestQueue through your singleton class.
MySingleton.getInstance(this).addToRequestQueue(jsonObjectRequest);
五、實現(xiàn)自定義請求
對于那些不支持開箱即用的數(shù)據(jù)類型,我們需要實現(xiàn)自定義請求類型。
1. 寫一個自定義請求
大多數(shù)請求在工具箱中都有現(xiàn)成的實現(xiàn),如果響應(yīng)是字符串、圖像或 JSON,則不需要實現(xiàn)自定義請求。
對于需要實現(xiàn)自定義請求的情況,你只需執(zhí)行以下操作:
擴展
Request<T>類,其中<T>表示請求所期望的已解析響應(yīng)的類型。因此,如果解析后的響應(yīng)是字符串,則通過擴展Request<String>創(chuàng)建自定義請求。實現(xiàn)抽象方法,
parseNetworkResponse()和deliverResponse()。
1.1 parseNetworkResponse
對于給定類型(例如字符串,圖像或 JSON),Response 封裝解析的響應(yīng)用來傳遞。以下是一個示例實現(xiàn) parseNetworkResponse():
@Override
protected Response<T> parseNetworkResponse(
NetworkResponse response) {
try {
String json = new String(response.data,
HttpHeaderParser.parseCharset(response.headers));
return Response.success(gson.fromJson(json, clazz),
HttpHeaderParser.parseCacheHeaders(response));
}
// handle errors
// ...
}
請注意:parseNetworkResponse() 將 NetworkResponse 作為參數(shù) ,其中包含響應(yīng)有效負(fù)載作為 byte []、HTTP 狀態(tài)代碼和響應(yīng)頭。
自定義實現(xiàn)必須返回一個 Response<T>,其中包含你自定義的響應(yīng)對象、緩存元數(shù)據(jù)或錯誤。
如果你的協(xié)議具有非標(biāo)準(zhǔn)緩存語義,你可以自己構(gòu)造一個 Cache.Entry,但大多數(shù)請求如下使用:
return Response.success(myDecodedObject,
HttpHeaderParser.parseCacheHeaders(response));
Volley 在工作線程中調(diào)用 parseNetworkResponse()。這確保了耗時的解析操作(例如將 JPEG 解碼為 Bitmap)不會阻塞 UI 線程。
1.2 deliverResponse
Volley 在 parseNetworkResponse() 方法中攜帶返回的對象回到主線程。大多數(shù)請求在此處調(diào)用回調(diào)接口,例如:
protected void deliverResponse(T response) {
listener.onResponse(response);
六、示例:GsonRequest
Gson 是一個通過反射將 Java 對象轉(zhuǎn)換為 JSON 或者將 JSON 轉(zhuǎn)換為 Java 對象的開源庫。你可以定義擁有相同 JSON 鍵字段的 Java 對象,將類對象傳給 Gson,Gson 自動使用響應(yīng)數(shù)據(jù)填充字段。
下面是使用 Gson 解析 Volley 請求的完整實現(xiàn):
public class GsonRequest<T> extends Request<T> {
private final Gson gson = new Gson();
private final Class<T> clazz;
private final Map<String, String> headers;
private final Listener<T> listener;
/**
* Make a GET request and return a parsed object from JSON.
*
* @param url URL of the request to make
* @param clazz Relevant class object, for Gson's reflection
* @param headers Map of request headers
*/
public GsonRequest(String url, Class<T> clazz, Map<String, String> headers,
Listener<T> listener, ErrorListener errorListener) {
super(Method.GET, url, errorListener);
this.clazz = clazz;
this.headers = headers;
this.listener = listener;
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
return headers != null ? headers : super.getHeaders();
}
@Override
protected void deliverResponse(T response) {
listener.onResponse(response);
}
@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
String json = new String(
response.data,
HttpHeaderParser.parseCharset(response.headers));
return Response.success(
gson.fromJson(json, clazz),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JsonSyntaxException e) {
return Response.error(new ParseError(e));
}
}
}