天天看點

OkHttp 3.7源碼分析(四)——緩存政策

okhttp3.7源碼分析文章清單如下:

<a href="https://yq.aliyun.com/articles/78105?spm=5176.8091938.0.0.hleond">okhttp源碼分析——整體架構</a>

<a href="https://yq.aliyun.com/articles/78104?spm=5176.8091938.0.0.hleond">okhttp源碼分析——攔截器</a>

<a href="https://yq.aliyun.com/articles/78103?spm=5176.8091938.0.0.hleond">okhttp源碼分析——任務隊列</a>

<a href="https://yq.aliyun.com/articles/78102?spm=5176.8091938.0.0.hleond">okhttp源碼分析——緩存政策</a>

<a href="https://yq.aliyun.com/articles/78101?spm=5176.8091938.0.0.hleond">okhttp源碼分析——多路複用</a>

合理地利用本地緩存可以有效地減少網絡開銷,減少響應延遲。http報頭也定義了很多與緩存有關的域來控制緩存。今天就來講講okhttp中關于緩存部分的實作細節。

首先來了解下http協定中緩存部分的相關域。

逾時時間,一般用在伺服器的response報頭中用于告知用戶端對應資源的過期時間。當用戶端需要再次請求相同資源時先比較其過期時間,如果尚未超過過期時間則直接傳回緩存結果,如果已經超過則重新請求。

相對值,機關時秒,表示目前資源的有效期。<code>cache-control</code>比<code>expires</code>優先級更高:

用戶端第一次請求時,伺服器傳回:

當用戶端二次請求時,可以頭部加上如下header:

如果目前資源沒有被二次修改,伺服器傳回304告知用戶端直接複用本地緩存。

etag是對資源檔案的一種摘要,可以通過etag值來判斷檔案是否有修改。當用戶端第一次請求某資源時,伺服器傳回:

用戶端再次請求時,可在頭部加上如下域:

如果檔案并未改變,則伺服器傳回304告知用戶端可以複用本地緩存。

不使用緩存

隻使用緩存

okhttp的緩存工作都是在<code>cacheinterceptor</code>中完成的,cache部分有如下幾個關鍵類:

cache:cache管理器,其内部包含一個disklrucache将cache寫入檔案系統:

cache内部通過<code>requestcount</code>,<code>networkcount</code>,<code>hitcount</code>三個統計名額來優化緩存效率

cachestrategy:緩存政策。其内部維護一個request和response,通過指定request和response來描述是通過網絡還是緩存擷取response,抑或二者同時使用

cachestrategy$factory:緩存政策工廠類根據實際請求傳回對應的緩存政策

既然實際的緩存工作都是在<code>cacheinterceptor</code>中完成的,那麼接下來看下<code>cahceinterceptor</code>的核心方法<code>intercept</code>方法源碼:

核心邏輯都以中文注釋的形式在代碼中标注出來了,大家看代碼即可。通過上面的代碼可以看出,幾乎所有的動作都是以cachestrategy緩存政策為依據做出的,那麼接下來看下緩存政策是如何生成的,相關代碼實作在<code>cachestrategy$factory.get()</code>方法中:

可以看到其核心邏輯在getcandidate函數中。基本就是http緩存協定的實作,核心代碼邏輯已認證中文注釋說明,大家直接看代碼就好。

cache内部通過disklrucache管理cache在檔案系統層面的建立,讀取,清理等等工作,接下來看下disklrucache的主要邏輯:

disklrucache内部日志檔案,對cache的每一次讀寫都對應一條日志記錄,disklrucache通過分析日志分析和建立cache。日志檔案格式如下:

日志檔案的應用場景主要有四個:

diskcachelru初始化時通過讀取日志檔案建立cache容器:lruentries。同時通過日志過濾操作不成功的cache項。相關邏輯在disklrucache.readjournalline,disklrucache.processjournal

初始化完成後,為避免日志檔案不斷膨脹,對日志進行重建精簡,具體邏輯在disklrucache.rebuildjournal

每當有cache操作時将其記錄入日志檔案中以備下次初始化時使用

當備援日志過多時,通過調用cleanuprunnable線程重建日志

每一個disklrucache.entry對應一個cache記錄:

一個entry主要由以下幾部分構成:

key:每個cache都有一個key作為其辨別符。目前cache的key為其對應url的md5字元串

cleanfiles/dirtyfiles:每一個entry對應多個檔案,其對應的檔案數由disklrucache.valuecount指定。目前在okhttp中valuecount為2。即每個cache對應2個cleanfiles,2個dirtyfiles。其中第一個cleanfiles/dirtyfiles記錄cache的meta資料(如url,建立時間,ssl握手記錄等等),第二個檔案記錄cache的真正内容。cleanfiles記錄處于穩定狀态的cache結果,dirtyfiles記錄處于建立或更新狀态的cache

currenteditor:entry編輯器,對entry的所有操作都是通過其編輯器完成。編輯器内部添加了同步鎖

清理線程,用于重建精簡日志:

其觸發條件在journalrebuildrequired()方法中:

當備援日志超過日志檔案本身的一般且總條數超過2000時執行

cache快照,記錄了特定cache在某一個特定時刻的内容。每次向disklrucache請求時傳回的都是目标cache的一個快照,相關邏輯在disklrucache.get中:

管理cache entry的容器,其資料結構是linkedhashmap。通過linkedhashmap本身的實作邏輯達到cache的lru替換

使用okio對file的封裝,簡化了i/o操作。

disklrucache可以看成是cache在檔案系統層的具體實作,是以其基本操作接口存在一一對應的關系:

cache.get() —&gt;disklrucache.get()

cache.put()—&gt;disklrucache.edit() //cache插入

cache.remove()—&gt;disklrucache.remove()

cache.update()—&gt;disklrucache.edit()//cache更新

其中get操作在3.4已經介紹了,remove操作較為簡單,put和update大緻邏輯相似,因為篇幅限制,這裡僅介紹cache.put操作的邏輯,其他的操作大家看代碼就好:

可以看到核心邏輯在<code>editor = cache.edit(key(response.request().url()));</code>,相關代碼在disklrucache.edit:

edit方法傳回對應cacheentry的editor編輯器。接下來再來看下<code>cache.put()</code>方法的<code>entry.writeto(editor);</code>,其相關邏輯:

其主要邏輯就是将對應請求的meta資料寫入對應cacheentry的索引為entry_metadata(0)的dirtyfile中。

最後再來看<code>cache.put()</code>方法的<code>return new cacherequestimpl(editor);</code>:

其中<code>close</code>,<code>abort</code>方法會調用<code>editor.abort</code>和<code>editor.commit</code>來更新日志,<code>editor.commit</code>還會将dirtyfile重置為cleanfile作為穩定可用的緩存,相關邏輯在<code>okhttp3.internal.cache.disklrucache$editor.completeedit</code>中:

cacherequestimpl實作cacherequest接口,向外部類(主要是cacheinterceptor)透出,外部對象通過cacherequestimpl更新或寫入緩存資料。

總結起來disklrucache主要有以下幾個特點:

通過linkedhashmap實作lru替換

通過本地維護cache記錄檔保證cache原子性與可用性,同時為防止日志過分膨脹定時執行日志精簡

每一個cache項對應兩個狀态副本:dirty,clean。clean表示目前可用狀态cache,外部通路到的cache快照均為clean狀态;dirty為更新态cache。由于更新和建立都隻操作dirty狀态副本,實作了cache的讀寫分離

每一個cache項有四個檔案,兩個狀态(dirty,clean),每個狀态對應兩個檔案:一個檔案存儲cache meta資料,一個檔案存儲cache内容資料