前言
在現(xiàn)在的Android開發(fā)之中,已經(jīng)比較少人使用volley進(jìn)行網(wǎng)絡(luò)請求了,之所以現(xiàn)在還寫這篇關(guān)于Volley的文章,是因?yàn)関olley是一個(gè)優(yōu)秀的框架,其設(shè)計(jì)嚴(yán)格遵循了面向?qū)ο蟮脑O(shè)計(jì)原則,學(xué)習(xí)volley的設(shè)計(jì)原則,對自己的項(xiàng)目開發(fā)有比較好的提示作用。
使用方式
-
導(dǎo)入
在AndroidStudio里面,只需要在Projrct structure里面添加依賴,在搜索框里輸入“volley”,直接搜索v,然后點(diǎn)擊添加即可。
添加volley - 使用
使用相對來說比較簡單,首先需要?jiǎng)?chuàng)建一個(gè)RequestQueue,然后添加Request即可
RequestQueue queue = Volley.newRequestQueue(this);
queue.add(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) {
}
}));
對于添加的Request,官方默認(rèn)實(shí)現(xiàn)的有以下幾種請求方式,當(dāng)然可以自定制Request,后續(xù)會(huì)講到。
原理解析
在詳細(xì)解釋每個(gè)步驟的原理之前,先看一下volley的整個(gè)UML圖
如圖,紅框部分是整個(gè)Volley主要部分,可以看到,最中間的RequestQueue是把所有功能組合起來的類,而整個(gè)Volley設(shè)計(jì)是遵守了依賴倒轉(zhuǎn)原則,即針對接口編程,而不是針對實(shí)現(xiàn)編程,由此對于功能的拓展將很容易實(shí)現(xiàn)。接下來講述整個(gè)Volley的運(yùn)作過程。
- 創(chuàng)建
在詳細(xì)解釋之前,先看一下創(chuàng)建的整體流程,當(dāng)熟悉整個(gè)流程之后,對源碼的理解會(huì)容易很多。
Volley.newRequestQueue流程
對于Volley而言,創(chuàng)建是由Volley.newRequestQueue()
開始的,返回一個(gè)RequestQueue實(shí)例,該靜態(tài)方法有兩個(gè)重載,如下
RequestQueue newRequestQueue(Context context);
RequestQueue newRequestQueue(Context context, HttpStack stack);
當(dāng)使用第一個(gè)重載方法時(shí),其實(shí)也是調(diào)用到第二個(gè)方法。
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null);
}
第二個(gè)參數(shù)HttpStack,是用來進(jìn)行網(wǎng)路請求的,由Volley的整體框架圖,可以看出其有兩個(gè)實(shí)現(xiàn)子類,選擇哪個(gè)子類是有SDK的版本決定的,源碼如下:
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
//放置緩存的地方
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
String userAgent = "volley/0";
if (stack == null) {
//如果版本號大于9(V2.3)
if (Build.VERSION.SDK_INT >= 9) {
//創(chuàng)建基于HttpURLConnection的HttpStack
stack = new HurlStack();
} else {
//創(chuàng)建基于HttpClient的HttpStack
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
//創(chuàng)建網(wǎng)絡(luò)請求和queue
Network network = new BasicNetwork(stack);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
由代碼可以看出,完全可以自定義實(shí)現(xiàn)HttpStack,這樣就可以和其它的網(wǎng)絡(luò)請求框架結(jié)合起來或者是自定義的網(wǎng)絡(luò)請求結(jié)合起來了。
對于RequestQueue的構(gòu)造方法,最終都會(huì)調(diào)用到一下方法
/**
* Creates the worker pool. Processing will not begin until {@link #start()} is called.
*
* @param cache A Cache to use for persisting responses to disk
* @param network A Network interface for performing HTTP requests
* @param threadPoolSize Number of network dispatcher threads to create
* @param delivery A ResponseDelivery interface for posting responses and errors
*/
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
可以看第三個(gè)參數(shù),網(wǎng)絡(luò)請求的線程數(shù),默認(rèn)是4,當(dāng)然也可以自己完全定制一個(gè)自己想要的線程數(shù)。
RequestQueue#start方法,主要是創(chuàng)建緩存分發(fā)線程和網(wǎng)絡(luò)訪問分發(fā)線程,一個(gè)queue只有一條緩存線程,有threadPoolSize數(shù)量的網(wǎng)絡(luò)訪問線程,默認(rèn)是4,因此不適用于數(shù)據(jù)量大、通訊頻繁的網(wǎng)絡(luò)操作,因?yàn)闀?huì)占用網(wǎng)絡(luò)請求的訪問線程。
public void start() {
stop(); // 確定當(dāng)前線程已經(jīng)停下來了
// 只創(chuàng)建一條緩存分發(fā)線程并且啟動(dòng),注入mNetWorkQueue,用于緩存獲取失
// 敗時(shí)進(jìn)行網(wǎng)絡(luò)請求
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
//創(chuàng)建多條網(wǎng)絡(luò)請求線程并啟動(dòng),在創(chuàng)建時(shí)注入mCache,用于緩存
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
-
添加Request
對于請求的添加,整體流程圖如下所示:
添加請求
RequestQueue.add()方法源碼如下:
public <T> Request<T> add(Request<T> request) {
// Tag the request as belonging to this queue and add it to the set of current requests.
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// Process requests in the order they are added.
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// 判斷是否可緩存,如果不可緩存,直接添加到網(wǎng)絡(luò)請求隊(duì)列
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
// Insert request into stage if there's already a request with the same cache key in flight.
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
//如果已經(jīng)存在當(dāng)前Request,那么只需要在等待隊(duì)列里面插入當(dāng)前請求即可,防止多次網(wǎng)絡(luò)訪問
if (mWaitingRequests.containsKey(cacheKey)) {
// There is already a request in flight. Queue up.
Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList<Request<?>>();
}
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
} else {
// Insert 'null' queue for this cacheKey, indicating there is now a request in
// flight.
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}
-
對Request進(jìn)行處理
在上部分代碼中,可以看到add()方法只是簡單的把請求插入到了網(wǎng)絡(luò)請求對列或者緩存請求對列,按照插入請求之后就會(huì)進(jìn)行網(wǎng)絡(luò)請求,可以猜測這兩個(gè)線程都是在不斷的進(jìn)行著輪詢,先來看一下CacheDispatcher的處理流程
流程圖
CacheDispatcher處理流程
CacheDispatcher的run()源碼如下
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// Make a blocking call to initialize the cache.
mCache.initialize();
while (true) {
try {
// 阻塞獲取一個(gè)Request,queue原型為BlockingQueue
final Request<?> request = mCacheQueue.take();
request.addMarker("cache-queue-take");
// 如果請求已經(jīng)取消,則跳過該請求
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// 嘗試從緩存里面獲取數(shù)據(jù)
Cache.Entry entry = mCache.get(request.getCacheKey());
//如果為空,表示沒有緩存,添加請求到網(wǎng)絡(luò)隊(duì)列
if (entry == null) {
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
mNetworkQueue.put(request);
continue;
}
// 如果緩存過期了,添加到網(wǎng)絡(luò)請求對列
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// We have a cache hit; parse its data for delivery back to the request.
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
if (!entry.refreshNeeded()) {
// 不需要刷新緩存,直接進(jìn)行結(jié)果傳遞
mDelivery.postResponse(request, response);
} else {
// 需要刷新的緩存,在把緩存結(jié)果傳遞時(shí),同時(shí)應(yīng)該進(jìn)行緩存的刷新
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
response.intermediate = true;
// Post the intermediate response back to the user and have
// the delivery then forward the request along to the network.
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Not much we can do about this.
}
}
});
}
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
}
}
可以看出run()是一直在循環(huán)中的,并且阻塞獲取Request,當(dāng)獲取到Request后分情況處理
對于NetWorkDIspatcher,主要是進(jìn)行網(wǎng)絡(luò)請求以及對請求結(jié)果的緩存,處理流程圖如下所示
run()的源代碼如下
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
Request<?> request;
try {
// Take a request from the queue.
request = mQueue.take();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
try {
request.addMarker("network-queue-take");
// If the request was cancelled already, do not perform the
// network request.
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
addTrafficStatsTag(request);
// 此處進(jìn)行網(wǎng)絡(luò)請求,由mNetWork處理
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// If the server returned 304 AND we delivered a response already,
// we're done -- don't deliver a second identical response.
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
// 對response解析,由Request解析
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// Write to cache if applicable.
// TODO: Only update cache metadata instead of entire record for 304s.
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
// Post the response back.
request.markDelivered();
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
VolleyError volleyError = new VolleyError(e);
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
mDelivery.postError(request, volleyError);
}
}
}
可以看出請求是通過創(chuàng)建RequestQuesu傳入的Network進(jìn)行處理的, 然后對請求返回的結(jié)果,是通過我們的Request.parseNetworkResponse(NetworkResponse response)
處理的,也就是說,如果是StringRequest,那么這個(gè)方法就是把請求結(jié)果轉(zhuǎn)換為String,所以我們自定義Request的時(shí)候,需要實(shí)現(xiàn)這個(gè)方法。
總結(jié)
這篇文章就寫到這里,雖然不一定會(huì)使用Volley來進(jìn)行網(wǎng)絡(luò)請求了(效率比較低),但是了解一下這個(gè)優(yōu)秀的框架,個(gè)人覺得還是很有必要的。現(xiàn)在進(jìn)行網(wǎng)絡(luò)請求,推薦使用RxJava + Retrofit的方式,其中Retrofit的網(wǎng)絡(luò)請求是通過okHttp實(shí)現(xiàn)的,在這里就不細(xì)說了。