網(wǎng)絡庫的介紹
1.HttpURLConnection
API簡單,體積較小,因而非常適用于Android項目,但是在android 2.2及以下版本中HttpUrlConnection存在著一些bug,所以建議在android 2.3以后使用HttpUrlConnection,在這之前使用的是HttpClient。
2.HttpClient (Apache )
高效穩(wěn)定,但是維護成本高昂,故android 開發(fā)團隊不愿意維護該庫更青睞輕便的HttpUrlConnection。Android 5.0后已廢棄該庫。
3.OKHttp
Square公司產(chǎn)品,OkHttp相比HttpURLConnection和HttpClient功能更加強大。
4.Volley
Volley是在2013年Google I/O大會上推出了一個新的網(wǎng)絡通信框架,內部封裝了HttpURLConnection和HttpClient, 解決了網(wǎng)絡數(shù)據(jù)解析和線程切換的問題。
主要用于解決通訊頻率高,但傳輸數(shù)據(jù)量小的情景而對于大數(shù)據(jù)量的網(wǎng)絡操作,比如說下載文件等,Volley的表現(xiàn)就會非常糟糕。
其實Volley的使用是很簡單的,總的來說就是發(fā)送一個http的請求,將請求加入到RequestQueue(請求隊列)中,這里的RequestQueue是一個請求隊列對象,它可以緩存所有的HTTP請求,然后按照一定的算法并發(fā)地發(fā)出這些請求。RequestQueue內部的設計就是非常合適高并發(fā)的,因此我們不必為每一次HTTP請求都創(chuàng)建一個RequestQueue對象,這是非常浪費資源的,基本上在每一個需要和網(wǎng)絡交互的Activity中創(chuàng)建一個RequestQueue對象就足夠了。
總的來說我們常用的Volley就下面三個步驟:Volley.newRequestQueue(context).add(request);
1. 創(chuàng)建一個RequestQueue對象。
2. 創(chuàng)建一個StringRequest對象。
3. 將StringRequest對象添加到RequestQueue里面。
下面我們主要通過這三句話來分析一下Volley源碼中的實現(xiàn)原理

