特點
適合數據量小的頻率高的請求,不適合下載上傳大文件
- Volley的網絡請求線程池默認大小為4。意味著可以并發(fā)進行4個請求霸饲,大于4個,會排在隊列中臂拓。
- Request#getBody() 方法返回byte[]類型厚脉,作為 Http.POST 和 Http.PUT Request body 中的數據。這就意味著需要把用 http 傳輸的數據一股腦讀取到內存中埃儿。如果文件過大器仗,有可能導致OOM。
考慮這樣一個場景:
你同時上傳4個文件童番,這四個文件都很大精钮,這時候你的內存占用就很高,很容易oom剃斧。
這時候轨香,你發(fā)網絡請求,調用普通api幼东。
所有的網絡線程都被上傳文件的任務占滿了臂容,你的網絡請求只有在文件上傳完畢后才能得到執(zhí)行科雳。體驗就是,很慢脓杉!
所以Volley適合數據量小糟秘,頻率快的請求。
用法
StringRequest
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final RequestQueue mQueue = Volley.newRequestQueue(this);
StringRequest stringRequest = new StringRequest("http://www.reibang.com",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.d("TAG", response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
mQueue.add(stringRequest);
}
}
源碼解析
說起分析源碼球散,那么應該從哪兒開始看起呢尿赚?這就要回顧一下Volley的用法了,還記得嗎蕉堰,使用Volley的第一步凌净,首先要調用Volley.newRequestQueue(context)方法來獲取一個RequestQueue對象,那么我們自然要從這個方法開始看起了屋讶,代碼如下所示:
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null);
}
這個方法僅僅只有一行代碼冰寻,只是調用了newRequestQueue()的方法重載,并給第二個參數傳入null皿渗。那我們看下帶有兩個參數的newRequestQueue()方法中的代碼斩芭,如下所示:
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 {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
Network network = new BasicNetwork(stack);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
可以看到,這里在①判斷如果stack是等于null的羹奉,則去創(chuàng)建一個HttpStack對象秒旋,這里會判斷如果手機系統(tǒng)版本號是大于9的约计,則創(chuàng)建一個HurlStack的實例诀拭,否則就創(chuàng)建一個HttpClientStack的實例。實際上HurlStack的內部就是使用HttpURLConnection進行網絡通訊的煤蚌,而HttpClientStack的內部則是使用HttpClient進行網絡通訊的耕挨,這里為什么這樣選擇呢?可以參考我之前翻譯的一篇文章Android訪問網絡尉桩,使用HttpURLConnection還是HttpClient筒占?
創(chuàng)建好了HttpStack之后,接下來又創(chuàng)建了一個Network對象蜘犁,它是用于根據傳入的HttpStack對象來處理網絡請求的翰苫,緊接著new出一個RequestQueue對象,并調用它的start()方法進行啟動这橙,然后將RequestQueue返回奏窑,這樣newRequestQueue()的方法就執(zhí)行結束了。
那么RequestQueue的start()方法內部到底執(zhí)行了什么東西呢屈扎?我們跟進去瞧一瞧:
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();
}
}
這里先是創(chuàng)建了一個CacheDispatcher的實例埃唯,然后調用了它的start()方法,接著在一個for循環(huán)里去創(chuàng)建NetworkDispatcher的實例鹰晨,并分別調用它們的start()方法墨叛。這里的CacheDispatcher和NetworkDispatcher都是繼承自Thread的止毕,而默認情況下for循環(huán)會執(zhí)行四次,也就是說當調用了Volley.newRequestQueue(context)之后漠趁,就會有五個線程一直在后臺運行扁凛,不斷等待網絡請求的到來,其中CacheDispatcher是緩存線程闯传,NetworkDispatcher是網絡請求線程令漂。
得到了RequestQueue之后,我們只需要構建出相應的Request丸边,然后調用RequestQueue的add()方法將Request傳入就可以完成網絡請求操作了叠必,那么不用說,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");
// If the request is uncacheable, skip the cache queue and go straight to the network.
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();
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);
if (VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
}
} 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的setShouldCache(false)方法來改變這一默認行為隅茎。
OK,那么既然默認每條請求都是可以緩存的嫉沽,自然就被添加到了緩存隊列中辟犀,于是一直在后臺等待的緩存線程就要開始運行起來了,我們看下CacheDispatcher中的run()方法绸硕,代碼如下所示:
public class CacheDispatcher extends Thread {
……
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// Make a blocking call to initialize the cache.
mCache.initialize();
while (true) { // ⑤
try {
// Get a request from the cache triage queue, blocking until
// at least one is available.
final Request<?> request = mCacheQueue.take();
request.addMarker("cache-queue-take");
// If the request has been canceled, don't bother dispatching it.
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// Attempt to retrieve this item from cache.
Cache.Entry entry = mCache.get(request.getCacheKey()); // ⑥
if (entry == null) {
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
mNetworkQueue.put(request);
continue;
}
// If it is completely expired, just send it to the network.
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()) {
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
} else {
// Soft-expired cache hit. We can deliver the cached response,
// but we need to also send the request to the network for
// refreshing.
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// Mark the response as intermediate.
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;
}
}
}
}
代碼有點長堂竟,我們只挑重點看。首先在⑤可以看到一個while(true)循環(huán)玻佩,說明緩存線程始終是在運行的出嘹,接著在⑥會嘗試從緩存當中取出響應結果,如何為空的話則把這條請求加入到網絡請求隊列中咬崔,如果不為空的話再判斷該緩存是否已過期税稼,如果已經過期了則同樣把這條請求加入到網絡請求隊列中,否則就認為不需要重發(fā)網絡請求垮斯,直接使用緩存中的數據即可郎仆。之后會在⑦調用Request的parseNetworkResponse()方法來對數據進行解析,再往后就是將解析出來的數據進行回調了甚脉,這部分代碼我們先跳過丸升,因為它的邏輯和NetworkDispatcher后半部分的邏輯是基本相同的,那么我們等下合并在一起看就好了牺氨,先來看一下NetworkDispatcher中是怎么處理網絡請求隊列的狡耻,代碼如下所示:
public class NetworkDispatcher extends Thread {
……
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Request<?> request;
while (true) { // ⑧
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);
// Perform the network request.
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;
}
// Parse the response here on the worker thread.
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) {
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
mDelivery.postError(request, new VolleyError(e));
}
}
}
}
同樣地墩剖,在⑧我們看到了類似的while(true)循環(huán),說明網絡請求線程也是在不斷運行的夷狰。在⑨會調用Network的performRequest()方法來去發(fā)送網絡請求岭皂,而Network是一個接口,這里具體的實現(xiàn)是BasicNetwork沼头,我們來看下它的performRequest()方法爷绘,如下所示:
public class BasicNetwork implements Network {
……
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();
while (true) { // ⑤
HttpResponse httpResponse = null;
byte[] responseContents = null;
Map<String, String> responseHeaders = new HashMap<String, String>();
try {
// Gather headers.
Map<String, String> headers = new HashMap<String, String>();
addCacheHeaders(headers, request.getCacheEntry());
httpResponse = mHttpStack.performRequest(request, headers); //⑩
StatusLine statusLine = httpResponse.getStatusLine();
int statusCode = statusLine.getStatusCode();
responseHeaders = convertHeaders(httpResponse.getAllHeaders());
// Handle cache validation.
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
request.getCacheEntry() == null ? null : request.getCacheEntry().data,
responseHeaders, true);
}
// Some responses such as 204s do not have content. We must check.
if (httpResponse.getEntity() != null) {
responseContents = entityToBytes(httpResponse.getEntity());
} else {
// Add 0 byte response as a way of honestly representing a
// no-content request.
responseContents = new byte[0];
}
// if the request is slow, log it.
long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
logSlowRequests(requestLifetime, request, responseContents, statusLine);
if (statusCode < 200 || statusCode > 299) {
throw new IOException();
}
return new NetworkResponse(statusCode, responseContents, responseHeaders, false);
} catch (Exception e) {
……
}
}
}
}
這段方法中大多都是一些網絡請求細節(jié)方面的東西,我們并不需要太多關心进倍,需要注意的是在⑩調用了HttpStack的performRequest()方法土至,這里的HttpStack就是在一開始調用newRequestQueue()方法是創(chuàng)建的實例,默認情況下如果系統(tǒng)版本號大于9就創(chuàng)建的HurlStack對象猾昆,否則創(chuàng)建HttpClientStack對象陶因。前面已經說過,這兩個對象的內部實際就是分別使用HttpURLConnection和HttpClient來發(fā)送網絡請求的垂蜗,我們就不再跟進去閱讀了楷扬,之后會將服務器返回的數據組裝成一個NetworkResponse對象進行返回。
在NetworkDispatcher中收到了NetworkResponse這個返回值后又會調用Request的parseNetworkResponse()方法來解析NetworkResponse中的數據贴见,以及將數據寫入到緩存烘苹,這個方法的實現(xiàn)是交給Request的子類來完成的,因為不同種類的Request解析的方式也肯定不同片部。還記得我們在上一篇文章中學習的自定義Request的方式嗎镣衡?其中parseNetworkResponse()這個方法就是必須要重寫的。
在解析完了NetworkResponse中的數據之后吞琐,又會調用ExecutorDelivery的postResponse()方法來回調解析出的數據捆探,代碼如下所示:
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
其中然爆,在mResponsePoster的execute()方法中傳入了一個ResponseDeliveryRunnable對象站粟,就可以保證該對象中的run()方法就是在主線程當中運行的了,我們看下run()方法中的代碼是什么樣的:
private class ResponseDeliveryRunnable implements Runnable {
private final Request mRequest;
private final Response mResponse;
private final Runnable mRunnable;
public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
mRequest = request;
mResponse = response;
mRunnable = runnable;
}
@SuppressWarnings("unchecked")
@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();
}
}
}
代碼雖然不多曾雕,但我們并不需要行行閱讀奴烙,抓住重點看即可。其中在?調用了Request的deliverResponse()方法剖张,有沒有感覺很熟悉切诀?沒錯,這個就是我們在自定義Request時需要重寫的另外一個方法搔弄,每一條網絡請求的響應都是回調到這個方法中幅虑,最后我們再在這個方法中將響應的數據回調到Response.Listener的onResponse()方法中就可以了。
總結
其中藍色部分代表主線程顾犹,綠色部分代表緩存線程倒庵,橙色部分代表網絡線程褒墨。我們在主線程中調用RequestQueue的add()方法來添加一條網絡請求,這條請求會先被加入到緩存隊列當中擎宝,如果發(fā)現(xiàn)可以找到相應的緩存結果就直接讀取緩存并解析郁妈,然后回調給主線程。如果在緩存中沒有找到結果绍申,則將這條請求加入到網絡請求隊列中噩咪,然后處理發(fā)送HTTP請求,解析響應結果极阅,寫入緩存胃碾,并回調主線程。