一作彤、前言
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(遠(yuǎn)程過程調(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)向隊列添加請求時兄纺,它會被緩存線程拾取并進(jìn)行分類:如果請求可以從緩存中獲取服務(wù),則緩存響應(yīng)將在緩存線程上進(jìn)行解析化漆,并將解析后的響應(yīng)在主線程上傳遞估脆。如果無法從緩存中為請求提供服務(wù),則將其置于網(wǎng)絡(luò)隊列中座云。第一個可用的網(wǎng)絡(luò)線程從隊列中獲取請求疙赠,執(zhí)行 HTTP 事務(wù),在子線程上解析響應(yīng)朦拖,然后將響應(yīng)寫入緩存圃阳,并將解析的響應(yīng)發(fā)送回主線程來進(jìn)行傳遞。
可以在任意線程中添加請求璧帝,但響應(yīng)始終在主線程上傳遞捍岳。
3. 取消請求
要取消請求,請對 Request
對象調(diào)用 cancel()
睬隶。一旦取消锣夹,Volley 保證你的響應(yīng)處理回調(diào)永遠(yuǎn)不會被調(diào)用。一般可以在 Activity 的 onStop()
方法中取消所有待處理的請求理疙。
但是晕城,這樣的話你必須跟蹤所有正在進(jìn)行的請求。有一種更簡單的方法:你可以使用一個標(biāo)記對象與每個請求進(jìn)行關(guān)聯(lián)窖贤。然后砖顷,使用此標(biāo)記對象獲得取消請求的范圍。例如赃梧,使用 Activity 將所有由它發(fā)出的請求進(jìn)行標(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
上下文進(jìn)行實例化,而不是 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í)行以下操作:
擴(kuò)展
Request<T>
類爹耗,其中<T>
表示請求所期望的已解析響應(yīng)的類型。因此谜喊,如果解析后的響應(yīng)是字符串潭兽,則通過擴(kuò)展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));
}
}
}