Volley源碼分析一

来源:http://www.cnblogs.com/qifengshi/archive/2017/06/22/7063673.html
-Advertisement-
Play Games

Volley源碼分析 雖然在2017年,volley已經是一個逐漸被淘汰的框架,但其代碼短小精悍,網路架構設計巧妙,還是有很多值得學習的地方。 第一篇文章,分析了請求隊列的代碼,請求隊列也是我們使用Volley的關鍵一步。 第二篇文章會分析Dispatcher RequestQueue 創建Requ ...


Volley源碼分析

雖然在2017年,volley已經是一個逐漸被淘汰的框架,但其代碼短小精悍,網路架構設計巧妙,還是有很多值得學習的地方。
第一篇文章,分析了請求隊列的代碼,請求隊列也是我們使用Volley的關鍵一步。
第二篇文章會分析Dispatcher

RequestQueue

創建RequestQueue對象的方式是採用如下的代碼:

RequestQueue queue = Volley.newRequestQueue(getApplicationContext());

該隊列是用來發起Http請求。主要看newRequestQueue方法
該方法的核心實現是2個參數的方法

newRequestQueue(Context context, HttpStack stack)

該方法做的事情如下:

  1. 創建一個CacheDir目錄
  2. 創建一個userAgent,預設是volly/0,實際是包名 + 版本代碼。如果出異常,userAgent就是預設值
  3. 初始化stack,如果是Android2.3一下 就用HttpClientStack創建對象,HttpClientStack是用HttpClient實現的。如果是Android2.3以上,就用HurlStack創建對象。後面我們在分析這個類的作用。
  4. 用創建好的stack去初始化NetWork對象,即 Network network = new BasicNetwork(stack); 用創建好的CacheDir去初始化DiskBasedCache對象,從而完成RequestQueue對象的初始化,RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
  5. 調用queue對象的start方法。

下麵,我們看一下HttpStack的作用,以及DiskBasedCache和NetWork的作用,最後看一下start方法做了什麼。

HttpStack是一個介面,該介面只有兩個實現類,一個是HttpClientStack,另一個是HulStack。我們先不看具體的實現類,只看介面方法的聲明。

 HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
        throws IOException, AuthFailureError;

該方法的作用是用來真正進行網路請求的,具體用那種http實現類去請求,就分成了HttpClient以及HttpUrl。

再看NetWork介面,該介面只有一個實現類,BasicNetWork,我們還是先不看具體的實現類,只看介面的方法聲明:

NetworkResponse performRequest(Request<?> request) throws VolleyError;

該方法的說明是執行一個指定的Request。

最後再看DiskBasedCache的功能。該類是Cache的一個具體的實現類,該類的功能是用緩存的文件存在硬碟中,預設的硬碟大小是5M。
上面的幾個介面的功能都說完了,通過其實現類的對象最終構造了RequestQueue。接下來我們看一下構造器方法的執行。

RequestQueue的成員變數以及方法

我們先看一下RequestQueue的成員變數都有什麼:

  • mSequenceGenerator 該類型為AtomicInteger,其功能是統計請求的個數,採用原子類的整形。
  • mWaitingRequests 該類型是一個HashMap,其功能是存儲request,key是cachekey。存儲的quest是重覆的request。
  • mCurrentRequests 該類型是一個HashSet,其功能是存儲request,該request能被放入的條件是當前正在被分派或者在等待。
  • mCacheQueue 該類型是PriorityBlockingQueue,其功能是存儲緩存的隊列。
  • mNetworkQueue 該類型是PriorityBlockingQueue,其功能是正在進行工作的隊列
  • DEFAULT_NETWORK_THREAD_POOL_SIZE 預設的分派器的線程 初始值為4
  • mCache 該類型是Cache 功能是存儲響應報文的對象response
  • mNetWork 該類型是NetWork 功能是進行網路請求。
  • mDelivery 該類型是ResponseDelivery,其功能是分派response
  • mDispatchers 該類型是NetworkDispatcher[] 其功能是NetWork分派器
  • mCacheDispatcher 該類型是CacheDispatcher[] 其功能是Cache分派器

