Android開發學習之路-Volley源碼解析

從簡單的StringRequest入手看看Volley的工做機制。html

先簡單說下Volley的用法:android

① 獲取一個RequestQueueapi

mRequestQueue = Volley.newRequestQueue(this);

② 構造一個StringRequest對象緩存

mStringRequest = new StringRequest(url, new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                mTextView.setText(response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.d(TAG, "onErrorResponse: " + error.getMessage());
            }
        });

③ 將StringRequest對象add進RequestQueue網絡

mRequestQueue.add(mStringRequest);

 

 


 下面經過源碼跟蹤一下Volley處理請求的過程:app

用過Volley都知道,請求通常是繼承自Request這個抽象類,那麼StringRequest天然也是。在構造方法中須要幾個參數,具體的構造方法以下框架

public StringRequest(int method, String url, Listener<String> listener,
            ErrorListener errorListener) {
        super(method, url, errorListener);
        mListener = listener;
    }

能夠看到咱們須要給它指定請求的方法、url、成功返回的回調類和錯誤的回調類。socket

接着咱們在須要請求的時候,把這個對象傳遞給RequestQueue的add方法。這個add方法是作什麼的咱們其實都能猜到,就是把這個請求放到隊列中ide

 1 public <T> Request<T> add(Request<T> request) {
 2         // Tag the request as belonging to this queue and add it to the set of current requests.
 3         request.setRequestQueue(this);
 4         synchronized (mCurrentRequests) {
 5             mCurrentRequests.add(request);
 6         }
 7 
 8         // Process requests in the order they are added.
 9         request.setSequence(getSequenceNumber());
10         request.addMarker("add-to-queue");
11 
12         // If the request is uncacheable, skip the cache queue and go straight to the network.
13         if (!request.shouldCache()) {
14             mNetworkQueue.add(request);
15             return request;
16         }
17 
18         // Insert request into stage if there's already a request with the same cache key in flight.
19         synchronized (mWaitingRequests) {
20             String cacheKey = request.getCacheKey();
21             if (mWaitingRequests.containsKey(cacheKey)) {
22                 // There is already a request in flight. Queue up.
23                 Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
24                 if (stagedRequests == null) {
25                     stagedRequests = new LinkedList<Request<?>>();
26                 }
27                 stagedRequests.add(request);
28                 mWaitingRequests.put(cacheKey, stagedRequests);
29                 if (VolleyLog.DEBUG) {
30                     VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
31                 }
32             } else {
33                 // Insert 'null' queue for this cacheKey, indicating there is now a request in
34                 // flight.
35                 mWaitingRequests.put(cacheKey, null);
36                 mCacheQueue.add(request);
37             }
38             return request;
39         }
40     }

代碼可能有點長,可是核心的地方也比較簡單,就是進行線程同步以後將請求放入請求的集合mNetworkQueue(在不須要緩存的狀況下)。若是須要緩存請求,則同步等待序列,判斷請求是否已經被髮出過,根據狀況返回請求。oop

由於這裏會根據是否須要緩存進行區別處理,下面按照不須要緩存來說解源碼。而StringRequest是會進行緩存的。

接着咱們看看RequestQueue是怎麼工做的。這裏其實咱們也是能夠知道,既然命名爲Queue,確定是會經過一個線程來不停的遍歷隊列中的等待者而後進行處理,跟Handler中的MessageQueue是很相似的。

咱們先看RequestQueue的構造,咱們通常經過下面這個方法來得到一個RequestQueue

mRequestQueue = Volley.newRequestQueue(this);

而實際上這個構造的方法內容是下面這樣的

 1 public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
 2         File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
 3 
 4         String userAgent = "volley/0";
 5         try {
 6             String packageName = context.getPackageName();
 7             PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
 8             userAgent = packageName + "/" + info.versionCode;
 9         } catch (NameNotFoundException e) {
10         }
11 
12         if (stack == null) {
13             if (Build.VERSION.SDK_INT >= 9) {
14                 stack = new HurlStack();
15             } else {
16                 // Prior to Gingerbread, HttpUrlConnection was unreliable.
17                 // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
18                 stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
19             }
20         }
21 
22         Network network = new BasicNetwork(stack);
23 
24         RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
25         queue.start();
26 
27         return queue;
28     }

從註釋能夠看到,在api等級大於9的時候,使用HttpUrlConnection來進行主要的網絡請求工做,到這裏已經很明顯了,Volley底層是使用HttpUrlConnection進行的。咱們看到這裏是使用了一個HttpClientStack來包裝根據userAgent獲得的HttpClient(這個類在最新的源碼中已經被移除了),而HttpClient實際上就是使用HttpUrlConnection來實現的。最後被包裝爲一個BasicNetwork對象。