這是官方的關于Volley工作流程圖 , 其中藍色部分代表主線程,綠色部分代表緩存線程,橙色部分代表網(wǎng)絡線程。我們在主線程中調用RequestQueue的add()方法來添加一條網(wǎng)絡請求,這條請求會先被加入到緩存隊列當中,如果發(fā)現(xiàn)可以找到相應的緩存結果就直接讀取緩存并解析,然后回調給主線程。如果在緩存中沒有找到結果,則將這條請求加入到網(wǎng)絡請求隊列中,然后處理發(fā)送HTTP請求,解析響應結果,寫入緩存,并回調主線程。
第一步:newRequestQueue(context)
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null); //執(zhí)行帶兩個參數(shù)的構造方法
}
這個方法僅僅只有一行代碼,只是調用了newRequestQueue()的方法重載,并給第二個參數(shù)傳入null。那我們看下帶有兩個參數(shù)的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) { //判斷如果stack是等于null的,則去創(chuàng)建一個HttpStack對象
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;
}
在上面的代碼中可以看出,如果stack是等于null的,則去創(chuàng)建一個HttpStack對象,這里會判斷如果手機系統(tǒng)版本號是大于9(這里指SDK版本)的,則創(chuàng)建一個HurlStack的實例,否則就創(chuàng)建一個HttpClientStack的實例。實際上HurlStack的內部就是使用HttpURLConnection進行網(wǎng)絡通訊的,而HttpClientStack的內部則是使用HttpClient進行網(wǎng)絡通訊的,至于為什么說需要大于或等于9,是因為SDK為9時對應的系統(tǒng)為android2.3,版本2.3以后推薦使用HttpURLConnection.
創(chuàng)建好了HttpStack之后,接下來又創(chuàng)建了一個Network對象,它是用于根據(jù)傳入的HttpStack對象來處理網(wǎng)絡請求的,緊接著new出一個RequestQueue對象,并調用它的start()方法進行啟動,然后將RequestQueue返回,這樣newRequestQueue()的方法就執(zhí)行結束了。接下啦我們看看RequestQueue的start()方法內部執(zhí)行內容:
/** Number of network request dispatcher threads to start. */
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;//默認網(wǎng)絡請求線程數(shù)量
/**
* Starts the dispatchers in this queue.
*/
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)之后,就會有五個線程一直在后臺運行,不斷等待網(wǎng)絡請求的到來,其中CacheDispatcher是緩存線程,NetworkDispatcher是網(wǎng)絡請求線程。
上面得到了RequestQueue之后,我們只需要構建出相應的Request,然后調用RequestQueue的add()方法將Request傳入就可以完成網(wǎng)絡請求操作了,下面我們分析一下add()方法的內部的邏輯
/**
* Adds a Request to the dispatch queue.
* @param request The request to service
* @return The passed-in request
*/
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); //如果不能緩存則直接將這條請求加入網(wǎng)絡請求隊列
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;
}
}
可以看到,開始的時候會判斷當前的請求是否可以緩存,如果不能緩存則直接將這條請求加入網(wǎng)絡請求隊列,可以緩存的話則將這條請求加入緩存隊列。在默認情況下,每條請求都是可以緩存的,當然我們也可以調用Request的setShouldCache(false)方法來改變這一默認行為。
既然默認每條請求都是可以緩存的,自然就被添加到了緩存隊列中,于是一直在后臺等待的緩存線程就要開始運行起來了,我們看下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) { //死循環(huán)
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) { //如何為空的話則把這條請求加入到網(wǎng)絡請求隊列中
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()) { //如果不為空的但該緩存已過期,則同樣把這條請求加入到網(wǎng)絡請求隊列中
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)); //對數(shù)據(jù)進行解析
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),說明緩存線程始終是在運行的,接著會嘗試從緩存當中取出響應結果,如何為空的話則把這條請求加入到網(wǎng)絡請求隊列中,如果不為空的話再判斷該緩存是否已過期,如果已經(jīng)過期了則同樣把這條請求加入到網(wǎng)絡請求隊列中,否則就認為不需要重發(fā)網(wǎng)絡請求,直接使用緩存中的數(shù)據(jù)即可。之后會調用Request的parseNetworkResponse()方法來對數(shù)據(jù)進行解析,再往后就是將解析出來的數(shù)據(jù)進行回調了,這部分代碼的邏輯和NetworkDispatcher后半部分的邏輯是基本相同的,那么我們等下合并在一起看就好了,先來看一下NetworkDispatcher中是怎么處理網(wǎng)絡請求隊列的,代碼如下所示:
public class NetworkDispatcher extends Thread {
.......//省略代碼
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) { //死循環(huán)
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);
// Perform the network request.
NetworkResponse networkResponse = mNetwork.performRequest(request); //執(zhí)行網(wǎng)絡請求
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); //解析響應的網(wǎng)絡數(shù)據(jù)
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);
}
}
}
private void parseAndDeliverNetworkError(Request<?> request, VolleyError error) {
error = request.parseNetworkError(error);
mDelivery.postError(request, error);
}
}
同樣地,我們看到了類似的while(true)循環(huán),說明網(wǎng)絡請求線程也是在不斷運行的。在死循環(huán)中會調用Network的performRequest()方法來去發(fā)送網(wǎng)絡請求,而Network是一個接口,我們在上面創(chuàng)建 stack是通過這段代碼(Network network = new BasicNetwork(stack);)實現(xiàn)網(wǎng)絡請求的,所以這里具體的實現(xiàn)是BasicNetwork,所以我們在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 = Collections.emptyMap();
try {
// Gather headers.
Map<String, String> headers = new HashMap<String, String>();
addCacheHeaders(headers, request.getCacheEntry());
httpResponse = mHttpStack.performRequest(request, headers); //調用了HttpStack的performRequest()方法
StatusLine statusLine = httpResponse.getStatusLine();
int statusCode = statusLine.getStatusCode();
responseHeaders = convertHeaders(httpResponse.getAllHeaders());
// Handle cache validation.
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
Entry entry = request.getCacheEntry();
if (entry == null) {
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
responseHeaders, true,
SystemClock.elapsedRealtime() - requestStart); //組裝成一個NetworkResponse對象進行返回
}
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
entry.responseHeaders.putAll(responseHeaders);
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
entry.responseHeaders, true,
SystemClock.elapsedRealtime() - requestStart); //組裝成一個NetworkResponse對象進行返回
}
// 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,
SystemClock.elapsedRealtime() - requestStart);
} catch (SocketTimeoutException e) {
...... //省略代碼
}
}
}
}
這段代碼主要是一些網(wǎng)絡請求細節(jié)方面的東西,需要注意的是調用了HttpStack的performRequest()方法,這里的HttpStack就是在一開始調用newRequestQueue()方法是創(chuàng)建的實例,默認情況下如果系統(tǒng)版本號大于9就創(chuàng)建的HurlStack對象,否則創(chuàng)建HttpClientStack對象。這兩個對象的內部實際就是分別使用HttpURLConnection和HttpClient來發(fā)送網(wǎng)絡請求的,之后會將服務器返回的數(shù)據(jù)組裝成一個NetworkResponse對象進行返回。
在NetworkDispatcher中收到了NetworkResponse這個返回值后又會調用Request的parseNetworkResponse()方法來解析NetworkResponse中的數(shù)據(jù),以及將數(shù)據(jù)寫入到緩存,這個方法的實現(xiàn)是交給Request的子類來完成的,因為不同種類的Request解析的方式也肯定不同。如果想自定義Request的方式,其中parseNetworkResponse()這個方法就是必須要重寫的。
在解析完了NetworkResponse中的數(shù)據(jù)之后,又會調用ExecutorDelivery的postResponse()方法來回調解析出的數(shù)據(jù),代碼如下所示:
1.在NetworkDispatcher類中
private final ResponseDelivery mDelivery;
// Parse the response here on the worker thread.
Response<?> response = request.parseNetworkResponse(networkResponse); //解析數(shù)據(jù)
request.addMarker("network-parse-complete");
mDelivery.postResponse(request, response); //回調解析出的數(shù)據(jù),具體實現(xiàn)在下面代碼中
2.在ExecutorDelivery類中回調解析出來的數(shù)據(jù)
public class ExecutorDelivery implements ResponseDelivery {
@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));
}
}
其中,在mResponsePoster的execute()方法中傳入了一個ResponseDeliveryRunnable對象,這樣就可以保證該對象中的run()方法就是在主線程當中運行的了,我們看下run()方法中的代碼是什么樣的:
private class ResponseDeliveryRunnable implements 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時需要重寫的另外一個方法,每一條網(wǎng)絡請求的響應都是回調到這個方法中,最后我們再在這個方法中將響應的數(shù)據(jù)回調到Response.Listener的onResponse()方法中就可以了。
5.Retrofit.
Square公司產(chǎn)品,內部封裝了OKhttp, 解決了網(wǎng)絡數(shù)據(jù)解析和線程切換的問題。