源碼學(xué)習(xí)|Volley 網(wǎng)絡(luò)請求緩存策略源碼分析

本文將基于Android N Framework層中的Volley庫,對Volley請求庫中的網(wǎng)絡(luò)緩存框架源碼進(jìn)行分析

在上面兩篇文章中,我們已經(jīng)對Volley的簡單使用和圖片加載的源碼進(jìn)行了簡單分析,在這篇文章中,我們將具體對Volley的網(wǎng)絡(luò)緩存源碼進(jìn)行分析吗货。

1. 使用HTTP緩存的作用

使用緩存其實(shí)主要有兩個(gè)原因隶糕。首先降低延遲,緩存離客戶端更近,因此闸英,從緩存請求內(nèi)容比從源服務(wù)器所用時(shí)間更少谆沃,呈現(xiàn)速度更快钝凶,網(wǎng)站就顯得更靈敏。其次降低網(wǎng)絡(luò)傳輸, 副本被重復(fù)使用唁影,大大降低了用戶的帶寬使用耕陷,其實(shí)也是一種變相的省錢(如果流量要付費(fèi)的話),同時(shí)保證了帶寬請求在一個(gè)低水平上据沈,更容易維護(hù)了哟沫。

2. Volley中的緩存實(shí)現(xiàn)

在前面文章中,我們已經(jīng)知道,Volley中RequestQueue是通過下面方法進(jìn)行構(gòu)建的

RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);

在初始化RequestQueue時(shí)會啟動兩個(gè)循環(huán)線程CacheDispatcher和NetworkDispatcher,它們會不斷的遍歷其耦合的隊(duì)列,執(zhí)行響應(yīng)操作。

/**
 * Starts the dispatchers in this queue.
 */
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();
    }
}

而對于每一個(gè)add()到RequestQueue中的Request對象,需要設(shè)置SequenceNumber(用來決定執(zhí)行順序),其中對于addMarker(Adds an event to this request's event log; for debugging.),注釋上說明是用來做調(diào)試用的,我們不用重點(diǎn)關(guān)注锌介。
對于不需要進(jìn)行緩存的請求,會直接加入到NetWorkQueue中,由NetWorkDispatcher進(jìn)行處理嗜诀。而對于需要緩存的請求,則先判斷waitingRequest隊(duì)列中是否存在,如果存在,說明已經(jīng)有相同任務(wù)通過CacheDispatcher進(jìn)行處理,則會加入到WaitingReqeust的Map中進(jìn)行排隊(duì),否則,則會加入到CacheDispatcher中進(jìn)行處理。

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

此處的緩存是WaitingRequests,其作用主要是對于短時(shí)間內(nèi)重復(fù)請求的Request進(jìn)行緩存,其結(jié)構(gòu)為HashMap孔祸。
我們在此處還要留意隆敢,就是對于WaitingRequests的處理,我們可以看到加入后等待隊(duì)列后崔慧,并無處理拂蝎,事實(shí)上,在已有請求Request Cancel時(shí)或者處于緩存尊浪,無需變化時(shí)匣屡,都會由Request調(diào)用finish()封救,進(jìn)而將所有等待隊(duì)列中的請求加入到緩存隊(duì)列中進(jìn)行遍歷執(zhí)行,最終返回緩存結(jié)果捣作。具體代碼參考如下所示:

/**
 * Called from {@link Request#finish(String)}, indicating that processing of the given request
 * has finished.
 *
 * <p>Releases waiting requests for <code>request.getCacheKey()</code> if
 *      <code>request.shouldCache()</code>.</p>
 */
<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);
            }
        }
    }
}

當(dāng)時(shí)看到這段的時(shí)候當(dāng)時(shí)挺疑惑誉结,為什么不直接返回一個(gè)緩存結(jié)果,將等待隊(duì)列的請求都拋棄掉券躁,后來想想也對惩坑,對于重復(fù)請求返回一個(gè)請求結(jié)果,這也不符合實(shí)際使用場景也拜。

3. CacheDispatcher的緩存策略:

通過上文中,我們可以看到,Volley會默認(rèn)為我們構(gòu)建一個(gè)DiskBasedCache用來緩存Request請求以舒。下面我們重點(diǎn)分析CacheDispatcher。
對于CacheQueue中的Request,如果Request已經(jīng)取消,則直接調(diào)用Request.finish()進(jìn)行處理,如果未命中緩存,則添加到NetworkQueue中,交由NetworkDispatcher進(jìn)行處理,如果entry過期,則重新設(shè)置entry并交由NetworkDispatcher進(jìn)行處理,如果緩存命中,則直接返回緩存內(nèi)容,由ResponseDelivery進(jìn)行轉(zhuǎn)發(fā),最后將結(jié)果回調(diào)回調(diào)用者慢哈。但是此處緩存命中也分了兩種情況,一種需要刷新緩存,一種不需要,如果需要刷新緩存,則需要將緩存結(jié)果先返回,然后在調(diào)用NetworkDispatcher進(jìn)行緩存刷新蔓钟。關(guān)鍵代碼如下所示:

    if (!entry.refreshNeeded()) {
        // Completely unexpired cache hit. Just deliver the response.
        mDelivery.postResponse(request, response);
    } else {
        // 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.
                }
            }
        });
    }

此處使用的mDelivery是ResponseDelivery接口的實(shí)現(xiàn)類ExecutorDelivery的對象,其主要作用是用來轉(zhuǎn)發(fā)請求結(jié)果與錯(cuò)誤信息卵贱。其在RequestQueue中傳入如下所示:

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

我們可以看到它會傳入一個(gè)主線程的Handler滥沫,傳入的Handler其實(shí)最終是通過ExecutorDelivery中的Executor來將所有消息以postRunnable()的方式返回到主線程中。但是這里還是會有一個(gè)疑問键俱,使用postResponse時(shí)兰绣,其傳入的Runnable依然會在主線程中運(yùn)行。為了保證主線程的優(yōu)先工作编振,為什么不把這個(gè)提到子線程去做呢缀辩?具體關(guān)鍵代碼如下:

  public ExecutorDelivery(final Handler handler) {
    // Make an Executor that just wraps the handler.
    mResponsePoster = new Executor() {
        @Override
        public void execute(Runnable command) {
            handler.post(command);
        }
    };
}


//ResponseDeliveryRunnable run方法中的傳入Runnable處理
// If we have been provided a post-delivery runnable, run it.
 if (mRunnable != null) {
   mRunnable.run();
 }

4. NetworkDispatcher的緩存策略:

NetworkDispatcher中只是簡單的執(zhí)行Request請求,將請求結(jié)果解析后踪央,將Response置入緩存中臀玄,此時(shí)的過期時(shí)間也是在此時(shí)進(jìn)行設(shè)置的。具體關(guān)鍵代碼如下:

 if (request.shouldCache() && response.cacheEntry != null) {
      mCache.put(request.getCacheKey(), response.cacheEntry);
      request.addMarker("network-cache-written");
 }

 // Post the response back.
request.markDelivered();
 mDelivery.postResponse(request, response);

最后補(bǔ)上一張自己喪心病狂畫的流程圖:


緩存操作流程圖

至此杯瞻,對于Volley的分析也就暫時(shí)告一段落了镐牺,如果分析中有什么錯(cuò)漏還請各位積極留言指導(dǎo),謝謝(∩_∩)哈哈~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末魁莉,一起剝皮案震驚了整個(gè)濱河市睬涧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌旗唁,老刑警劉巖畦浓,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異检疫,居然都是意外死亡讶请,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來夺溢,“玉大人论巍,你說我怎么就攤上這事》缦欤” “怎么了嘉汰?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長状勤。 經(jīng)常有香客問我鞋怀,道長,這世上最難降的妖魔是什么持搜? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任密似,我火速辦了婚禮,結(jié)果婚禮上葫盼,老公的妹妹穿的比我還像新娘残腌。我一直安慰自己,他們只是感情好剪返,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布废累。 她就那樣靜靜地躺著邓梅,像睡著了一般脱盲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上日缨,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天钱反,我揣著相機(jī)與錄音,去河邊找鬼匣距。 笑死面哥,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的毅待。 我是一名探鬼主播尚卫,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼尸红!你這毒婦竟也來了吱涉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤外里,失蹤者是張志新(化名)和其女友劉穎怎爵,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盅蝗,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鳖链,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了墩莫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芙委。...
    茶點(diǎn)故事閱讀 38,724評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡逞敷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出灌侣,到底是詐尸還是另有隱情兰粉,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布顶瞳,位于F島的核電站玖姑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏慨菱。R本人自食惡果不足惜焰络,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望符喝。 院中可真熱鬧闪彼,春花似錦、人聲如沸协饲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茉稠。三九已至描馅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間而线,已是汗流浹背铭污。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留膀篮,地道東北人嘹狞。 一個(gè)月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像誓竿,于是被迫代替她去往敵國和親磅网。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容