接着根據獲得的BasicNetwork對象和一個DiskBasedCache對象(磁盤緩存)來構造一個RequestQueue,而且調用了它的start方法來啓動這個線程。

再看看RequestQueue的start方法:

 1 public void start() {
 2         stop();  // Make sure any currently running dispatchers are stopped.
 3         // Create the cache dispatcher and start it.
 4         mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
 5         mCacheDispatcher.start();
 6 
 7         // Create network dispatchers (and corresponding threads) up to the pool size.
 8         for (int i = 0; i < mDispatchers.length; i++) {
 9             NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
10                     mCache, mDelivery);
11             mDispatchers[i] = networkDispatcher;
12             networkDispatcher.start();
13         }
14     }

這裏咱們先不顧這個mCacheDispatcher,直接看到下面的for循環,這個for循環遍歷了mDispatchers,這個mDispatcher其實至關於一個線程池,這個線程池的大小默認是4。而後分別讓這裏面的線程運行起來(調用了它們的start方法)。這裏爲何要有多個線程來處理呢?緣由很簡單,由於咱們每個請求都不必定會立刻處理完畢,多個線程進行同時處理的話效率會提升。

咱們進入NetworkDispatcher看看它的run方法:

 1 @Override
 2     public void run() {
 3         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
 4         while (true) {
 5             long startTimeMs = SystemClock.elapsedRealtime();
 6             Request<?> request;
 7             try {
 8                 // Take a request from the queue.
 9                 request = mQueue.take();
10             } catch (InterruptedException e) {
11                 // We may have been interrupted because it was time to quit.
12                 if (mQuit) {
13                     return;
14                 }
15                 continue;
16             }
17 
18             try {
19                 request.addMarker("network-queue-take");
20 
21                 // If the request was cancelled already, do not perform the
22                 // network request.
23                 if (request.isCanceled()) {
24                     request.finish("network-discard-cancelled");
25                     continue;
26                 }
27 
28                 addTrafficStatsTag(request);
29 
30                 // Perform the network request.
31                 NetworkResponse networkResponse = mNetwork.performRequest(request);
32                 request.addMarker("network-http-complete");
33 
34                 // If the server returned 304 AND we delivered a response already,
35                 // we're done -- don't deliver a second identical response.
36                 if (networkResponse.notModified && request.hasHadResponseDelivered()) {
37                     request.finish("not-modified");
38                     continue;
39                 }
40 
41                 // Parse the response here on the worker thread.
42                 Response<?> response = request.parseNetworkResponse(networkResponse);
43                 request.addMarker("network-parse-complete");
44 
45                 // Write to cache if applicable.
46                 // TODO: Only update cache metadata instead of entire record for 304s.
47                 if (request.shouldCache() && response.cacheEntry != null) {
48                     mCache.put(request.getCacheKey(), response.cacheEntry);
49                     request.addMarker("network-cache-written");
50                 }
51 
52                 // Post the response back.
53                 request.markDelivered();
54                 mDelivery.postResponse(request, response);
55             } catch (VolleyError volleyError) {
56                 volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
57                 parseAndDeliverNetworkError(request, volleyError);
58             } catch (Exception e) {
59                 VolleyLog.e(e, "Unhandled exception %s", e.toString());
60                 VolleyError volleyError = new VolleyError(e);
61                 volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
62                 mDelivery.postError(request, volleyError);
63             }
64         }
65     }

第三行設置了這些線程的優先級,這個優先級比較低,目的是爲了儘可能減小對UI線程的影響保證流暢度。

接着第9行,調用mQueue的take方法取出隊列頭的一個請求進行處理,這個mQueue是什麼?其實它就是咱們在上面add方法中添加進去的一個請求。

咱們不看這些設置狀態標記的地方,直接看到第31行,若是請求沒有被取消,也就是正常的狀況下,咱們會調用mNetwork的performRequest方法進行請求的處理。不知道你還記的這個mNetwork不,它其實就是咱們上面提到的那個由HttpUrlConnection層層包裝的網絡請求對象。

若是請求獲得告終果,咱們會看到54行調用了mDelivery的postResponose方法來回傳咱們的請求結果。

 

這裏還有兩個重要的地方須要再瞭解一下,一個是究竟postResponse是怎麼傳回咱們的請求結果的,另外一個就是performRequest是怎麼去進行網絡請求的。

先看第一個,結果的回傳。咱們先了解下這個mDelivery是怎麼定義的。它實際上是在RequestQueue中建立的,能夠看到RequestQueue的其中一個構造方法:

1 public RequestQueue(Cache cache, Network network, int threadPoolSize) {
2         this(cache, network, threadPoolSize,
3                 new ExecutorDelivery(new Handler(Looper.getMainLooper())));
4     }

 這裏直接就new了一個ExecutorDelivery對象,並傳入了一個不斷從MainLooper中獲取Message的Handler。再看看postResponse方法的內容:

1 @Override
2     public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
3         request.markDelivered();
4         request.addMarker("post-response");
5         mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
6     }

 這裏看到第5行調用了mResponsePoster的execute方法並傳入了一個ResponseDeliveryRunnable對象,再看mResponsePoster的定義:

1 public ExecutorDelivery(final Handler handler) {
2         // Make an Executor that just wraps the handler.
3         mResponsePoster = new Executor() {
4             @Override
5             public void execute(Runnable command) {
6                 handler.post(command);
7             }
8         };
9     }

 也就是咱們在這裏把ResponseDeliveryRunnable對象經過Handler的post方法發送出去了。這裏爲何要發送到MainLooper中?由於RequestQueue是在子線程中執行的,回調到的代碼也是在子線程中的,若是在回調中修改UI,就會報錯。再者,爲何要使用post方法?緣由也很簡單,由於咱們在消息發出以後再進行回調,post方法容許咱們傳入一個Runnable的實現了,post成功會自動執行它的run方法,這個時候在run方法中進行結果的判斷而且進行回調:

 1 private class ResponseDeliveryRunnable implements Runnable {
 2         private final Request mRequest;
 3         private final Response mResponse;
 4         private final Runnable mRunnable;
 5 
 6         public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
 7             mRequest = request;
 8             mResponse = response;
 9             mRunnable = runnable;
10         }
11 
12         @SuppressWarnings("unchecked")
13         @Override
14         public void run() {
15             // If this request has canceled, finish it and don't deliver.
16             if (mRequest.isCanceled()) {
17                 mRequest.finish("canceled-at-delivery");
18                 return;
19             }
20 
21             // Deliver a normal response or error, depending.
22             if (mResponse.isSuccess()) {
23                 mRequest.deliverResponse(mResponse.result);
24             } else {
25                 mRequest.deliverError(mResponse.error);
26             }
27 
28             // If this is an intermediate response, add a marker, otherwise we're done
29             // and the request can be finished.
30             if (mResponse.intermediate) {
31                 mRequest.addMarker("intermediate-response");
32             } else {
33                 mRequest.finish("done");
34             }
35 
36             // If we have been provided a post-delivery runnable, run it.
37             if (mRunnable != null) {
38                 mRunnable.run();
39             }
40        }
41     }

 能夠看到,23行是調用Request的deleverResponse方法將結果回調給StringRequest。接着看看StringRequest中該方法是實現:

1 @Override
2     protected void deliverResponse(String response) {
3         mListener.onResponse(response);
4     }

 直接經過咱們構造StringRequest時傳進來的Listener的回調方法onResponse來將結果回調給Activity。deleverError也是一樣的作法。

 

再來看看performRequest是怎麼進行網絡請求的。

