摘要:在大行其道的今天,已經略顯過時。我們按照上面的思路,從和作為出發點,由淺入深來閱讀源碼。在最后看類源碼時我們可以看到這個過程。負責分發,對來說,就是通過將發送到了主線程在中調用了。的個數,決定了網絡請求的最大并發數。
Volley
在 retrofit+okhttp 大行其道的今天,volley 已經略顯過時。使用 volley,我們無法避免寫一些樣板代碼,但在它剛出現時,曾經很大程度降低了 android 開發中網絡請求這部分的難度,簡潔、輕量、容易定制擴展,我們依然能從它的源碼中感受到這些。
看一個使用 volley 的小 demo
StringRequest request = new StringRequest("http://example.com", new Response .Listener() { @Override public void onResponse(String s) { } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { } }); RequestQueue requestQueue = Volley.newRequestQueue(context); requestQueue.add(request);
三步走
創建一個 Request
創建一個 RequestQueue
聯結兩者
這就是 volley 作為一個網絡請求庫提供給我們的用戶界面,超級簡潔有沒有。Request 是一個數據,是一個相對靜態的概念,我們使用它來說我們的網絡請求要發送到哪里、帶有什么樣的參數、返回的數據是什么對象、請求成功了怎樣處理、請求失敗了又要怎么辦,等等等等。RequestQueue 呢,是我們的執行引擎,吃掉一個個 Request,消費 Request 的屬性,同時返回 Response。
我們按照上面的思路,從 Request 和 RequestQueue 作為出發點,由淺入深來閱讀 volley 源碼。
Request前面說到,Request 在 volley 中是相對靜態的概念,靜態是好的,我們喜歡靜態的東西,所以我們從 Request 開始。看 Request 的定義
public abstract class Requestimplements Comparable > { }
我們看到,Request 是個抽象類,有泛型,實現了 Comparable 接口。看到這里你也許可以大膽推測,這個 Request 是可以大大擴展的,大多數網絡請求以表單的方式作為參數,十分多變,那么這個泛型來指名返回結果的類型應該是十分合理的。沒錯,就是這樣。實現 Comparable 呢,Request 的比較有什么含義?想到上面有 Queue 的概念,這個 Comaparable 也許和請求的優先級有關,對的,事實上 RequestQueue 中的確使用了 PriorityBlockingQueue 來達到管理 Request 優先級的效果,后文再細說。
上面簡單的一行代碼已經向我們透漏了大量信息,讓我們更近一步。看看 Request 有哪些屬性
public abstract class Requestimplements Comparable > { /** * Request method of this request. Currently supports GET, POST, PUT, DELETE, HEAD, OPTIONS, * TRACE, and PATCH. */ private final int mMethod; /** URL of this request. */ private final String mUrl; /** Default tag for {@link TrafficStats}. */ private final int mDefaultTrafficStatsTag; /** Listener interface for errors. */ private final Response.ErrorListener mErrorListener; /** Sequence number of this request, used to enforce FIFO ordering. */ private Integer mSequence; /** The request queue this request is associated with. */ private RequestQueue mRequestQueue; /** Whether or not responses to this request should be cached. */ private boolean mShouldCache = true; /** Whether or not this request has been canceled. */ private boolean mCanceled = false; /** Whether or not a response has been delivered for this request yet. */ private boolean mResponseDelivered = false; /** Whether the request should be retried in the event of an HTTP 5xx (server) error. */ private boolean mShouldRetryServerErrors = false; /** The retry policy for this request. */ private RetryPolicy mRetryPolicy; /** * When a request can be retrieved from cache but must be refreshed from * the network, the cache entry will be stored here so that in the event of * a "Not Modified" response, we can be sure it hasn"t been evicted from cache. */ private Cache.Entry mCacheEntry = null; /** An opaque token tagging this request; used for bulk cancellation. */ private Object mTag; }
我們簡單的分下類:
mMethod、mUrl:用什么 Http 方法給誰發請求,mUrl 中可以附帶參數(如果想在 body 中傳遞參數呢,想定制 Header 呢)
mShouldCache、mCanceled、mCacheEntry:緩存相關
mRequestQueue:關聯的 RequestQueue,結束 Request 時使用
mErrorListener:錯誤回調,想想上面的 demo,還有一個成功的回調,但這里并沒有,下文再說
mShouldRetryServerErrors、mRetryPolicy:控制重試
mTag:不同的 Request 可以設置相關的 tag,這樣我們可以用 tag 來批量取消 Request
mSequence、mDefaultTrafficStatsTag:忽略
想一想當我們自定義一個 Request 的時候會關心那些屬性呢,Method、Url、Cache、ErrorListener,大概就這樣吧。
在上面的分類中還有一些問題要解決,一個一個來。
public byte[] getBody() throws AuthFailureError { Mapparams = getParams(); if (params != null && params.size() > 0) { return encodeParameters(params, getParamsEncoding()); } return null; }
這是個 public 方法,我們可以 override 它。不急,可以看到它使用了 getParams()、encodeParameters()、getParamsEncoding() 方法,我們 override 這三個方法也可以達到間接影響 Body 的效果,encodeParameters() 是 private 的,還好一般沒有改變它的必要。
Headerpublic MapgetHeaders() throws AuthFailureError { return Collections.emptyMap(); }
override!
請求成功的回調abstract protected void deliverResponse(T response);另外一個比較重要的方法
abstract protected ResponseparseNetworkResponse(NetworkResponse response);
將 NetworkResponse 轉化為客戶端的類型,這是一個 abstract 方法,我們自定義的 Request 子類必須實現。事實上,demo 中的 StringRequest 就是 volley 中的一個 Request 的具體實現,我們可以從中學習:
@Override protected ResponseparseNetworkResponse(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)); }
可以看到,Request 定義了一個網絡請求的細節問題,服務、參數、重試策略、緩存策略、返回數據處理等。其中 Header、參數、返回值類型的變化可以通過繼承 Request,實現不同的子類來實現。在 volley 中內置了一些實現,包括 StringRequest、JsonObjectRequest、JsonArrayRequest、ImageRequest等,繼承的方式顯得不太靈活,需要定義一堆的 Request。我們可以稍作擴展,用組合來避免 Request 類的爆炸。
public interface ResponseParser{ Response parseNetworkResponse(NetworkResponse networkResponse); } public class MyRequest extends Request { final private Map params; final private Map headers; final private ResponseParser responseParser; final private Response.Listener listener; public MyRequest(int method, String url, Response.ErrorListener errorListener, Map params, Map headers, ResponseParser responseParser, Response.Listener listener) { super(method, url, errorListener); this.params = params; this.headers = headers; this.responseParser = responseParser; this.listener = listener; } @Override protected Response parseNetworkResponse(NetworkResponse networkResponse) { return responseParser.parseNetworkResponse(networkResponse); } @Override protected void deliverResponse(T t) { listener.onResponse(t); } @Override public Map getHeaders() throws AuthFailureError { return headers; } @Override protected Map getParams() throws AuthFailureError { return params; } }
在使用 volley 的過程中,我們大部分時間會是處理 Request 相關的內容。將 Request 設計的簡單直白,降低使用者的難度,這是 volley 非常友好的地方,同時,Volley 的 Request 創建過程也許并不是太有趣,比如請求參數的創建,無聊且不太直觀。根據具體的業務,寫一些 Builder 可以一定程度彌補這一點。接下來是 RequestQueue 的部分,RequestQueue 在邏輯上是動態的,是一臺機器運轉的部分,稍微復雜一些,但是,在某種意義上它又是穩定的,它提供了很好的默認實現,多數情況下我們不需要定制,不需要擴展,不需要理解具體細節,它在那里,它完美的 work。
RequestQueueRequestQueue 這臺機器是如何運轉的呢?使用 add 方法,一個 Request 首先會被放入 mCacheQueue,mCacheQueue 是一個 PriorityBlockingQueue,CacheDispatcher 從 mCacheQueue 中取出 Request,查看 cache 中是否已經有相同之前相同的請求得到的有效緩存,有就直接使用 ResponseDelivery 將緩存的結果分發到 Request 中的 deliverResponse 方法,從而傳遞到用戶手中;如果沒有有效的緩存結果,則將 Request 添加到 mNetworkQueue,NetworkDispatcher 從 mNetworkQueue 中讀取 Request,使用 Network 執行 Request 中的請求,成功后將結果放入 Cache,同時用 ResponseDelivery 分發結果。這就是一個 Request 的周期。
流程圖如下:
上面我們提到了一些關鍵的類或屬性。
mCacheQueue、mNetworkQueue:兩個都是 PriorityBlockingQueue,結合 Request 實現的 Comparable 接口,Request 的優先級在這里實現。
CacheDispatcher、NetworkDispatcher:負責從 Request 到 Response 的轉化
Cache、Network:將 Request 轉化為 Response 的實際執行者
ResponseDelivery:分發 Response
有了這些概念后,我們分別分析這些類的代碼,試著由低向上來理解 RequestQueue。
CacheDispatcherpublic class CacheDispatcher extends Thread { 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() { while (true) { try { // Get a request from the cache triage queue, blocking until // at least one is available. final Request> request = mCacheQueue.take(); Cache.Entry entry = mCache.get(request.getCacheKey()); if (entry == null) { mNetworkQueue.put(request); continue; } Response> response = request.parseNetworkResponse( new NetworkResponse(entry.data, entry.responseHeaders)); mDelivery.postResponse(request, response); } catch (InterruptedException e) { // We may have been interrupted because it was time to quit. if (mQuit) { return; } continue; } } } }
為便于理解,刪去了大量代碼,只保留下核心部分。
在構造方法中,我們看到了 mCacheQueue,Request 在 RequestQueue 類中會先放到這里;還有mNetworkQueue,mCacheQueue 中的 Request 沒有命中 Cache,會被再放到這里;mCache,存取 Response 的地方;mDelivery,Response 被分發的地方。
CacheDispatcher 繼承了 Thread,我們可以 quit 它,看到 quit 中先標記,然后 interrupt,意味著 run 方法中會相應的處理。
run 中,在線程中開啟了無限循環,每次循環從 mCacheQueue 讀取 Request,然后進行 1 中提到的過程。也可以看到對于 quit 的處理。
NetworkDispatcherpublic class NetworkDispatcher extends Thread { public NetworkDispatcher(BlockingQueue> queue, Network network, Cache cache, ResponseDelivery delivery) { mQueue = queue; mNetwork = network; mCache = cache; mDelivery = delivery; } public void quit() { mQuit = true; interrupt(); } @Override public void run() { while (true) { 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; } NetworkResponse networkResponse = mNetwork.performRequest(request); Response> response = request.parseNetworkResponse(networkResponse);. if (request.shouldCache() && response.cacheEntry != null) { mCache.put(request.getCacheKey(), response.cacheEntry); } mDelivery.postResponse(request, response); } } }
NetworkDispatcher 和 CacheDispatcher 是不是非常相似?
Cache一個接口類,和普通的 Cache 沒有什么太大差別,不贅述。
Network
public interface Network { public NetworkResponse performRequest(Request> request) throws VolleyError; } public interface HttpStack { public HttpResponse performRequest(Request> request, MapadditionalHeaders) throws IOException, AuthFailureError; }
它的實現是 BasicNetwork,BasicNetwork 依賴 HttpStack,HttpStack 是實際發起網絡請求的地方,volley 分別提供了 HttpClient 和 HttpURLConnection 的實現。我們可以定制這部分,比如基于 OKHttp 的實現。在最后看 Volley 類源碼時我們可以看到這個過程。
ResponseDeliveryResponseDelivery 負責分發 Response,對 Android 來說,就是通過 Handler 將 Response 發送到了主線程
public class ExecutorDelivery implements ResponseDelivery { private final Executor mResponsePoster; public ExecutorDelivery(final Handler handler) { mResponsePoster = new Executor() { @Override public void execute(Runnable command) { handler.post(command); } }; } @Override public void postResponse(Request> request, Response> response, Runnable runnable) { mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable)); } }
在 ResponseDeliveryRunnable 中調用了 Request.deliverResponse。
RequestQueue有了上面的內容,理解 RequestQueue 就很容易了。首先看構造方法。
public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) { mCache = cache; mNetwork = network; mDispatchers = new NetworkDispatcher[threadPoolSize]; mDelivery = delivery; }
一共四個參數。
cache:一個實現了讀寫刪功能的 Cache 對象,通過它我們可以實現自己的緩存策略。
Network:前面提到,這是一個接口,只有一個要實現的方法,傳入一個 Request,傳出一個 Response。或許我們也可以把緩存策略放到這里面來,讓 Cache 對 RequestQueue 透明,也未嘗不是一個不錯的想法。
threadPoolSize:NetworkDispatcher 的個數,決定了網絡請求的最大并發數。
ResponseDelivery:分發 Response,默認實現 ExecutorDelivery 將 Response 分發到 Request.deliverResponse, 這就是我們 override deliverResponse 方法可以得到 Response 的原因,同時我們從上面 ExecutorDelivery 的代碼中看到,這個分發是通過像一個 Handler post 一個 Runnable 實現的,Handler 的性質決定了數據分發到哪個線程。
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(); } }
mCacheDispatcher、mDispatchers 初始化,從上面我們知道它們都是 Thread 的子類, start 后內部開始循環,從 mCacheQueue、mNetworkQueue 讀取 Request 進行處理。
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; } }
和我們開始討論 RequestQueue 時的流程圖多了一些細節的處理,比如如果一個 Request 被設置為不可緩存,則直接添加到 mNetworkQueue;有相等的 Request 時,會把重復的請求加到一個等待隊列,分享請求結果。
RequestQueue 的內容就到這里了。RequestQueue 在并發上沒有選擇使用 Java 提供的線程池,而是使用固定數據的線程數組(NetworkDispatcher)配合阻塞的優先級隊列來實現任務并發。同時在緩存、網絡、分發方式上提供了可定制的接口,當然,多數時候使用默認實現就可以了。為了方便的使用默認的實現,就像文章開始的 demo 展示的那樣,volley 提供了一個工具類 Volley。
VolleyVolley 只提供兩個方法。用來創建默認的 RequestQueue,如下:
public class Volley { 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 { // 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; } public static RequestQueue newRequestQueue(Context context) { return newRequestQueue(context, null); } }擴展
Volley 自帶的 NetworkImageView
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66937.html
摘要:前言想必很多人都用過,為了建立網絡編程的知識體系,是必須要講的知識點,所以我這里有必要再次介紹一下的使用。簡介在年大會上推出了一個新的網絡通信框架。在使用前請下載庫并放在目錄下并到工程中。 前言 Volley想必很多人都用過,為了建立網絡編程的知識體系,Volley是必須要講的知識點,所以我這里有必要再次介紹一下Volley的使用。 1.Volley簡介 在2013年Google I/...
摘要:接下來看看網絡調度線程。讓我們再回到,請求網絡后,會將響應結果存在緩存中,如果響應結果成功則調用來回調給主線程。我們用請求網絡的寫法是這樣的將請求添加在請求隊列中看到第行整個的大致流程都通了吧,好了關于的源碼就講到這里。 1.Volley結構圖 showImg(https://segmentfault.com/img/remote/1460000011351317); 從上圖可以看到V...
閱讀 1648·2019-08-30 15:55
閱讀 973·2019-08-30 15:44
閱讀 866·2019-08-30 10:48
閱讀 2025·2019-08-29 13:42
閱讀 3179·2019-08-29 11:16
閱讀 1235·2019-08-29 11:09
閱讀 2053·2019-08-26 11:46
閱讀 611·2019-08-26 11:44