Android Volley框架使用源碼分享
過去在Android上網(wǎng)絡(luò)通信都是使用的Xutils 因為用它可以順道處理了圖片和網(wǎng)絡(luò)這兩個方面,后來發(fā)覺Xutils里面使用的是HttpClient 而Google在6.0的版本上已經(jīng)把HttpClient廢除了,所以開始尋找新的網(wǎng)絡(luò)框架,okhttp也用過,但是它是在作用在UI線程,使用起來還需要用handler 所以就先用著Volley框架了。 這里我先分析下Volley框架的簡單網(wǎng)絡(luò)請求的源碼。
使用Volley請求網(wǎng)絡(luò)數(shù)據(jù)的簡單過程:
RequestQueue queue = Volley.newRequestQueue(this); //實例化一個請求隊列 Google推薦寫一個單例類 獲取唯一一個隊列 StringRequest request = new StringRequest(Request.Method.POST, url1, new Response.Listener<String>() { @Override public void onResponse(String response) { Toast.makeText(MainActivity.this, "success"+response, Toast.LENGTH_SHORT).show(); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Toast.makeText(MainActivity.this, "失敗了"+error.getMessage(), Toast.LENGTH_SHORT).show(); } }){ @Override protected Map<String, String> getParams() throws AuthFailureError { //重寫這個函數(shù)提交參數(shù) 也可以重寫一個Request實現(xiàn)這個方法 Map<String,String> params = new HashMap<>(); params.put(aaa+"name","1233555"); //參數(shù) return params; } }; queue.add(request);
請求的處理在newRequestQueue的時候就開始執(zhí)行了 只不過那時候請求隊列中還沒有請求 所以阻塞了 當(dāng) add的方法執(zhí)行時 才開始真正請求網(wǎng)絡(luò)
所以我們先來看 queue.add(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); //在當(dāng)前隊列中加入 } // Process requests in the order they are added. request.setSequence(getSequenceNumber()); request.addMarker("add-to-queue"); //設(shè)置標(biāo)志 // If the request is uncacheable, skip the cache queue and go straight to the network. if (!request.shouldCache()) { //根據(jù)是否需要緩存 如果不需要緩存 就直接加入網(wǎng)絡(luò)任務(wù)隊列中 然后返回 如果需要緩存 那么在下面代碼中加入緩存隊列 默認(rèn)是需要緩存的 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)) { //判斷當(dāng)前正在被處理并可以緩存的請求中是否包含該請求的key 如果包含說明已經(jīng)有一個相同的請求 那么就加入到其中 // 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); } return request; } }
分析add方法 首先加入到mCurrentRequests集合中 這個集合存放所有這個隊列所處理的請求 然后判斷這個請求是否需要緩存,如果不需要緩存,那么直接加入mNetworkQueue隊列中等待處理即可,如果需要那么最終加入到mCacheQueue隊列中,因為RequestQueue在處理請求時總會先處理緩存的任務(wù),在處理緩存時如果第一次處理沒有緩存還是會加入mNetworkQueue隊列中處理,如果有緩存那么就直接獲取緩存了,之后判斷當(dāng)前的請求中是否有相同的請求,如果有的話那么就把這個請求加入到暫存集合中,如果沒有那么就加入一個空的到請求到暫存隊列中,用來以后判斷是否有和這個請求相同的請求,然后加入緩存隊列中即可。
然后我們來看RequstQueue的創(chuàng)建過程
public static RequestQueue newRequestQueue(Context context, HttpStack stack) { File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); //創(chuàng)建一個文件用于緩存 String userAgent = "volley/0"; //用戶代理初始化 try { String packageName = context.getPackageName(); PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); userAgent = packageName + "/" + info.versionCode; //用戶代理為app包名+版本號 } catch (NameNotFoundException e) { } if (stack == null) { //如果沒傳入HttpStack 那么采用下述默認(rèn)的 這里可以自行重寫擴(kuò)展HttpStack 體現(xiàn)了該框架的高擴(kuò)展性 if (Build.VERSION.SDK_INT >= 9) { //如果sdk版本高于2.3 采用HurlStack 內(nèi)部是httpUrlConnection實現(xiàn) stack = new HurlStack(); } else { //如果版本低于2.3 采用httpClientStack // 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); //創(chuàng)建一個網(wǎng)絡(luò)工作 僅僅作用于請求網(wǎng)絡(luò) RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); //實例化一個請求隊列 傳入?yún)?shù) queue.start(); return queue; }
</pre><pre code_snippet_id="1680121" snippet_file_name="blog_20160512_5_2241745" name="code" class="java">public RequestQueue(Cache cache, Network network, int threadPoolSize) { //構(gòu)造函數(shù) 會創(chuàng)建默認(rèn)的ExecutorDelivery 用于回調(diào) this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper()))); }
RequestQueue的創(chuàng)建過程也比較簡單 根據(jù)sdk版本號判斷使用HttpURLConnection還是HttpClient 因為在2.3之前 httpUrlConnection有一個重大的bug 所以使用HttpClient代替,而httpUrlConnection體積小 支持gzip壓縮和緩存,并且速度相對httpClient快 并逐漸優(yōu)化 所以選擇httpUrlConnection 之后根據(jù)創(chuàng)建的NetWork 創(chuàng)建RequestQueue隊列 然后開啟即可
之后我們查看 queue的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); //創(chuàng)建一個緩存調(diào)度器 是一個線程 start后執(zhí)行run方法 mCacheDispatcher.start(); // Create network dispatchers (and corresponding threads) up to the pool size. for (int i = 0; i < mDispatchers.length; i++) { //默認(rèn)會有4個NetworkDispatcher 為了提高效率 執(zhí)行netWorkQueue里的request NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery); mDispatchers[i] = networkDispatcher; networkDispatcher.start(); } }
這個方法 先執(zhí)行緩存調(diào)度器線程然后執(zhí)行4個網(wǎng)絡(luò)工作調(diào)度器線程,因為在緩存調(diào)度器中 會判斷是否緩存過,如果緩存過并且沒過期,就直接復(fù)用緩存的,不把任務(wù)加入netWordQueue中 所以下面的NetWork調(diào)度器線程就會取不到請求而阻塞,不會執(zhí)行,而如果沒有緩存,緩存調(diào)度器線程中就會把請求加入NetWork隊列中,下面的netWork調(diào)度器就會取到該請求并執(zhí)行了
我們仔細(xì)看一下CacheDispatcher線程的源碼:
run方法的代碼比較長 我們分開來看 先看第一部分:
@Override public void run() { if (DEBUG) VolleyLog.v("start new dispatcher"); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //設(shè)置線程的優(yōu)先級 值為10 // 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"); //添加一個標(biāo)記 // 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"); //添加緩存miss標(biāo)記 // Cache miss; send off to the network dispatcher. mNetworkQueue.put(request); //換區(qū)緩存失敗 添加到netWork中等待請求 continue; } // If it is completely expired, just send it to the network. if (entry.isExpired()) { //判斷緩存是否過期了 如果過期了 那么就添加到netWork中等待請求 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"); //執(zhí)行到了這里說明緩存沒有過期 并且可以使用 Response<?> response = request.parseNetworkResponse( //把讀取到的緩存內(nèi)容解析成Response對象 new NetworkResponse(entry.data, entry.responseHeaders)); request.addMarker("cache-hit-parsed"); //添加標(biāo)記 if (!entry.refreshNeeded()) { //如果緩存不需要刷新 直接調(diào)用 mDelivery.postResponse方法 在其中會回調(diào)request的listener接口 // Completely unexpired cache hit. Just deliver the response. mDelivery.postResponse(request, response); } else { //如果需要刷新 把請求加入mNetworkQueue中 等待請求 // 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; } } }
上面代碼的具體過程也很簡單 首先從緩存請求隊列取出一個請求,在緩存中看看有沒有該請求的緩存,如果沒有 那么 請求放入NetWork調(diào)度器中 等待調(diào)用 如果有 也分幾種情況 如果獲取到的是空,放入NetWOrk 如果過期 放入 NetWork 如果不需要刷新 就直接從緩存獲取響應(yīng)信息并解析 然后用mDelivery回調(diào)接口即可 如果需要刷新 放入NetWOrd隊列等待調(diào)用。。。
我們再來看看NetworkDispatcher 線程的代碼就可以了 類似于CacheDispatcher的代碼:
@Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //設(shè)置優(yōu)先級 10 while (true) { long startTimeMs = SystemClock.elapsedRealtime(); //獲取請求執(zhí)行開始時間 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í)行請求的函數(shù) 并返回響應(yī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) 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) { //如果需要緩存 那么把響應(yīng)的信息存入緩存中 mCache.put(request.getCacheKey(), response.cacheEntry); request.addMarker("network-cache-written"); } // Post the response back. request.markDelivered(); mDelivery.postResponse(request, response); //之后回調(diào)一些方法 } catch (VolleyError volleyError) { volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs); parseAndDeliverNetworkError(request, volleyError); //回調(diào)錯誤接口 } 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); //回調(diào)錯誤接口 } } }
NetworkDispatcher 線程的執(zhí)行過程 先從 networkDispatch中獲取一個請求 然后判斷 是否取消了 如果沒有 那么就執(zhí)行NetWOrk的performRequest方法 執(zhí)行http請求,這個函數(shù)內(nèi)部才是真正的請求數(shù)據(jù) ,請求后 根據(jù)設(shè)置的shouldCache標(biāo)志 判斷是否放入緩存中 之后回調(diào)一些接口方法 即可 這樣就完成了一個請求
最后我們看一看NetWork類mNetwork.performRequest(request)方法是如何提交請求的吧 代碼比較長 但是不難:
@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(); //初始化響應(yīng)頭為空 try { // Gather headers. Map<String, String> headers = new HashMap<String, String>(); //請求頭 addCacheHeaders(headers, request.getCacheEntry()); //根據(jù)緩存添加請求頭 httpResponse = mHttpStack.performRequest(request, headers); //調(diào)用HttpStack的方法請求網(wǎng)絡(luò) StatusLine statusLine = httpResponse.getStatusLine(); int statusCode = statusLine.getStatusCode(); responseHeaders = convertHeaders(httpResponse.getAllHeaders()); //獲取響應(yīng)頭 // Handle cache validation. if (statusCode == HttpStatus.SC_NOT_MODIFIED) { //如果為304 讀取的緩存 Entry entry = request.getCacheEntry(); //查看以前是否緩存過 if (entry == null) { //如果以前緩存的為空 那么 說明上次緩存的請求也為空 直接返回response 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); //如果不空 那么就添加頭 然后返回 數(shù)據(jù)了 return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data, entry.responseHeaders, true, SystemClock.elapsedRealtime() - requestStart); } // Some responses such as 204s do not have content. We must check. if (httpResponse.getEntity() != null) { //不是304的情況
responseContents = entityToBytes(httpResponse.getEntity()); //獲取響應(yīng)的內(nèi)容 下面返回響應(yīng)即可 } 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); } 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 { // TODO: Only throw ServerError for 5xx status codes. throw new ServerError(networkResponse); } } else { throw new NetworkError(networkResponse); } } }
然后看 HttpStack的 請求代碼:
@Override public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { String url = request.getUrl(); HashMap<String, String> map = new HashMap<String, String>(); map.putAll(request.getHeaders()); //添加請求頭 map.putAll(additionalHeaders); if (mUrlRewriter != null) { String rewritten = mUrlRewriter.rewriteUrl(url); if (rewritten == null) { throw new IOException("URL blocked by rewriter: " + url); } url = rewritten; } URL parsedUrl = new URL(url); HttpURLConnection connection = openConnection(parsedUrl, request); //打開連接 for (String headerName : map.keySet()) { //設(shè)置頭 connection.addRequestProperty(headerName, map.get(headerName)); } setConnectionParametersForRequest(connection, request); //在這個函數(shù)里添加請求的參數(shù) 和一些基本的信息配置 // Initialize HttpResponse with data from the HttpURLConnection. ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); int responseCode = connection.getResponseCode(); //下面就是些獲取響應(yīng)信息后的處理了 if (responseCode == -1) { // -1 is returned by getResponseCode() if the response code could not be retrieved. // Signal to the caller that something was wrong with the connection. throw new IOException("Could not retrieve response code from HttpUrlConnection."); } StatusLine responseStatus = new BasicStatusLine(protocolVersion, connection.getResponseCode(), connection.getResponseMessage()); BasicHttpResponse response = new BasicHttpResponse(responseStatus); response.setEntity(entityFromConnection(connection)); for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) { if (header.getKey() != null) { Header h = new BasicHeader(header.getKey(), header.getValue().get(0)); response.addHeader(h); } } return response; }
這個函數(shù)中主要是HttpUrlConnection的使用 添加頭在 connection.addRequestProperty方法中 添加參數(shù)需要獲取流 然后寫入?yún)?shù) 下面這個函數(shù)中有介紹 假設(shè)是post方式:
case Method.POST: connection.setRequestMethod("POST"); addBodyIfExists(connection, request); break;
private static void addBodyIfExists(HttpURLConnection connection, Request<?> request) throws IOException, AuthFailureError { byte[] body = request.getBody(); if (body != null) { connection.setDoOutput(true); connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType()); DataOutputStream out = new DataOutputStream(connection.getOutputStream()); out.write(body); out.close(); } }
將body寫入到流中 就可以 參數(shù)的封裝在 body中
public byte[] getBody() throws AuthFailureError { Map<String, String> params = getParams(); if (params != null && params.size() > 0) { return encodeParameters(params, getParamsEncoding()); } return null; } getParams方法 是Request需要重寫的一個方法 返回值就是參數(shù)的Map集合 [java] view plain copy 在CODE上查看代碼片派生到我的代碼片 private byte[] encodeParameters(Map<String, String> params, String paramsEncoding) { StringBuilder encodedParams = new StringBuilder(); try { for (Map.Entry<String, String> entry : params.entrySet()) { encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding)); encodedParams.append('='); encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding)); encodedParams.append('&'); } return encodedParams.toString().getBytes(paramsEncoding); } catch (UnsupportedEncodingException uee) { throw new RuntimeException("Encoding not supported: " + paramsEncoding, uee); } }
這個函數(shù)就是按照一定規(guī)則拼接字符串參數(shù)即可 然后 就可以提交參數(shù)了
最后介紹下這個框架主要的幾個類、成員及他們作用:
RequestQueue 用來處理請求的隊列,請求都放在這個類中 調(diào)用start方法 開始處理請求
mCache 請求的緩存,當(dāng)提交了一個請求 并且此請求需要緩存時,會放入這個緩存中
mNetwork 單純用于提交網(wǎng)絡(luò)請求的接口 只有一個提交請求的方法 需要傳入一個HttpStack來完成請求的提交
mDelivery 用于請求響應(yīng)后的 接口回調(diào)等功能
mDispatchers NetWork調(diào)度器線程數(shù)組 包含4個對象處理請求 目的是為了提高效率 當(dāng)沒有緩存可以獲取或者已經(jīng)過期 需要刷新時 會調(diào)用這個線程的run方法 如果沒有 則阻塞
mCacheDispatcher 緩存調(diào)度器線程 處理已經(jīng)緩存了的請求 如果沒有緩存 則將請求放入 NetWorkQueue 等待調(diào)用
以上就是本文的全部內(nèi)容,希望對大家學(xué)習(xí)Android Volley框架有所幫助。
相關(guān)文章
Android getSystemService用法實例總結(jié)
這篇文章主要介紹了Android getSystemService用法,結(jié)合實例形式總結(jié)分析了getSystemService獲取系統(tǒng)Service的相關(guān)使用方法與注意事項,需要的朋友可以參考下2016-01-01Android使用個推實現(xiàn)三方應(yīng)用的推送功能
這篇文章主要為大家詳細(xì)介紹了Android使用個推實現(xiàn)三方應(yīng)用的推送功能,感興趣的小伙伴們可以參考一下2016-08-08詳解如何從原生Android 跳轉(zhuǎn)到hbuilder項目
這篇文章主要介紹了從原生Android 跳轉(zhuǎn)到hbuilder項目,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08Android仿蘋果關(guān)機界面實現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了Android仿蘋果關(guān)機界面的實現(xiàn)代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-09-09Android 使用版本控制工具時添加忽略文件的方式(詳解)
下面小編就為大家?guī)硪黄狝ndroid 使用版本控制工具時添加忽略文件的方式(詳解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-01-01Android 基于RecyclerView實現(xiàn)的歌詞滾動自定義控件
這篇文章主要介紹了Android 基于RecyclerView實現(xiàn)的歌詞滾動自定義控件,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-03-03