下麵我們繼續看構造方法:

  public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

無論幾個參數的構造方法,最終都會執行這個。上面在 new RequestQueue中,我們的傳入的構造方法只有Cache和NetWork對象,這樣的話,threadPoolSize預設就是4,而delivery就是 new ExecutorDelivery(new Handler(Looper.getMainLooper()))
關於這個ExecutorDelivery這個後面在分析。

下麵,我們主要看RequestQueue的幾個關鍵方法:

  • start
  • stop
  • cancel
  • finsh
  • add

stop方法

    public void stop() {
        if (mCacheDispatcher != null) {
            mCacheDispatcher.quit();
        }
        for (final NetworkDispatcher mDispatcher : mDispatchers) {
            if (mDispatcher != null) {
                mDispatcher.quit();
            }
        }
    }

stop方法的全部代碼非常少,主要的做的事情如下:

  1. 如果CacheDispatcher不為空,則調用quit方法退出他。
  2. 如果NetWorkDispatcher不為空,則調用quit方法退出他。

關鍵就在於quit方法。

quit方法的代碼如下:

    public void quit() {
        mQuit = true;
        interrupt();
    }

CacheDispatcher是繼承與Thread。quit方法的作用是設置一個退出的標誌位,並且調用interrupt方法,這樣在run方法執行的過程中,由於線程已經中斷,會執行catch語句塊的內容,檢查標誌位,直接return。

下麵是去掉了與自身功能相關的代碼以後,剩下的部分。可以看出是很標準的線程退出寫法。

public void run() {
        //執行初始化的操作,省略掉
        while (true) {
            try {
                 //執行業務代碼,省略掉
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
            }
        }
    }

stop的方法分析完畢

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();
        }
    }

start方法比較簡潔,因此我在這裡放出全部的方法源碼。start方法主要做了以下幾件事情。

  1. 調用stop方法停止了正在運行的dispatchers。
  2. 創建CacheDispatcher,並且啟動它。
  3. 根據初始化的線程數量創建NetworkDispatcher,並啟動他。

stop方法上面已經分析了,就不在說了,先調用stop的目的就是先停止已經啟動的dispatcher。

創建CacheDispather,NetWorkDispatcher,他們其實就是一堆線程,調用他們的start方法。關於他們的分析,後面在具體介紹。

cancel方法

cancel方法實際上是cancalAll方法,但實際上最終都會調用cancelAll(RequestFilter filter) filter的作用實際上是過濾出要取消的request。然後調用request.cancel。
關於request類,這個放到後面的分析。

finish方法


<T> void finish(Request<T> request) {
        // Remove from the set of requests currently being processed.
        synchronized (mCurrentRequests) {
            mCurrentRequests.remove(request);
        }
        synchronized (mFinishedListeners) {
          for (RequestFinishedListener<T> listener : mFinishedListeners) {
            listener.onRequestFinished(request);
          }
        }

        if (request.shouldCache()) {
            synchronized (mWaitingRequests) {
                String cacheKey = request.getCacheKey();
                Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
                if (waitingRequests != null) {
                    if (VolleyLog.DEBUG) {
                        VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
                                waitingRequests.size(), cacheKey);
                    }
                    // Process all queued up requests. They won't be considered as in flight, but
                    // that's not a problem as the cache has been primed by 'request'.
                    mCacheQueue.addAll(waitingRequests);
                }
            }
        }
    }

finish方法是一個泛型方法,該finish方法執行了以下幾個事情:

  1. 從CurrentRequest中移除掉該request.
  2. 調用finishListener
  3. 判斷該request是否需要緩存,對需要緩存的request,將其他放到CacheQueue。

從上面的成員變數說明上,可以看出CurrentRequst實際上存儲正在分派或者執行的request,對於finish自然是從該隊列中移除。

     /** Callback interface for completed requests. */
    public interface RequestFinishedListener<T> {
        /** Called when a request has finished processing. */
        void onRequestFinished(Request<T> request);
    }

