編輯:關於Android編程
作為Google的親兒子,Volley框架從誕生之日起就受到極大推崇,他簡單且適用於異步環境下的頻繁網絡操作,但是對於上傳文件或者想要post一些較大數據的場合,顯然他是束手無策的,這篇博文我會從源碼角度帶大家看看Volley框架到底是怎麼個執行流程;
平常我們使用Volley的標准步驟是:
(1)創建一個RequestQueue隊列;
(2)創建一個Request對象(當然實際中可能就是Request的子類了,比如:StringRequest、JsonRequest等等);
(3)調用RequestQueue的add方法將Request對象添加到請求隊列中;
那麼很自然,源碼分析應該是從創建RequestQueue隊列開始的,創建RequestQueue的語句是Volley.newRequestQueue(context)
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null);
}
public static RequestQueue newRequestQueue(Context context, HttpStack stack)
{
return newRequestQueue(context, stack, -1);
}
public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
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;
if (maxDiskCacheBytes <= -1)
{
// No maximum size specified
queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
}
else
{
// Disk cache size specified
queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
}
queue.start();
return queue;
}
newRequestQueue有四個構造函數,但是他們最後都會執行到newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes)這個構造函數上面,我只列出了用到的3個,newRequestQueue(context)首先會調用newRequestQueue(context,null),接著調用newRequestQueue(context,null,-1),程序走到第21行時,判斷stack是否等於null,等於的話則進入if語句塊,如果SDK版本號大於9的話,則創建HurlStack對象,否則的話創建HttpClientStack對象,接著在第31行創建一個參數為stack的Network對象,在第34行判斷maxDiskCacheBytes是否小於等於-1,這裡我們傳入的參數是-1,則進入if語句塊,利用剛剛生成的network對象創建RequestQueue對象,我們來看看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(Cache cache, Network network)實際上最後都會調用到有四個參數的構造函數上,其中DEFAULT_NETWORK_THREAD_POOL_SIZE的值為4,表示網絡請求緩存池的大小是4;
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
回到newRequestQueue函數,在創建出RequestQueue隊列之後,第45行啟動了我們的請求隊列,很自然我們需要到RequestQueue裡面的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();
}
}
首先執行stop方法暫停當前的緩存線程和網絡請求線程:
public void stop() {
if (mCacheDispatcher != null) {
mCacheDispatcher.quit();
}
for (int i = 0; i < mDispatchers.length; i++) {
if (mDispatchers[i] != null) {
mDispatchers[i].quit();
}
}
}
默認情況下的緩存線程只有一個,網絡請求線程有4個;
接著start方法的第4行重新創建一個緩存線程,並且啟動該線程,第8行通過for循環默認創建4個網絡請求線程,並且啟動每個線程,注意CacheDispatcher和NetworkDispatcher是繼承自Thread的,所以本質上他們還是線程:
public class CacheDispatcher extends Thread {
public class NetworkDispatcher extends Thread {
這也就解釋了在你使用Volley創建RequestQueue隊列之後,同時會看到還有另外5個線程的原因了;
接著我們便去創建自己的Request對象,隨後調用add方法將當前的Request對象添加到了RequestQueue隊列中,那麼,add方法到底做了些什麼事呢?
publicRequest add(Request 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 > stagedRequests = mWaitingRequests.get(cacheKey); if (stagedRequests == null) { stagedRequests = new LinkedList >(); } 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; } }
第3--10行設置一些Request請求的參數,接著第13行判斷當前請求是否可以被緩存,如果不可以的話會將當前請求加入到網絡請求隊列中同時直接返回request結束add方法,可以緩存的話(默認情況下是可以緩存的,因為在Request類中存在這麼一句代碼:private boolean mShouldCache = true;),則會跳過if語句去執行synchronized語句塊,首先會到第21行查看在mWaitingRequests中是否存在cacheKey對應的對象,mWaitingRequests是一個Map對象,而cacheKey的值是通過getCacheKey得到的,實際上就是一個url,具體值到底是什麼可以在Request.java類中看到如下源碼:
public String getCacheKey() {
return getUrl();
}
/** The redirect url to use for 3xx http responses */ private String mRedirectUrl; /** URL of this request. */ private final String mUrl; public String getUrl() { return (mRedirectUrl != null) ? mRedirectUrl : mUrl; }
getCacheKey會調用getUrl,而getUrl返回的值就是重定向或者我們本身所請求的url了,接著分析add方法,如果mWaitingRequests中存在cacheKey的話,則首先會在23行獲得該cacheKey所對應的value(這個值是一個Queue隊列)值,接著在第27行將當前的Request請求添加到剛剛獲得的隊列中,隨後28行更新mWaitingRequests中對應於cacheKey的value值;如果mWaitingRequests中不存在cacheKey的話,則在36行將當前Request請求添加到緩存隊列中;
我們發現上面的add操作不是把當前請求添加到網絡請求隊列中就是將其添加到緩存隊列中,兩個隊列中的請求真正的執行者就是我們之前創建的那5個線程了,因為他們都是線程,所以執行的代碼必定出現在各自的run方法中,首先來看看緩存線程的run方法,它位於CacheDispatcher.java類中:
@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;
}
}
}
可以看到此run方法第9行是while(true)死循環,說明這個線程會一直在後台執行的,第13行首先會從緩存隊列中取出請求,接著在第23行獲取當前Request對應的緩存中的內容,第24行進行判斷,如果不存在的話進入if語句塊,將當前請求添加到網絡請求隊列中,並且跳出此次循環;如果當前緩存中存在與當前Request對應的緩存內容的話,接著在32行判斷該緩存內容是否已經過期,如果已經過期的話,則執行if語句塊,將當前請求添加到網絡請求隊列中,同時也跳出本次循環的剩余內容;如果當前Request對應的緩存內容即存在也沒過期的話,就會執行後面的內容,在第41行會調用Request的parseNetworkResponse方法 :
abstract protected Response可以看到,這只是一個抽象方法,具體需要子類去實現,比如StringRequest的parseNetworkResponse方法為:parseNetworkResponse(NetworkResponse response);
@Override
protected Response 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));
}
回到CacheDispatcher的run方法第45行會查看當前的cache緩存內容如果是沒有過期的話,直接調用mDelivery的postResponse將response結果發送出去,mDelivery是ResponseDelivery類型的,他是一個接口,我們找到他的一個實現類ExecutorDelivery,它裡面的postResponse方法如下:
@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類型的對象,他是ExecutorDelivery的私有內部類,繼承自Runnable:
private class ResponseDeliveryRunnable implements Runnable {
調用mResponsePoster的execute方法其實就是調用ResponseDeliveryRunnable的run方法,來看看它裡面做了些什麼事:
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();
}
}
第9行會判斷請求是否成功,成功的話會調用Request的deliverResponse方法,失敗的話調用Request的deliverError方法:
abstract protected void deliverResponse(T response);可以看到Request的deliverResponse是個抽象方法,需要我們在子類中實現,比如:StringRequest中的實現是:
@Override
protected void deliverResponse(String response) {
mListener.onResponse(response);
}
他會回調Listener的onResponse方法,而這個方法就是我們在平常返回結果執行成功後需要自己實現的方法了;
而Request的deliverError就是一個具體方法了:
public void deliverError(VolleyError error) {
if (mErrorListener != null) {
mErrorListener.onErrorResponse(error);
}
}
他會調用ErrorListener的onErrorResponse方法,這也就是我們平常返回結果執行失敗後需要回調的方法,onErrorResponse裡面的代碼需要我們自己實現;
這樣的話,緩存隊列中的線程執行過程已經分析完畢了,接下來就是網絡請求隊列中的線程執行流程了,其實想想也知道,兩者肯定有共同的地方,只不過緩存線程的話,因為已經存在返回的結果了,所以我們只需要將該結果進行適當的解析並且返回給主線程就可以了,網絡請求線程的話就需要我們首先先去網絡中獲取到結果隨後才能進行適當的解析並且返回給主線程,好的,接下來從源碼角度看看Volley是怎麼實現的:
查看NetworkDispatcher的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);
// 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) {
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);
}
}
}
首先看到第4行和CacheDispatcher一樣,同樣也是一個while(true)的死循環,表明網絡請求線程也是會在後台一直查看網絡請求隊列中是否有需要執行的請求的,第9行從網絡請求隊列中取出隊頭元素,接著第23行判斷請求是否被暫停,被暫停的話則結束本次循環,沒有的話執行到31行,通過Network的performRequest方法執行request請求,而Network只是一個接口,我們找到他的一個實現類BasicNetwork來看看它裡面performRequest這個方法的源碼:
@Override
public NetworkResponse performRequest(Request request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();
while (true) {
HttpResponse httpResponse = null;
byte[] responseContents = null;
Map responseHeaders = Collections.emptyMap();
try {
// Gather headers.
Map headers = new HashMap();
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) {
Entry entry = request.getCacheEntry();
if (entry == null) {
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
responseHeaders, true,
SystemClock.elapsedRealtime() - requestStart);
}
// A HTTP 304 response does not have all header fields. We
// have to use the header fields from the cache entry plus
// the new ones from the response.
// 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);
}
// Handle moved resources
if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
String newUrl = responseHeaders.get("Location");
request.setRedirectUrl(newUrl);
}
// 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) {
attemptRetryOnException("socket", request, new TimeoutError());
} catch (ConnectTimeoutException e) {
attemptRetryOnException("connection", request, new TimeoutError());
} catch (MalformedURLException e) {
throw new RuntimeException("Bad URL " + request.getUrl(), e);
} catch (IOException e) {
int statusCode = 0;
NetworkResponse networkResponse = null;
if (httpResponse != null) {
statusCode = httpResponse.getStatusLine().getStatusCode();
} else {
throw new NoConnectionError(e);
}
if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
VolleyLog.e("Request at %s has been redirected to %s", request.getOriginUrl(), request.getUrl());
} else {
VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
}
if (responseContents != null) {
networkResponse = new NetworkResponse(statusCode, responseContents,
responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
statusCode == HttpStatus.SC_FORBIDDEN) {
attemptRetryOnException("auth",
request, new AuthFailureError(networkResponse));
} else if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
attemptRetryOnException("redirect",
request, new AuthFailureError(networkResponse));
} else {
// TODO: Only throw ServerError for 5xx status codes.
throw new ServerError(networkResponse);
}
} else {
throw new NetworkError(networkResponse);
}
}
}
}
這個方法相對來說比較長,我們只挑重點來進行分析,首先第12行之前的部分都是在對我們的請求進行一些初始化的操作,第12行調用HttpStack的performRequest方法,並且將返回結果傳給了HttpResponse對象,這裡的HttpStack就是我們剛開始在newRequestQueue中創建的HttpStack對象,如果SDK版本號大於9的話,創建的是HurlStack對象,否則的話創建的是HttpClientStack對象,HurlStack內部是使用HttpURLConnection來進行網絡請求的,HttpClientStack內部是使用HttpClient進行網絡請求的,之後會將HttpResponse對象封裝成NetworkResponse對象返回;
回到我們NetworkDispatcher的run方法的第31行,此時獲取到了NetworkResponse對象,第47行我們判斷當前Request對象是否允許緩存並且Response對象的結果是否為null,滿足條件之後進入if語句塊中,將當前的response返回結果添加到緩存中,接下來的過程就和上面CacheDispatcher一樣了,首先對NetworkResponse進行解析,隨後在第54行通過ExecutorDelivery對象調用postResponse或者在第62行通過ExecutorDelivery對象調用postError將其結果返回給主線程,這點之前上面已經講過啦!
上面我們大體上分析了Volley框架的源碼,在此做一個小結:
(1)在Volley中存在三種類型的線程:主線程、緩存線程(1個)、網絡請求線程(默認4個)
(2)在Volly存在兩個隊列:緩存隊列(緩存線程利用死循環從中取出Request請求執行)、網絡請求隊列(網絡請求線程利用死循環從中取出Request請求執行)
(3)我們在主線程中調用newRequestQueue方法時,同時會發現啟動了另外5個線程;
(4)在主線程中調用RequestQueue的add方法添加一條網絡請求之後,該請求默認情況下是會被加入到緩存隊列中的,如果在緩存中找到了對應於該請求的緩存結果,則會對其直接進行解析返回,之後調用ExecutorDelivery的postResponse或者postError方法將返回結果傳遞給主線程,這兩個方法會調用Request的deliverResponse和deliverError方法,而這兩個方法最終就會分別調用Listener的onResponse方法和ErrorListener的onErrorResponse方法,這也就是我們通常在使用Volley框架的時候需要自己實現的方法了;如果緩存中不存在對應Request的緩存結果或者對應於Request的緩存結果已經過期的話,則會將其添加到網絡請求隊列中,發送HTTP請求獲取響應結果,並對響應結果進行封裝、解析、寫入緩存,之後呢,再采用和之前一樣的方法回調回主線程
Android中Activity切換時共享視圖元素的切換動畫(4.x兼容方案)
方案一:PreLollipopTransition首先在 build.gradle 配置文件添加這個庫依賴dependencies { compile
Android 自定義View 密碼框實例代碼
暴露您view中所有影響可見外觀的屬性或者行為。•通過XML添加和設置樣式•通過元素的屬性來控制其外觀和行為,支持和重要事件交流的事件監聽器詳細步
【干貨分享】關於 Android N 的那些事兒
今年3月,Google 破天荒提前半年發布了 Android N 開發者預覽版。當然,作為一個不合格的谷粉並沒有第一時間體驗安裝,因為至今仍然能夠回憶起來去年今日此門中(
Android實現帶圖標的ListView
Android實現帶圖標的ListView已經成為每一個android應用開發初學者的必學知識,熟練掌握此知識點,對我們以後的開發工作會大有好處,今天我們就來一步一步實現