mNetwork是Network接口的對象,而這個接口只有一個實現類,就是BasicNetwork,咱們看看這個BasicNetwork中的performRequest的代碼:

 1 @Override
 2     public NetworkResponse performRequest(Request<?> request) throws VolleyError {
 3         long requestStart = SystemClock.elapsedRealtime();
 4         while (true) {
 5             HttpResponse httpResponse = null;
 6             byte[] responseContents = null;
 7             Map<String, String> responseHeaders = Collections.emptyMap();
 8             try {
 9                 // Gather headers.
10                 Map<String, String> headers = new HashMap<String, String>();
11                 addCacheHeaders(headers, request.getCacheEntry());
12                 httpResponse = mHttpStack.performRequest(request, headers);
13                 StatusLine statusLine = httpResponse.getStatusLine();
14                 int statusCode = statusLine.getStatusCode();
15 
16                 responseHeaders = convertHeaders(httpResponse.getAllHeaders());
17                 // Handle cache validation.
18                 if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
19 
20                     Entry entry = request.getCacheEntry();
21                     if (entry == null) {
22                         return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
23                                 responseHeaders, true,
24                                 SystemClock.elapsedRealtime() - requestStart);
25                     }
26 
27                     // A HTTP 304 response does not have all header fields. We
28                     // have to use the header fields from the cache entry plus
29                     // the new ones from the response.
30                     // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
31                     entry.responseHeaders.putAll(responseHeaders);
32                     return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
33                             entry.responseHeaders, true,
34                             SystemClock.elapsedRealtime() - requestStart);
35                 }
36 
37                 // Some responses such as 204s do not have content.  We must check.
38                 if (httpResponse.getEntity() != null) {
39                   responseContents = entityToBytes(httpResponse.getEntity());
40                 } else {
41                   // Add 0 byte response as a way of honestly representing a
42                   // no-content request.
43                   responseContents = new byte[0];
44                 }
45 
46                 // if the request is slow, log it.
47                 long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
48                 logSlowRequests(requestLifetime, request, responseContents, statusLine);
49 
50                 if (statusCode < 200 || statusCode > 299) {
51                     throw new IOException();
52                 }
53                 return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
54                         SystemClock.elapsedRealtime() - requestStart);
55             } catch (SocketTimeoutException e) {
56                 attemptRetryOnException("socket", request, new TimeoutError());
57             } catch (ConnectTimeoutException e) {
58                 attemptRetryOnException("connection", request, new TimeoutError());
59             } catch (MalformedURLException e) {
60                 throw new RuntimeException("Bad URL " + request.getUrl(), e);
61             } catch (IOException e) {
62                 int statusCode = 0;
63                 NetworkResponse networkResponse = null;
64                 if (httpResponse != null) {
65                     statusCode = httpResponse.getStatusLine().getStatusCode();
66                 } else {
67                     throw new NoConnectionError(e);
68                 }
69                 VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
70                 if (responseContents != null) {
71                     networkResponse = new NetworkResponse(statusCode, responseContents,
72                             responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
73                     if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
74                             statusCode == HttpStatus.SC_FORBIDDEN) {
75                         attemptRetryOnException("auth",
76                                 request, new AuthFailureError(networkResponse));
77                     } else {
78                         // TODO: Only throw ServerError for 5xx status codes.
79                         throw new ServerError(networkResponse);
80                     }
81                 } else {
82                     throw new NetworkError(networkResponse);
83                 }
84             }
85         }

 這段代碼中,先10和11行代碼將cache的屬性設置給header,接着第9行調用mHttpStack對象的performRequest方法並傳入請求對象和頭部來進行請求,獲得一個HttpResponse對象。

接着將HttpResponse對象中的狀態嗎取出,若是值爲HttpStatus.SC_NOT_MODIFIED(也就是304),則表示請求獲得的Response沒有變化,直接顯示緩存內容。

第39行表示請求成功而且獲取到請求內容,將內容取出並做爲一個NetworkResponse對象的屬性並返回給NetworkDispatcher,接着將其轉,接着就是上面介紹的回調給主線程了。

用過HttpClient的都知道,其實這裏獲得的HttpResponse就是由HttpClient返回的,咱們直接看第12行調用的performRequest的源碼:

 1 @Override
 2     public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
 3             throws IOException, AuthFailureError {
 4         HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);
 5         addHeaders(httpRequest, additionalHeaders);
 6         addHeaders(httpRequest, request.getHeaders());
 7         onPrepareRequest(httpRequest);
 8         HttpParams httpParams = httpRequest.getParams();
 9         int timeoutMs = request.getTimeoutMs();
10         // TODO: Reevaluate this connection timeout based on more wide-scale
11         // data collection and possibly different for wifi vs. 3G.
12         HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
13         HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);
14         return mClient.execute(httpRequest);
15     }

 這裏14行的mClient其實就是一個HttpClient對象。

 

說到這裏,是否是感受很混亂呢?這裏我作了一個圖,能夠參考看看

咱們這裏簡單再解析下:

① 在RequestQueue建立的時候,會生成多個NetwrokDispatcher,接着這些NetwrokDispatcher會不斷的從請求隊列中讀取請求,若是有就使用包裝好的請求類來執行performRequest,接着將結果經過postResponse方法傳包裝好並經過post方法發送到MainLooper。

② 在MainLooper中,判斷Response是否有內容,經過deliverResponse將結果回調給RequestQueue,RequestQueue經過咱們構造時傳入的Listener中的回調方法對結果進行回調。

③ 在咱們須要請求的時候,構建一個StringRequest(設置好對應的回調接口和實現回調方法)並將其add到MessageQueue中便可自動完成請求。

 

咱們應該掌握什麼?

① 子線程中應該如何將結果回調給主線程

② 若是本身要設計一個相似的框架,知道如何進行設計保證低耦合和便於維護

③ 經過學習其餘的Request子類定義咱們本身的請求類

④ 閱讀源代碼的技巧,好比查看直接子類,方法和參數定義查看等等

相關文章
相關標籤/搜索