一、前言
Volley 是一個基于 HTTP 的網(wǎng)絡開源庫,讓 Android 應用更快更容易地連接網(wǎng)絡,在 GitHub 上可以找到它的源項目。Volley 具有以下優(yōu)點:
* 自動調(diào)度網(wǎng)絡請求。
* 支持多并發(fā)網(wǎng)絡連接。
* 支持緩存。
* 支持請求優(yōu)先級。
* 支持取消請求,可以取消單個請求,也可以取消包含多個請求的請求塊。
* 支持自定義。
* 支持異步數(shù)據(jù)排序功能。
* 支持調(diào)試和具備跟蹤工具。
Volley 適用于 RPC(遠程過程調(diào)用:Remote Procedure Call)類型操作,例如將搜索結(jié)果頁面作為結(jié)構(gòu)化數(shù)據(jù)獲取。Volley 可以很容易與所有協(xié)議集成,支持原始字符串,圖像和 JSON。
Volley 不適合大型下載或流媒體操作,因為 Volley 在解析期間將所有響應保存在內(nèi)存中。對于大型下載操作,可以考慮使用 DownloadManager
。
最簡單添加 Volley 的方法是將以下依賴項添加到應用程序的 build.gradle
文件中:
dependencies {
...
compile 'com.android.volley:volley:1.1.1'
}
除此之外,你還可以克隆 Volley 項目庫并將其設(shè)置為庫項目:
- 通過在命令行鍵入以下內(nèi)容來克隆項目庫:
git clone https://github.com/google/volley
- 將下載的源作為 Android 庫模塊導入到應用項目中。
二、使用 Volley 發(fā)送請求
使用 Volley 之前,必須將 android.permission.INTERNET
權(quán)限添加到應用的清單中。不然,應用無法連接到網(wǎng)絡。
1. 使用 newRequestQueue
Volley 提供了一個便捷的方法 Volley.newRequestQueue
為你設(shè)置并啟動 RequestQueue
隊列,該 RequestQueue
使用默認值。例如:
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 總是在主線程上傳遞已解析的響應,這樣可以很方便地使用接收到的數(shù)據(jù)填充 UI 控件。
2. 發(fā)送請求
要發(fā)送請求,只需構(gòu)建一個請求并使用 add()
將其添加到 RequestQueue
,如上所示。添加請求后,請求將通過網(wǎng)絡獲取服務,解析并傳遞其原始響應。
當調(diào)用 add()
時,Volley 運行一個高速緩存處理線程和一個網(wǎng)絡分派線程池。當向隊列添加請求時,它會被緩存線程拾取并進行分類:如果請求可以從緩存中獲取服務,則緩存響應將在緩存線程上進行解析,并將解析后的響應在主線程上傳遞。如果無法從緩存中為請求提供服務,則將其置于網(wǎng)絡隊列中。第一個可用的網(wǎng)絡線程從隊列中獲取請求,執(zhí)行 HTTP 事務,在子線程上解析響應,然后將響應寫入緩存,并將解析的響應發(fā)送回主線程來進行傳遞。
可以在任意線程中添加請求,但響應始終在主線程上傳遞。
3. 取消請求
要取消請求,請對 Request
對象調(diào)用 cancel()
。一旦取消,Volley 保證你的響應處理回調(diào)永遠不會被調(diào)用。一般可以在 Activity 的 onStop()
方法中取消所有待處理的請求。
但是,這樣的話你必須跟蹤所有正在進行的請求。有一種更簡單的方法:你可以使用一個標記對象與每個請求進行關(guān)聯(lián)。然后,使用此標記對象獲得取消請求的范圍。例如,使用 Activity 將所有由它發(fā)出的請求進行標記,并從 onStop()
中調(diào)用 requestQueue.cancelAll(this)
。同樣,在 ViewPager 選項卡中使用各自的選項卡標記所有縮略圖圖像請求,并在滑動時取消,以確保新選項卡不會被另一個選項卡的請求持有。
以下是使用字符串標記的示例:
- 定義標記并將其添加到你的請求中。
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()
方法中,取消所有具有此標記的請求。
@Override
protected void onStop () {
super.onStop();
if (mRequestQueue != null) {
mRequestQueue.cancelAll(TAG);
}
}
取消請求時要注意,該請求的響應是否是必要的。
三、設(shè)置 RequestQueue
1. 設(shè)置網(wǎng)絡和緩存
RequestQueue 需要兩樣東西都完成它的工作:一個是用于執(zhí)行請求傳輸?shù)木W(wǎng)絡,一個是用于處理緩存的緩存。Volley 工具箱中已經(jīng)有標準的實現(xiàn):DiskBasedCache
提供帶有內(nèi)存索引的單文件響應緩存,BasicNetwork
根據(jù)你首選的 HTTP 客戶端提供網(wǎng)絡傳輸。
BasicNetwork
是 Volley 的默認網(wǎng)絡實現(xiàn)。BasicNetwork
必須被用來連接到網(wǎng)絡的 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
,并在得到響應后調(diào)用 stop()
來實現(xiàn)單次請求。
但更常見的情況是創(chuàng)建一個 RequestQueue
的單例,使得它在應用的生命周期內(nèi)保持運行。
2. 使用單例模式
設(shè)置一個 RequestQueue
的單例通常效率最高。推薦的方法是實現(xiàn)封裝 RequestQueue
和其他 Volley 功能的單例類。另一種方法是創(chuàng)建 Application
的子類并在 Application.onCreate()
方法中設(shè)置 RequestQueue
,但是這種方法并不推薦,因為靜態(tài)單例可以以更模塊化的方式提供相同的功能。
一個關(guān)鍵概念是 RequestQueue
必須使用 Application
上下文進行實例化,而不是 Activity
上下文。這樣可以確保 RequestQueue
在應用的生命周期內(nèi)持續(xù)使用,而不是每次重新創(chuàng)建 Activtiy
時重新實例化(例如當用戶旋轉(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);
四、提出標準請求
Volley 支持的常見請求類型:
StringRequest。指定 URL 并接收原始字符串作為響應。
JsonObjectRequest 和 JsonArrayRequest(兩個都是 JsonRequest 的子類)。指定 URL 并分別獲取 JSON 對象或數(shù)組作為響應。
如果你的預期響應是這些類型之一,則不必實現(xiàn)自定義請求。
1. 請求 JSON
Volley 為 JSON 請求提供以下類:
JsonArrayRequest
- 使用給定的 URL 獲取 JSONArray 響應體。JsonObjectRequest
- 使用給定的 URL 獲取 JSONObject 響應體,允許將 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),如果響應是字符串、圖像或 JSON,則不需要實現(xiàn)自定義請求。
對于需要實現(xiàn)自定義請求的情況,你只需執(zhí)行以下操作:
擴展
Request<T>
類,其中<T>
表示請求所期望的已解析響應的類型。因此,如果解析后的響應是字符串,則通過擴展Request<String>
創(chuàng)建自定義請求。實現(xiàn)抽象方法,
parseNetworkResponse()
和deliverResponse()
。
1.1 parseNetworkResponse
對于給定類型(例如字符串,圖像或 JSON),Response 封裝解析的響應用來傳遞。以下是一個示例實現(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ù) ,其中包含響應有效負載作為 byte []、HTTP 狀態(tài)代碼和響應頭。
自定義實現(xiàn)必須返回一個 Response<T>
,其中包含你自定義的響應對象、緩存元數(shù)據(jù)或錯誤。
如果你的協(xié)議具有非標準緩存語義,你可以自己構(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 自動使用響應數(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));
}
}
}