Android之對Volley網路框架的一些理解
##前言
Volley這個網路框架大家並不陌生,它的優點網上一大堆, 適合網路通訊頻繁操作,並能同時實現多個網路通訊、擴充套件性強、通過介面配置之類的優點。在寫這篇文章之前,特意去了解並使用Volley這個網路框架,文章是對Volley的一點點理解,如有寫得不到位的地方,歡迎大家指出。
##用法
使用Volley的通用步驟就是通過Volley暴露的newRequestQueue方法,建立的我們的RequestQueue,接著往我們的RequestQueue中新增Request( StringRequest、JsonRequest、ImageRequest,以及你自己定義的Request)。
private void initNetWork() { RequestQueue queue = Volley.newRequestQueue(this); String url = ""; StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() { @Override public void onResponse(String response) { } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { } }); queue.add(stringRequest); queue.start(); }
步驟很簡單,總結就三步:
- 建立一個RequestQueue物件。
- 建立一個StringRequest物件。
- 將StringRequest物件新增到RequestQueue裡面。
上面的例項通過get方式請求資料的,post的方式也是很簡單,在建立Request的時候,第一個引數改為Request.Methode.POST:
private void initNetWork_1() { RequestQueue queue = Volley.newRequestQueue(this); String url = ""; StringRequest stringRequest = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() { @Override public void onResponse(String response) { } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { } }) { @Override protected Map<String, String> getParams() throws AuthFailureError { // TODO Auto-generated method stub return super.getParams(); } }; queue.add(stringRequest); queue.start(); }
仔細檢視上面POST和GET的不同方式的請求程式碼,可以看出POST的時候多了一個getParams方法,返回的Map型別,getParams獲取的資料是用於向伺服器提交的引數。
在這裡我們看到,如果每次都去定義Map,然後往裡面put鍵值對,這是一件很沮喪的事情,這裡給出一個工具類,可以通過解析物件轉換成我們需要的Map,程式碼如下:
package com.example.volleyproject; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Map; public class MapUtil { public static <T> Map<String, String> changeTtoMap(T m) { HashMap<String, String> map = new HashMap<String, String>(); // 獲取實體類的所有屬性 Field[] field = m.getClass().getDeclaredFields(); // 遍歷所有屬性 for (int j = 0; j < field.length; j++) { // 獲取屬性的名字 String name = field[j].getName(); String value = (String) getFieldValueObj(m, name); if (value != null && !value.equals("")) { map.put(name, value); } } return map; } /** * 獲取對應的屬性值 * * @param target * 物件 * @param fname * Filed * @return */ public static Object getFieldValueObj(Object target, String fname) { // 獲取欄位值 // 如:username 欄位,getUsername() if (target == null || fname == null || "".equals(fname)) {// 如果型別不匹配,直接退出 return ""; } Class clazz = target.getClass(); try { // 先通過getXxx()方法設定類屬性值 String methodname = "get" + Character.toUpperCase(fname.charAt(0)) + fname.substring(1); Method method = clazz.getDeclaredMethod(methodname); // 獲取定義的方法 if (!Modifier.isPublic(method.getModifiers())) { // 設定非共有方法許可權 method.setAccessible(true); } return (Object) method.invoke(target); // 執行方法回撥 } catch (Exception me) {// 如果get方法不存在,則直接設定類屬性值 try { Field field = clazz.getDeclaredField(fname); // 獲取定義的類屬性 if (!Modifier.isPublic(field.getModifiers())) { // 設定非共有類屬性許可權 field.setAccessible(true); } return (Object) field.get(target); // 獲取類屬性值 } catch (Exception fe) { } } return ""; } }
最後在getParams 呼叫這個工具類中的changeTtoMap方法即可:
private void initNetWork_1() {
RequestQueue queue = Volley.newRequestQueue(this);
String url = "";
final RequestParams params = new RequestParams();
params.id = "1";
params.name = "bill";
StringRequest stringRequest = new StringRequest(Request.Method.POST,
url, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
}) {
@Override
protected Map<String, String> getParams() throws AuthFailureError {
return MapUtil.changeTtoMap(params);
}
};
queue.add(stringRequest);
queue.start();
}
##Volley的淺談
所有操作都是基於上面講的三個步驟,第一步建立一個RequestQueue物件。
RequestQueue queue = Volley.newRequestQueue(this);
請求佇列(RequestQueue)不需要出現多個,因此我們可以在Application中進行初始化。
/**
* Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
*
* @param context A {@link Context} to use for creating the cache dir.
* @return A started {@link RequestQueue} instance.
*/
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null);
}
方法很簡單,建立的我們的請求佇列,這裡通過傳入Context來確定我們快取目錄。
/**
* Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
*
* @param context A {@link Context} to use for creating the cache dir.
* @param stack An {@link HttpStack} to use for the network, or null for default.
* @return A started {@link RequestQueue} instance.
*/
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
Network network = new BasicNetwork(stack);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
在Volley中,http的處理請求,預設 Android2.3 及以上基於 HttpURLConnection的 HurlStack,2.3 以下基於 HttpClient 的 HttpClientStack,通過上面的這段程式碼可知:
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
這裡面的HurlStack和HttpClientStack都實現了HttpStack這個介面,HttpStack是用於http請求,返回請求結果的。
public interface HttpStack {
/**
* Performs an HTTP request with the given parameters.
*
* <p>A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise,
* and the Content-Type header is set to request.getPostBodyContentType().</p>
*
* @param request the request to perform
* @param additionalHeaders additional headers to be sent together with
* {@link Request#getHeaders()}
* @return the HTTP response
*/
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError;
}
public class HttpClientStack implements HttpStack {
}
public class HurlStack implements HttpStack{
}
在根據SDK版本確定使用哪個Http請求方式後建立BasicNetwork物件,BasicNetwork實現了Network介面:
/**
* An interface for performing requests.
*/
public interface Network {
/**
* Performs the specified request.
* @param request Request to process
* @return A {@link NetworkResponse} with data and caching metadata; will never be null
* @throws VolleyError on errors
*/
public NetworkResponse performRequest(Request<?> request) throws VolleyError;
}
NetWork用於 呼叫HttpStack處理請求,並將結果轉換為可被ResponseDelivery處理的NetworkResponse。 這裡面的BasicNetwork就是一個具體是實現。
接著執行:
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
DiskBasedCache是我們快取的目錄:
public DiskBasedCache(File rootDirectory) {
this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
}
public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
mRootDirectory = rootDirectory;
mMaxCacheSizeInBytes = maxCacheSizeInBytes;
}
這裡面定義了快取的地址,預設最大是5MB。繼續檢視RequestQueue的構造器:
public RequestQueue(Cache cache, Network network) {
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
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;
}
在建立RequestQueue例項時進行了一系列的初始化(快取、HttpStack的處理請求、執行緒池大小、返回結果的分發)。
NetworkDispatcher是一個執行緒, 用於排程處理網路的請求。啟動後會不斷從網路請求佇列中取請求處理,佇列為空則等待,請求處理結束則將結果傳遞給ResponseDelivery去執行後續處理,並判斷結果是否要進行快取。
public class NetworkDispatcher extends Thread {
}
這裡面的ResponseDelivery用於返回結果分發介面,目前只有基於ExecutorDelivery的 handler 對應執行緒內進行分發。
ExecutorDelivery通過內部的handler對應的執行緒進行返回結果的分發:
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);
}
};
}
Executor框架是java 5引入的併發庫,把任務拆分為一些列的小任務,即Runnable,然後在提交給一個Executor執行,Executor.execute(Runnalbe) 。Executor在執行時使用內部的執行緒池完成操作。
這裡面傳遞進來的是new Handler(Looper.getMainLooper()),傳入UI執行緒的Handler的目的是用於UI更新,比如通過通過Volley進行圖片下載時的ImageView更新。
上面也提到過ResponseDelivery用於返回結果分發,目前只基於ExecutorDelivery的handler對應執行緒進行分發,由此我們找到資料分發的方法:
@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都呼叫了Executor.execute(Runnalbe)方法進行併發執行,ResponseDeliveryRunnable實現了Runnable介面並重寫run方法:
public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
mRequest = request;
mResponse = response;
mRunnable = runnable;
}
@Override
public void run() {
// If this request has canceled, finish it and don't deliver.
if (mRequest.isCanceled()) {
mRequest.finish("canceled-at-delivery");
return;
}
// Deliver a normal response or error, depending.
if (mResponse.isSuccess()) {
mRequest.deliverResponse(mResponse.result);
} else {
mRequest.deliverError(mResponse.error);
}
// If this is an intermediate response, add a marker, otherwise we're done
// and the request can be finished.
if (mResponse.intermediate) {
mRequest.addMarker("intermediate-response");
} else {
mRequest.finish("done");
}
// If we have been provided a post-delivery runnable, run it.
if (mRunnable != null) {
mRunnable.run();
}
}
run方法中的程式碼比較簡單,進行資料的分發,這裡面的mResponse就是我們返回的結果,通過mRequest.deliverResponse(mResponse.result);進行資料分發,這裡面的Request是一個請求的抽象類,進入StringRequest類中,我們發現StringRequest實現了這個抽象類:
public class StringRequest extends Request<String>{
}
在StringRequest 中我們找到這麼一段程式碼:
@Override
protected void deliverResponse(String response) {
mListener.onResponse(response);
}
對比之前我們定義的StringRequest的程式碼:
StringRequest stringRequest = new StringRequest(Request.Method.GET,
url, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
});
是不是很熟悉,最後結果就是通過ExecutorDelivery中handler對應執行緒進行分發處理的。
到這裡請求佇列(RequestQueue)就初始化完畢了。
接著就是定義我們的Request,Volley定義了現成的Request,像 StringRequest、JsonRequest、ImageReques,當然我們可以根據StringRequest一樣定義我們自己的Request,只需實現Request這個抽象類:
package com.android.volley.toolbox;
import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;
import java.io.UnsupportedEncodingException;
/**
* A canned request for retrieving the response body at a given URL as a String.
*/
public class StringRequest extends Request<String> {
private final Listener<String> mListener;
/**
* Creates a new request with the given method.
*
* @param method the request {@link Method} to use
* @param url URL to fetch the string at
* @param listener Listener to receive the String response
* @param errorListener Error listener, or null to ignore errors
*/
public StringRequest(int method, String url, Listener<String> listener,
ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
}
/**
* Creates a new GET request.
*
* @param url URL to fetch the string at
* @param listener Listener to receive the String response
* @param errorListener Error listener, or null to ignore errors
*/
public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
this(Method.GET, url, listener, errorListener);
}
@Override
protected void deliverResponse(String response) {
mListener.onResponse(response);
}
@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));
}
}
檢視StringRequest原始碼,重寫了以下兩個方法:
abstract protected void deliverResponse(T response);
abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
NetworkResponse存放的是從伺服器返回並以位元組形式的資料。按照StringRequest我們可以定義GsonRequest:
public class GsonRequest<T> extends Request<T> {
private final Listener<T> mListener;
private Gson mGson;
private Class<T> mClass;
public GsonRequest(int method, String url, Class<T> clazz, Listener<T> listener,
ErrorListener errorListener) {
super(method, url, errorListener);
mGson = new Gson();
mClass = clazz;
mListener = listener;
}
public GsonRequest(String url, Class<T> clazz, Listener<T> listener,
ErrorListener errorListener) {
this(Method.GET, url, clazz, listener, errorListener);
}
@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
String jsonString = new String(response.data,
HttpHeaderParser.parseCharset(response.headers));
return Response.success(mGson.fromJson(jsonString, mClass),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
}
}
@Override
protected void deliverResponse(T response) {
mListener.onResponse(response);
}
}
到這裡為止我們的佇列以及請求響應都已定義好,最後通過請求佇列的start方法進行請求:
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();
}
}
public void stop() {
if (mCacheDispatcher != null) {
mCacheDispatcher.quit();
}
for (int i = 0; i < mDispatchers.length; i++) {
if (mDispatchers[i] != null) {
mDispatchers[i].quit();
}
}
}
CacheDispatcher和NetworkDispatcher中的quit方法:
public void quit() {
mQuit = true;
interrupt();
}
通過start方法對佇列進行排程,方法中的先通過stop進行快取和網路請求的排程終止,通過它們的quit方法中的interrupt方法進行執行緒中斷,CacheDispatcher是 一個執行緒,用於排程處理走快取的請求。啟動後會不斷從快取請求佇列中取請求處理,佇列為空則等待,請求處理結束則將結果傳遞給ResponseDelivery去執行後續處理。當結果未快取過、快取失效或快取需要重新整理的情況下,該請求都需要重新進入NetworkDispatcher去排程處理。
由此start方法中的處理邏輯就一目瞭然了,先是經過快取的請求,在未快取過或是快取失效時,再進入網路的請求,並判斷結果是否要進行快取。