編輯:關於Android編程
臨近畢業,各種事情各種忙。我也沒有認真專注寫過博客,最近仔細看了Volley框架的使用及其源碼,思前想後,想挑戰一下自己,還是寫一篇博客來分享,如有錯誤,歡迎吐槽。
網絡請求是一個App很重要的一部分,android系統只是提供了一個平台,而android應用則是基於這個平台上進行展示數據,起到與用戶進行交互的作用,數據來源於服務端,而二者之間必須通過互聯網進行傳輸數據,在Android系統發布初期,很多開發者都是在Apache協會的Http協議的基礎上進行網絡請求方法的封裝,當然有小白和大神,難免會出現一些意想不到的問題。谷歌也是考慮到了這一點,在2013年I/O大會上發布了一個基於HttpURLConnection的網絡請求框架–Volley。
Volley把AsyncHttpClient和Universal-Image-Loader的優點集於了一身,既可以像AsyncHttpClient一樣非常簡單地進行HTTP通信,也可以像Universal-Image-Loader一樣輕松加載網絡上的圖片。
Volley適用於請求一些數據量不大且頻繁的數據,不適合請求一些大數據。
我們知道Volley所有的請求都是基於Request這個抽象類,我們來看看這個類中的主要方法:
/**
* Creates a new request with the given method (one of the values from {@link Method}),
* URL, and error listener. Note that the normal response listener is not provided here as
* delivery of responses is provided by subclasses, who have a better idea of how to deliver
* an already-parsed response.
*/
public Request(int method, String url, Response.ErrorListener listener) {
mMethod = method;
mUrl = url;
mErrorListener = listener;
setRetryPolicy(new DefaultRetryPolicy());
mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
}
這裡Request的構造方法,沒什麼特別,就是把傳入的參數傳遞給對應的對象,以及設置了一個請求的優先級,這裡為默認。
abstract protected Response parseNetworkResponse(NetworkResponse response);
這是Request中最主要的兩個方法之一,每個繼承的類都必須具體實現這兩個方法。parseNetworkResponse方法中response是最後服務端返回的字節碼數據之後在子線程中被調用,進行解析數據,我們可以將這個字節碼數據轉換成我們想要的類型,比如StringRequest中,parseNetworkResponse方法的實現如下:
@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));
}
很明顯,這個將字節碼以指定的編碼格式轉換成了String字符串。
abstract protected void deliverResponse(T response);
看著方法名就可以知道,這個是將最後解析過的請求數據交付給主線程,讓主線程進行處理,還記得我們在構造函數中傳入一個Listener的對象嗎,對,就是它。我們可以通過接口回調的方式將最終數據返回給主線程使用。還是一樣,我們看看StringRequest中的實現:
@Override
protected void deliverResponse(String response) {
mListener.onResponse(response);
}
Request抽象類中還有一些其他的屬性,比如:
public Request setTag(Object tag) {
mTag = tag;
return this;
}
給每個Request附加一個標簽,便於管理。
public Request setRetryPolicy(RetryPolicy retryPolicy) {
mRetryPolicy = retryPolicy;
return this;
}
設置一個自定義的在請求失敗的情況下重新請求的規則。
public void addMarker(String tag) {
if (MarkerLog.ENABLED) {
mEventLog.add(tag, Thread.currentThread().getId());
} else if (mRequestBirthTime == 0) {
mRequestBirthTime = SystemClock.elapsedRealtime();
}
}
這個方法主要是打印日志的方式展示請求的過程進度。
/**
* Returns the URL of this request.
*/
public String getUrl() {
return mUrl;
}
/**
* Returns the cache key for this request. By default, this is the URL.
*/
public String getCacheKey() {
return getUrl();
}
這裡要注意的是把url作為一個緩存的Key。
我們知道我們需要把Request添加到RequestQueue中才能完成網絡請求,我們一般通過靜態方法
RequestQueue mQueue = Volley.newRequestQueue(MainActivity.this);
那麼我們看看RequestQueue的代碼:
創建一個RequestQueue對象,
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) {
//sdk版本高於9時,創建一個HrlStack對象,裡面是網絡請求的具體邏輯
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
//創建一個Network對象,裡面進行了網絡請求數據方法的二次封裝
Network network = new BasicNetwork(stack);
//傳入一個緩存對象以及netWork對象
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
//啟動隊列
queue.start();
return queue;
}
可以看出,在這個方法中,最後在創建RequestQueue的時候,傳入了一個緩存對象以及封裝好的網絡請求方法的對象,最後調用一個start方法,現在我們還沒有看它的具體實現,但是我們知道現在是萬事俱備,只欠東風了。就像一台搾汁機接通了電源並且開啟了開關,我們只需要扔進水果就行了。當然,我們這裡不能扔進水果…我們需要做的就是:
requestQueue.add(request);
把Request對象加入進去,讓它自己去跑,我們已經在Request中兩個抽象方法中寫好了解析以及最終數據的交付邏輯。我們來看看RequestQueue.add()
中的代碼:
public Request add(Request request) {
// 在此request設置一個標記,指明此request屬於的RequestQueue
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
//將請求添加到隊列中
mCurrentRequests.add(request);
}
// 處理請求按順序添加
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// 如果request不需要緩存,直接加入mNetworkQueue隊列,等待處理
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)) {
// 等待緩存的隊列中已經有了此請求
Queue> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList>();
}
stagedRequests.add(request);
//將新增加的request的集合對象覆蓋進去
mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
}
} else {
//如果等待緩存隊列中只有一個請求,那麼value的值就為null
mWaitingRequests.put(cacheKey, null);
//將request添加到緩存工作隊列中
mCacheQueue.add(request);
}
return request;
}
}
可能一部分讀者看到這裡就有點暈了,怎麼這麼多的隊列。我們一個個解釋。
mCurrentRequests,它的對象聲明是這樣的:
private final Set
Set集合中存放的是無序且不重復的子對象。mCurrentRequests 在這裡包含了所有我們通過requestQueue.add(request);
方法添加的request,這個集合主要起到管理request的作用,在request請求在NetworkDispatcher線程中處理結束之後,無論請求成功或者失敗,都會調用Request的方法:
void finish(final String tag) {
if (mRequestQueue != null) {
mRequestQueue.finish(this);
}
很明顯,Request中調用了mRequestQueue的finish方法,讓我們看下代碼:
void finish(Request request) {
// Remove from the set of requests currently being processed.
synchronized (mCurrentRequests) {
mCurrentRequests.remove(request);
}
這裡我貼出了部分代碼。我們看到,當請求結束之後,就從mCurrentRequests移除掉了這個request。
mNetworkQueue和mCacheQueue,它們的聲明是這樣的:
private final PriorityBlockingQueue
private final PriorityBlockingQueue
先來說下PriorityBlockingQueue這個類吧,這個類實現了implements BlockingQueue
這個接口,BlockingQueue是jdk中Concurrent包中新增的用於解決了多線程中,如何高效安全“傳輸”數據的問題,並且它能保證線程安全。所以將mNetworkQueue和mCacheQueue傳入子線程中,讓子線程從中獲取request進行處理。
mWaitingRequests:
private final Map
new HashMap
mWaitingRequests是一個Map集合,裡面保存著鍵值對,以request的url為key。
接著往下我們看`queue.start();`這個方法中的代碼:
/**
* Starts the dispatchers in this queue.
*/
public void start() {
stop(); // 如果隊列在開啟狀態,關閉
//創建一個緩存分發線程,並且啟動它
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// 創建多個網絡請求的線程,並且啟動它們
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
NetworkDispatcher和CacheDispatcher都是繼承於Thread類。從上面的源碼可知,我們將默認啟動是4個子線程,這4個子線程一直在跑,執行RequestQueue中的Request。而mCacheDispatcher只開啟一個線程,用於緩存響應數據。
接著我們來看在NetworkDispatcher線程中是如何處理請求的:
public class NetworkDispatcher extends Thread {
/** 需要處理的請求隊列 */
private final BlockingQueue> mQueue;
/** 封裝好了的用來處理網絡請求的接口 */
private final Network mNetwork;
/** 用於寫入緩存的對象 */
private final Cache mCache;
/** 用來請求之後的接口回調 */
private final ResponseDelivery mDelivery;
/** 用於退出此線程 */
private volatile boolean mQuit = false;
public NetworkDispatcher(BlockingQueue> queue,
Network network, Cache cache,
ResponseDelivery delivery) {
mQueue = queue;
mNetwork = network;
mCache = cache;
mDelivery = delivery;
}
/**
* 結束此線程
*/
public void quit() {
mQuit = true;
interrupt();
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private void addTrafficStatsTag(Request request) {
//Api>=14以上調用此方法(if API >= 14)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { TrafficStats.setThreadStatsTag(
request.getTrafficStatsTag());
}
}
@Override
public void run() {
//設置線程為後台線程
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//全真循環,不斷獲取mQueue中的request進行處理
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
Request request;
try {
// 獲取請求
request = mQueue.take();
} catch (InterruptedException e) {
// 請求超時的時候,結束線程
if (mQuit) {
return;
}
continue;
}
try {
request.addMarker("network-queue-take");
// 請求被取消
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
addTrafficStatsTag(request);
// 執行請求
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// 如果我們已經遞交過同樣的響應數據,那麼不會再次遞交
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
// 在子線程中解析字節碼為我們想要的數據類型
Response response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// request需要緩存,並且response的緩存入口不為空
if (request.shouldCache() && response.cacheEntry != null) {
//以request的url為key,以response的緩存入口為value進行緩存
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
//標志已經遞交,請求結束
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);
}
}
Volley默認是開啟四個NetworkDispatcher這個線程,用於不斷從mQueue中獲取request進行處理請求。以mNetwork為處理器,並且在獲取成功之後回調在request中parseNetworkResponse方法進行解析成我們想要的數據。這個方法是不是很熟悉,沒錯,它就是我們在自定義Request中所要重新的抽象方法之一。在解析失敗時也會有相應的接口回調。
在請求需要進行緩存並且響應數據緩存入口不為空的情況下,我們將以request的url為key,以response的緩存入口為value進行緩存。
最後將解析好的數據通過 mDelivery.postResponse(request, response);
方法拋給主線程處理。
CacheDispatcher是一個緩存線程,讓我們看看它的代碼實現:
public class CacheDispatcher extends Thread {
private static final boolean DEBUG = VolleyLog.DEBUG;
/** 緩存隊列*/
private final BlockingQueue> mCacheQueue;
/** 網絡請求隊列*/
private final BlockingQueue> mNetworkQueue;
/** 緩存對象 */
private final Cache mCache;
/** 用於將最終數據傳遞的接口*/
private final ResponseDelivery mDelivery;
/** 線程是否存活 */
private volatile boolean mQuit = false;
public CacheDispatcher(
BlockingQueue> cacheQueue, BlockingQueue> networkQueue,
Cache cache, ResponseDelivery delivery) {
mCacheQueue = cacheQueue;
mNetworkQueue = networkQueue;
mCache = cache;
mDelivery = delivery;
}
/**
* 結束線程
*/
public void quit() {
mQuit = true;
interrupt();
}
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher"); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 初始化線程對象
mCache.initialize();
//全真循環,不斷獲取mCacheQueue中的request進行處理
while (true) {
try {
//從mCacheQueue中獲取request對象,如果mCacheQueue為空,那麼會堵塞暫停,直至mCacheQueue添加了對象
final Request request = mCacheQueue.take();
request.addMarker("cache-queue-take");
// 取消了緩存要求
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// 獲取緩存的條目
Cache.Entry entry =mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// 如果緩存中找不到緩存條目,那麼把請求扔mNetworkQueue,讓它去通過網絡請求數據
mNetworkQueue.put(request);
continue;
}
//如果緩存條目過期的話,那麼把request加入mNetworkQueue讓它去通過網絡重新請求數據
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
//有了有效的緩存對象,將緩存對象通過Request中定義的解析方法解析成我們想要的數據類型
request.addMarker("cache-hit");
Response response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
if (!entry.refreshNeeded()) {
// 不需要刷新的數據,可以直接遞交給主線程使用了
mDelivery.postResponse(request, response);
} else {
//需要刷新的數據,將它它去通過網絡重新請求數據
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// 因為只是響應數據需要刷新,所以可以把response作為媒介,標記這個為true
response.intermediate = true;
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) {
if (mQuit) {
return;
}
continue;
}
}
}
}
Volley官網的框架結構流程圖:
總結一下:Volley框架中封裝了網絡請求的具體方法在HurlStack類中,它就相當於一個加載器,BasicNetwork類對它進行了二次封裝。CacheDispatcher的目的就是從mCacheQueue中獲取request,然後再從緩存中判斷是否有對應的緩存,如果沒有或者過期等原因,會將此request拋給mNetworkQueue隊列,讓mNetworkQueue在NetworkDispatcher中,而在NetworkDispatcher中通過獲取mNetworkQueue的每個request,使用默認的HurlStack加載器(當然也可以自定義)去網絡上請求數據,最後講請求成功的響應數據再緩存起來。
至此。Volley框架的源碼算是解析完畢。那麼如何在實際開發項目中正確使用它呢,我會在下一篇中敘述。
使用Scroller實現絢麗的ListView左右滑動刪除Item效果這裡來給大家帶來使用Scroller的小例子,同時也能用來幫助初步解除的讀者更加熟悉的掌握Scrol
注意:以下內容中出現的類和部分類的方法只能在Android源碼中或者通過反射機制才能使用,在SDK中編譯是通不過的!!如Android.os.Service; Memeo
最近因項目需求,需要在存儲卡查找文件,經測試發現部分手機掛載路徑查找不到,這裡分享一個有效的方法。 /** * 獲取所有存儲卡掛載路徑 * @return
今天在Android Studio中把另外一個項目引入當前項目,編譯的時候出現了java.util.zip.ZipException: duplicate entry錯誤