網(wǎng)絡(luò)庫(kù)的介紹
1.HttpURLConnection
API簡(jiǎn)單,體積較小,因而非常適用于Android項(xiàng)目,但是在android 2.2及以下版本中HttpUrlConnection存在著一些bug,所以建議在android 2.3以后使用HttpUrlConnection桦锄,在這之前使用的是HttpClient。
2.HttpClient (Apache )
高效穩(wěn)定蔫耽,但是維護(hù)成本高昂结耀,故android 開發(fā)團(tuán)隊(duì)不愿意維護(hù)該庫(kù)更青睞輕便的HttpUrlConnection。Android 5.0后已廢棄該庫(kù)匙铡。
3.OKHttp
Square公司產(chǎn)品图甜,OkHttp相比HttpURLConnection和HttpClient功能更加強(qiáng)大。
4.Volley
Volley是在2013年Google I/O大會(huì)上推出了一個(gè)新的網(wǎng)絡(luò)通信框架鳖眼,內(nèi)部封裝了HttpURLConnection和HttpClient, 解決了網(wǎng)絡(luò)數(shù)據(jù)解析和線程切換的問題黑毅。
主要用于解決通訊頻率高,但傳輸數(shù)據(jù)量小的情景而對(duì)于大數(shù)據(jù)量的網(wǎng)絡(luò)操作,比如說(shuō)下載文件等钦讳,Volley的表現(xiàn)就會(huì)非常糟糕矿瘦。
其實(shí)Volley的使用是很簡(jiǎn)單的,總的來(lái)說(shuō)就是發(fā)送一個(gè)http的請(qǐng)求,將請(qǐng)求加入到RequestQueue(請(qǐng)求隊(duì)列)中,這里的RequestQueue是一個(gè)請(qǐng)求隊(duì)列對(duì)象,它可以緩存所有的HTTP請(qǐng)求愿卒,然后按照一定的算法并發(fā)地發(fā)出這些請(qǐng)求缚去。RequestQueue內(nèi)部的設(shè)計(jì)就是非常合適高并發(fā)的,因此我們不必為每一次HTTP請(qǐng)求都創(chuàng)建一個(gè)RequestQueue對(duì)象琼开,這是非常浪費(fèi)資源的易结,基本上在每一個(gè)需要和網(wǎng)絡(luò)交互的Activity中創(chuàng)建一個(gè)RequestQueue對(duì)象就足夠了。
總的來(lái)說(shuō)我們常用的Volley就下面三個(gè)步驟:Volley.newRequestQueue(context).add(request);
1. 創(chuàng)建一個(gè)RequestQueue對(duì)象柜候。
2. 創(chuàng)建一個(gè)StringRequest對(duì)象衬衬。
3. 將StringRequest對(duì)象添加到RequestQueue里面。
下面我們主要通過這三句話來(lái)分析一下Volley源碼中的實(shí)現(xiàn)原理
這是官方的關(guān)于Volley工作流程圖 , 其中藍(lán)色部分代表主線程改橘,綠色部分代表緩存線程,橙色部分代表網(wǎng)絡(luò)線程玉控。我們?cè)谥骶€程中調(diào)用RequestQueue的add()方法來(lái)添加一條網(wǎng)絡(luò)請(qǐng)求飞主,這條請(qǐng)求會(huì)先被加入到緩存隊(duì)列當(dāng)中,如果發(fā)現(xiàn)可以找到相應(yīng)的緩存結(jié)果就直接讀取緩存并解析高诺,然后回調(diào)給主線程碌识。如果在緩存中沒有找到結(jié)果,則將這條請(qǐng)求加入到網(wǎng)絡(luò)請(qǐng)求隊(duì)列中虱而,然后處理發(fā)送HTTP請(qǐng)求筏餐,解析響應(yīng)結(jié)果,寫入緩存牡拇,并回調(diào)主線程魁瞪。
第一步:newRequestQueue(context)
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null); //執(zhí)行帶兩個(gè)參數(shù)的構(gòu)造方法
}
這個(gè)方法僅僅只有一行代碼穆律,只是調(diào)用了newRequestQueue()的方法重載,并給第二個(gè)參數(shù)傳入null导俘。那我們看下帶有兩個(gè)參數(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)建一個(gè)HttpStack對(duì)象
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)建一個(gè)HttpStack對(duì)象辅髓,這里會(huì)判斷如果手機(jī)系統(tǒng)版本號(hào)是大于9(這里指SDK版本)的,則創(chuàng)建一個(gè)HurlStack的實(shí)例少梁,否則就創(chuàng)建一個(gè)HttpClientStack的實(shí)例洛口。實(shí)際上HurlStack的內(nèi)部就是使用HttpURLConnection進(jìn)行網(wǎng)絡(luò)通訊的,而HttpClientStack的內(nèi)部則是使用HttpClient進(jìn)行網(wǎng)絡(luò)通訊的,至于為什么說(shuō)需要大于或等于9,是因?yàn)镾DK為9時(shí)對(duì)應(yīng)的系統(tǒng)為android2.3,版本2.3以后推薦使用HttpURLConnection.
創(chuàng)建好了HttpStack之后凯沪,接下來(lái)又創(chuàng)建了一個(gè)Network對(duì)象第焰,它是用于根據(jù)傳入的HttpStack對(duì)象來(lái)處理網(wǎng)絡(luò)請(qǐng)求的,緊接著new出一個(gè)RequestQueue對(duì)象著洼,并調(diào)用它的start()方法進(jìn)行啟動(dòng)樟遣,然后將RequestQueue返回,這樣newRequestQueue()的方法就執(zhí)行結(jié)束了身笤。接下啦我們看看RequestQueue的start()方法內(nèi)部執(zhí)行內(nèi)容:
/** Number of network request dispatcher threads to start. */
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;//默認(rèn)網(wǎng)絡(luò)請(qǐ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)建了一個(gè)CacheDispatcher的實(shí)例豹悬,然后調(diào)用了它的start()方法,接著在一個(gè)for循環(huán)里去創(chuàng)建NetworkDispatcher的實(shí)例液荸,并分別調(diào)用它們的start()方法瞻佛。這里的CacheDispatcher和NetworkDispatcher都是繼承自Thread的,而默認(rèn)情況下for循環(huán)會(huì)執(zhí)行四次娇钱,也就是說(shuō)當(dāng)調(diào)用了Volley.newRequestQueue(context)之后伤柄,就會(huì)有五個(gè)線程一直在后臺(tái)運(yùn)行,不斷等待網(wǎng)絡(luò)請(qǐng)求的到來(lái)文搂,其中CacheDispatcher是緩存線程适刀,NetworkDispatcher是網(wǎng)絡(luò)請(qǐng)求線程。
上面得到了RequestQueue之后煤蹭,我們只需要構(gòu)建出相應(yīng)的Request笔喉,然后調(diào)用RequestQueue的add()方法將Request傳入就可以完成網(wǎng)絡(luò)請(qǐng)求操作了,下面我們分析一下add()方法的內(nèi)部的邏輯
/**
* 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()) { //判斷當(dāng)前的請(qǐng)求是否可以緩存
mNetworkQueue.add(request); //如果不能緩存則直接將這條請(qǐng)求加入網(wǎng)絡(luò)請(qǐng)求隊(duì)列
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); //可以緩存的話則在將這條請(qǐng)求加入緩存隊(duì)列
}
return request;
}
}
可以看到硝皂,開始的時(shí)候會(huì)判斷當(dāng)前的請(qǐng)求是否可以緩存常挚,如果不能緩存則直接將這條請(qǐng)求加入網(wǎng)絡(luò)請(qǐng)求隊(duì)列,可以緩存的話則將這條請(qǐng)求加入緩存隊(duì)列稽物。在默認(rèn)情況下奄毡,每條請(qǐng)求都是可以緩存的,當(dāng)然我們也可以調(diào)用Request的setShouldCache(false)方法來(lái)改變這一默認(rèn)行為贝或。
既然默認(rèn)每條請(qǐng)求都是可以緩存的吼过,自然就被添加到了緩存隊(duì)列中锐秦,于是一直在后臺(tái)等待的緩存線程就要開始運(yùn)行起來(lái)了,我們看下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()); //從緩存當(dāng)中取出響應(yīng)結(jié)果
if (entry == null) { //如何為空的話則把這條請(qǐng)求加入到網(wǎng)絡(luò)請(qǐng)求隊(duì)列中
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()) { //如果不為空的但該緩存已過期农猬,則同樣把這條請(qǐng)求加入到網(wǎng)絡(luò)請(qǐng)求隊(duì)列中
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)); //對(duì)數(shù)據(jù)進(jìn)行解析
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;
}
}
}
}
可以看到一個(gè)while(true)循環(huán),說(shuō)明緩存線程始終是在運(yùn)行的售淡,接著會(huì)嘗試從緩存當(dāng)中取出響應(yīng)結(jié)果斤葱,如何為空的話則把這條請(qǐng)求加入到網(wǎng)絡(luò)請(qǐng)求隊(duì)列中,如果不為空的話再判斷該緩存是否已過期揖闸,如果已經(jīng)過期了則同樣把這條請(qǐng)求加入到網(wǎng)絡(luò)請(qǐng)求隊(duì)列中揍堕,否則就認(rèn)為不需要重發(fā)網(wǎng)絡(luò)請(qǐng)求,直接使用緩存中的數(shù)據(jù)即可汤纸。之后會(huì)調(diào)用Request的parseNetworkResponse()方法來(lái)對(duì)數(shù)據(jù)進(jìn)行解析衩茸,再往后就是將解析出來(lái)的數(shù)據(jù)進(jìn)行回調(diào)了,這部分代碼的邏輯和NetworkDispatcher后半部分的邏輯是基本相同的贮泞,那么我們等下合并在一起看就好了楞慈,先來(lái)看一下NetworkDispatcher中是怎么處理網(wǎng)絡(luò)請(qǐng)求隊(duì)列的,代碼如下所示:
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)絡(luò)請(qǐ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); //解析響應(yīng)的網(wǎng)絡(luò)數(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)囊蓝,說(shuō)明網(wǎng)絡(luò)請(qǐng)求線程也是在不斷運(yùn)行的。在死循環(huán)中會(huì)調(diào)用Network的performRequest()方法來(lái)去發(fā)送網(wǎng)絡(luò)請(qǐng)求令蛉,而Network是一個(gè)接口聚霜,我們?cè)谏厦鎰?chuàng)建 stack是通過這段代碼(Network network = new BasicNetwork(stack);)實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求的,所以這里具體的實(shí)現(xiàn)是BasicNetwork,所以我們?cè)贐asicNetwork類中查看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); //調(diào)用了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); //組裝成一個(gè)NetworkResponse對(duì)象進(jìn)行返回
}
// 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); //組裝成一個(gè)NetworkResponse對(duì)象進(jìn)行返回
}
// 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)絡(luò)請(qǐng)求細(xì)節(jié)方面的東西蝎宇,需要注意的是調(diào)用了HttpStack的performRequest()方法,這里的HttpStack就是在一開始調(diào)用newRequestQueue()方法是創(chuàng)建的實(shí)例祷安,默認(rèn)情況下如果系統(tǒng)版本號(hào)大于9就創(chuàng)建的HurlStack對(duì)象姥芥,否則創(chuàng)建HttpClientStack對(duì)象。這兩個(gè)對(duì)象的內(nèi)部實(shí)際就是分別使用HttpURLConnection和HttpClient來(lái)發(fā)送網(wǎng)絡(luò)請(qǐng)求的汇鞭,之后會(huì)將服務(wù)器返回的數(shù)據(jù)組裝成一個(gè)NetworkResponse對(duì)象進(jìn)行返回撇眯。
在NetworkDispatcher中收到了NetworkResponse這個(gè)返回值后又會(huì)調(diào)用Request的parseNetworkResponse()方法來(lái)解析NetworkResponse中的數(shù)據(jù),以及將數(shù)據(jù)寫入到緩存虱咧,這個(gè)方法的實(shí)現(xiàn)是交給Request的子類來(lái)完成的,因?yàn)椴煌N類的Request解析的方式也肯定不同锚国。如果想自定義Request的方式,其中parseNetworkResponse()這個(gè)方法就是必須要重寫的腕巡。
在解析完了NetworkResponse中的數(shù)據(jù)之后,又會(huì)調(diào)用ExecutorDelivery的postResponse()方法來(lái)回調(diào)解析出的數(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); //回調(diào)解析出的數(shù)據(jù),具體實(shí)現(xiàn)在下面代碼中
2.在ExecutorDelivery類中回調(diào)解析出來(lái)的數(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()方法中傳入了一個(gè)ResponseDeliveryRunnable對(duì)象煎楣,這樣就可以保證該對(duì)象中的run()方法就是在主線程當(dāng)中運(yùn)行的了,我們看下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();
}
}
分析重點(diǎn)代碼即可,主要看Request的deliverResponse()方法车伞,這個(gè)方法也是我們?cè)谧远xRequest時(shí)需要重寫的另外一個(gè)方法择懂,每一條網(wǎng)絡(luò)請(qǐng)求的響應(yīng)都是回調(diào)到這個(gè)方法中,最后我們?cè)僭谶@個(gè)方法中將響應(yīng)的數(shù)據(jù)回調(diào)到Response.Listener的onResponse()方法中就可以了另玖。
5.Retrofit.
Square公司產(chǎn)品困曙,內(nèi)部封裝了OKhttp, 解決了網(wǎng)絡(luò)數(shù)據(jù)解析和線程切換的問題。