該介面的目的是提供一個request結束以後回調的介面。

判斷request是否需要緩存,可以通過setShouldCache設置,然後從mWaitingReuqests中移除該key對應的隊列,然後將隊列加入cacheQueue。也就說,如果waitingQueue中還存在一樣的request,則全部移除掉。

add方法

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);
        }

        // 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<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;
        }
    }

將request關聯到當前的requestQueue,然後mCurrentRequeue添加添加該request,request設置加入隊列的次序,判斷該request是否需要緩存,如果不需要緩存,就直接加入netWorkQueue中,等待被執行。
如果需要緩存,就先判斷該WaitingRequest中是否有該request,如果有就得到該cacheKey對應的隊列,把該request加進去,如果沒有,就創建一個stagedRequests,將它加進去。然後將stagedRequests放入WaitiingRequest中。如果沒有cacheKey,waitingRequest就插入一個key為cacheKey,value為null的值進入,然後將request加入cacheQueue。

至此,RequestQueue的部分就分析完了,其主要的功能是根據傳入的Request來決定將該Request加入到那個Queue中,然後在通過Dispatcher進行調度。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 文章轉自: "直播協議的選擇:RTMP vs. HLS" 前言 隨著直播業務的興起,越來越多的直播平臺開始涌現,這火熱的程度好像一個應用不帶上直播業務出來都不好意思跟人打招呼。想要做一個直播業務,主要包括三個部分:採集推流端、流媒體服務端、播放端。這裡不多說,就主要結合 iOS 平臺,從觀看端出發, ...
  • 以前的左右滑動效果採用自定義scrollview或者linearlayout來實現,recyclerview可以很好的做這個功能,一般的需求就是要麼一個獨立的左右滑動效果,要麼在一個列表裡的中間部分一個左右滑動效果 而列表裡面也容易,只是需要解決一點小問題,個人認為值得一提的就是高度問題,一般的人採 ...
  • 前言:一段時間沒接觸,很容易就忘記以前的知識。專寫一篇,供幾個月沒接觸,拿起卻忘記了。 0、巨集定義、系統相關 0-1)、巨集定義 0-2)、系統相關 1、View 1-1)View的屬性 1-2)View的layer 1-3)View的方法 2、UILabel 2-1)UILabel的屬性 3、UIB ...
  • 用來記錄自己所用到的知識 前兩天在做項目的時候發現有時候在訪問網路數據的時候由於後臺要做的工作較多,給我們返回數據的時間較長,所以老大叫我加了一個載入中的logo圖用來提高用戶體驗. 於是就在網上找了許多大神寫的案例,再結合自己的情況完成了一個Loading工具類 效果: ok,現在來說說怎麼做的 ...
  • 文章相對來說比較複雜,特別是查找版本ID對應的步驟,這裡推薦使用另一種方案,操作起來更簡單。 本文介紹如何使用Workflow及Fiddler下載IOS舊版本APP應用。 ...
  • 關於 iOS 與 OS X 端字體的優化(橫豎屏會出現字體加粗不一致等) iOS 瀏覽器橫屏時會重置字體大小,設置 text-size-adjust 為 none 可以解決 iOS 上的問題,但桌面版 Safari 的字體縮放功能會失效,因此最佳方案是將 text-size-adjust 為 10... ...
  • 1。手機充電充一整晚的 capacity 和 充電充到 100% 立刻停止的 capacity 是不一樣的, 為什麼不一樣呢? 手機充電到 100% 立刻停止, 因為化學特性尚未穩定, 電池電壓 會慢慢 往下掉, 未達到 額定的 capacity, 若是充一整晚呢? 在沒有 power path 的 ...
  • 碰見一個很奇葩的問題, 某些手機在設置了不知什麼後, 某些 APP 死活 HTTPS 請求失敗, 例如以 UMeng 統計HTTP 請求失敗為例, Log如下: UMLOG: (Error Applog) Error Domain=NSURLErrorDomain Code=-1202 "此伺服器的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...