女人久久久,最近更新中文字幕在线,成人国内精品久久久久影院vr,中文字幕亚洲综合久久综合,久久精品秘?一区二区三区美小说

原創(chuàng)生活

國(guó)內(nèi) 商業(yè) 滾動(dòng)

基金 金融 股票

期貨金融

科技 行業(yè) 房產(chǎn)

銀行 公司 消費(fèi)

生活滾動(dòng)

保險(xiǎn) 海外 觀察

財(cái)經(jīng) 生活 期貨

當(dāng)前位置:滾動(dòng) >

Retrofit 和OkHttp 中使用網(wǎng)絡(luò)緩存,提高訪問效率

文章來源:財(cái)金網(wǎng)  發(fā)布時(shí)間: 2019-04-23 10:53:32  責(zé)任編輯:cfenews.com
+|-

【原標(biāo)題:Retrofit 和OkHttp 中使用網(wǎng)絡(luò)緩存,提高訪問效率】財(cái)金網(wǎng)消息 OkHttp緩存原理

我們先從HTTP協(xié)議開始入手,關(guān)于緩存的HTTP請(qǐng)求/返回頭有以下幾個(gè),我列了張表格一一解釋.

請(qǐng)求頭/返回頭含義

Cache-Control這個(gè)字段用于指定所有緩存機(jī)制在整個(gè)請(qǐng)求/響應(yīng)鏈中

必須服從的指令。

Pragma與Cache-Control一樣,是兼容HTTP1.0的頭部

Expires資源過期時(shí)間

Last-Modified資源最后修改的時(shí)間

If-Modified-Since在請(qǐng)求頭中指定一個(gè)日期,若資源最后更新時(shí)間超過該日期,

則服務(wù)器接受請(qǐng)求,相反的頭為If-Unmodified-Since

ETag識(shí)別內(nèi)容版本的唯一字符串,與資源關(guān)聯(lián)的記號(hào)

與緩存最相關(guān)的Cache-Control有多條指令,并且在請(qǐng)求或返回頭中的效果不一樣.

在請(qǐng)求頭中Cache-Control的指令

指令參數(shù)說明

no-cache無緩存必須向服務(wù)器確認(rèn)是否過期候才能使用,

即不接受過期緩存,并非不緩存

no-store無真正意義上的不緩存

max-age=[秒]必須響應(yīng)的最大age值

max-stale=[秒]可忽略可接受的最大過期時(shí)間

min-fresh=[秒]必須詢問再過[秒]時(shí)間后資源是否過期,若過期則不返回

only-if-cached無只獲取緩存的資源而不聯(lián)網(wǎng)獲取

在返回頭中Cache-Control的指令

指令參數(shù)說明

public無可向任意方提供響應(yīng)的緩存

private無向特定用戶提供響應(yīng)緩存

no-cache可省略不緩存

no-store無不緩存

max-age=[秒]必須響應(yīng)的最大age值

max-stale=[秒]可忽略可接受的最大過期時(shí)間

min-fresh=[秒]必須詢問再過[秒]時(shí)間后資源是否過期,若過期則不返回

only-if-cached無只獲取緩存的資源而不聯(lián)網(wǎng)獲取

假設(shè)Okhttp完全遵守HTTP協(xié)議(實(shí)際上應(yīng)該也是),利用Cache-Control我們可以緩存某些必要的資源.

有網(wǎng)絡(luò)的時(shí)候:短時(shí)間內(nèi)頻繁的請(qǐng)求,后面的請(qǐng)求使用緩存中的資源.

無網(wǎng)絡(luò)的時(shí)候:獲取之前緩存的數(shù)據(jù)進(jìn)行暫時(shí)的頁(yè)面顯示,當(dāng)網(wǎng)絡(luò)更新時(shí)對(duì)當(dāng)前activity的數(shù)據(jù)進(jìn)行刷新,刷新界面,避免界面空白的場(chǎng)景.

編寫OkHttp網(wǎng)絡(luò)攔截器

class CacheNetworkInterceptor implements Interceptor {

public Response intercept(Interceptor.Chain chain) throws IOException {

//無緩存,進(jìn)行緩存

return chain.proceed(chain.request()).newBuilder()

.removeHeader("Pragma")

//對(duì)請(qǐng)求進(jìn)行最大60秒的緩存

.addHeader("Cache-Control", "max-age=60")

.build();

}

}

static class CacheInterceptor implements Interceptor {

public Response intercept(Interceptor.Chain chain) throws IOException {

Response resp;

Request req;

if (ok) {

//有網(wǎng)絡(luò),檢查10秒內(nèi)的緩存

req = chain.request()

.newBuilder()

.cacheControl(new CacheControl

.Builder()

.maxAge(10, TimeUnit.SECONDS)

.build())

.build();

} else {

//無網(wǎng)絡(luò),檢查30天內(nèi)的緩存,即使是過期的緩存

req = chain.request().newBuilder()

.cacheControl(new CacheControl.Builder()

.onlyIfCached()

.maxStale(30, TimeUnit.DAYS)

.build())

.build();

}

resp = chain.proceed(req);

return resp.newBuilder().build();

}

}

配置OKHTTP中的Cache

int cacheSize = 10 * 1024 * 1024; // 10 MiB

Cache cache = new Cache(httpCacheDirectory, cacheSize);

OkHttpClient client = new OkHttpClient.Builder()

.cache(cache)

//加入攔截器,注意Network與非Network的區(qū)別

.addInterceptor(new CacheInterceptor())

.addNetworkInterceptor(new CacheNetworkInterceptor())

.connectTimeout(10, TimeUnit.SECONDS)

.readTimeout(10, TimeUnit.SECONDS)

.build();

//最后通過使用該HTTP Client進(jìn)行網(wǎng)絡(luò)請(qǐng)求, 就實(shí)現(xiàn)上述利用緩存優(yōu)化應(yīng)用的需求

在retrofit中使用只要將retrofit的okhttpclient換成這個(gè)帶緩存的okhttpclient即可

private val okhttpClient = OkHttpClient.Builder()

.connectTimeout(timeout, TimeUnit.MILLISECONDS)

.readTimeout(timeout, TimeUnit.MILLISECONDS)

.writeTimeout(timeout, TimeUnit.MILLISECONDS)

.retryOnConnectionFailure(true)

.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))

.addInterceptor(CacheInterceptor())

.addNetworkInterceptor(CacheNetworkInterceptor())

.cache(Cache(File(App.app.externalCacheDir, "ok-cache"), 1024 * 1024 * 30L))

.build()

var retrofit2 = Retrofit.Builder().baseUrl(baseURL)

.addCallAdapterFactory(RxJavaCallAdapterFactory.create())

.addConverterFactory(GsonConverterFactory.create())

.client(okhttpClient)

.build()

解釋一下上面的代碼,CacheInterceptor主要的作用是判斷當(dāng)前網(wǎng)絡(luò)是否有效,如果有效,則創(chuàng)建一個(gè)請(qǐng)求.

該請(qǐng)求能獲取一個(gè)10秒內(nèi)未過期的緩存,否則強(qiáng)制獲取一個(gè)緩存(過期了30天也允許).而CacheNetworkInterceptor主要是在緩存沒命中的情況下,請(qǐng)求網(wǎng)絡(luò)后,修改返回頭,加上Cache-Control,告知OKHTTP對(duì)該請(qǐng)求進(jìn)行一個(gè)60秒的緩存.

因此,當(dāng)頻繁請(qǐng)求的時(shí)候,OKHTTP使用10秒之內(nèi)的緩存而不重復(fù)請(qǐng)求網(wǎng)絡(luò).當(dāng)沒網(wǎng)絡(luò)的時(shí)候,請(qǐng)求會(huì)獲取30天內(nèi)的緩存,避免界面白屏.

OkHttp關(guān)于Cache的源碼分析

分析源碼之前先看下Cache的策略

Cache.png

Cache.png

Response getResponseWithInterceptorChain() throws IOException {

// Okhttp獲取Response的入口

// 采用責(zé)任鏈模式,一層層按順序轉(zhuǎn)交Request并處理Response

List interceptors = new ArrayList<>();

// 用戶定義的攔截器

interceptors.addAll(client.interceptors());

interceptors.add(retryAndFollowUpInterceptor);

interceptors.add(new BridgeInterceptor(client.cookieJar()));

//CacheInterceptor主要用于做緩存控制

interceptors.add(new CacheInterceptor(client.internalCache()));

interceptors.add(new ConnectInterceptor(client));

if (!forWebSocket) {

//用戶定義的Network攔截器

interceptors.addAll(client.networkInterceptors());

}

// 發(fā)起實(shí)際請(qǐng)求的攔截器

interceptors.add(new CallServerInterceptor(forWebSocket));

Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,

originalRequest, this, eventListener, client.connectTimeoutMillis(),

client.readTimeoutMillis(), client.writeTimeoutMillis());

return chain.proceed(originalRequest);

}

這里我們主要看CacheInterceptor的實(shí)現(xiàn),CacheInterceptor代碼比較長(zhǎng),我們分段來解釋:

@Override public Response intercept(Chain chain) throws IOException {

Response cacheCandidate = cache != null

? cache.get(chain.request())

: null;

// 實(shí)際上是類似map,將返回內(nèi)容的URL的MD5的值當(dāng)key,返回內(nèi)容當(dāng)response

// 然后從cache文件里面查詢是否存在該緩存

long now = System.currentTimeMillis();

//根據(jù)當(dāng)前的時(shí)間,以及緩存策略,來獲取response

CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();

Request networkRequest = strategy.networkRequest;

Response cacheResponse = strategy.cacheResponse;

// 根據(jù)策略得到cacheReposne 與 NetworkRequest

// 之后的代碼就是根據(jù)這兩個(gè)東西設(shè)置返回頭

// 不進(jìn)行網(wǎng)絡(luò)請(qǐng)求,且緩存以及過期了,返回504錯(cuò)誤

if (networkRequest == null && cacheResponse == null) {

return new Response.Builder()

.request(chain.request())

.protocol(Protocol.HTTP_1_1)

.code(504)

.message("Unsatisfiable Request (only-if-cached)")

.body(Util.EMPTY_RESPONSE)

.sentRequestAtMillis(-1L)

.receivedResponseAtMillis(System.currentTimeMillis())

.build();

}

// 不進(jìn)行網(wǎng)絡(luò)請(qǐng)求,此時(shí)緩存命中,直接返回緩存,后面的攔截器也不會(huì)調(diào)用了

if (networkRequest == null) {

return cacheResponse.newBuilder()

.cacheResponse(stripBody(cacheResponse))

.build();

}

// 否則需要請(qǐng)求網(wǎng)絡(luò),繼續(xù)調(diào)用責(zé)任鏈后面的攔截器,請(qǐng)求網(wǎng)絡(luò)并獲取response

Response networkResponse = null;

try {

networkResponse = chain.proceed(networkRequest);

} finally {

// 請(qǐng)求異常,關(guān)閉緩存避免泄漏

if (networkResponse == null && cacheCandidate != null) {

closeQuietly(cacheCandidate.body());

}

}

// 請(qǐng)求了網(wǎng)絡(luò)的同時(shí),緩存其實(shí)也找到的情況

// (比如 需要向服務(wù)器確認(rèn)緩存是否可用的情況)

if (cacheResponse != null) {

// 返回了304, 我們都知道304的返回時(shí)不帶body的,此時(shí)必須向獲取cache的body

if (networkResponse.code() == HTTP_NOT_MODIFIED) {

Response response = cacheResponse.newBuilder()

.headers(combine(cacheResponse.headers(), networkResponse.headers()))

.sentRequestAtMillis(networkResponse.sentRequestAtMillis())

.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())

.cacheResponse(stripBody(cacheResponse))

.networkResponse(stripBody(networkResponse))

.build();

networkResponse.body().close();

// Update the cache after combining headers but before stripping the

// Content-Encoding header (as performed by initContentStream()).

cache.trackConditionalCacheHit();

cache.update(cacheResponse, response);

return response;

} else {

closeQuietly(cacheResponse.body());

}

}

//省略---------

}

// 緩存策略CacheStrategy主要的策略寫在該方法下

private CacheStrategy getCandidate() {

// 沒有緩存!

if (cacheResponse == null) {

return new CacheStrategy(request, null);

}

// 當(dāng)請(qǐng)求的協(xié)議是https的時(shí)候,如果cache沒有hansake就丟棄緩存

if (request.isHttps() && cacheResponse.handshake() == null) {

return new CacheStrategy(request, null);

}

/// -- 省略一些代碼

// 根據(jù)緩存的緩存時(shí)間,緩存可接受最大過期時(shí)間等等HTTP協(xié)議上的規(guī)范

// 來判斷緩存是否可用,

if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {

Response.Builder builder = cacheResponse.newBuilder();

if (ageMillis + minFreshMillis >= freshMillis) {

builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");

}

long oneDayMillis = 24 * 60 * 60 * 1000L;

if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {

builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");

}

return new CacheStrategy(null, builder.build());

}

}

// 請(qǐng)求條件, 當(dāng)etag,lastModified,servedDate這三種屬性存在時(shí)

//需要向服務(wù)器確認(rèn)緩存的有效性

String conditionName;

String conditionValue;

if (etag != null) {

conditionName = "If-None-Match";

conditionValue = etag;

} else if (lastModified != null) {

conditionName = "If-Modified-Since";

conditionValue = lastModifiedString;

} else if (servedDate != null) {

conditionName = "If-Modified-Since";

conditionValue = servedDateString;

} else {

return new CacheStrategy(request, null); // 不存在的時(shí)候,按流程進(jìn)行請(qǐng)求

}

Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();

Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

// 構(gòu)造一個(gè)請(qǐng)求詢問服務(wù)器資源是否過期

Request conditionalRequest = request.newBuilder()

.headers(conditionalRequestHeaders.build())

.build();

return new CacheStrategy(conditionalRequest, cacheResponse);

借用一張圖來說明http的整個(gè)工作流程

流程也很清晰明了了,簡(jiǎn)單的說及時(shí)通過Request創(chuàng)建RealCall對(duì)象,經(jīng)過層層interceptor之后最終產(chǎn)生一個(gè)response.

不過值得注意的是,當(dāng)CacheInterceptor命中緩存之后, 后面的攔截器將不再執(zhí)行.

這也是addInterceptor 與 addNetworkInterceptor之間的區(qū)別.

最后附上當(dāng)網(wǎng)絡(luò)可用的時(shí)候,自動(dòng)重新請(qǐng)求的一個(gè)基于MVP模式的實(shí)現(xiàn)方案

NetStatusMonitor是一個(gè)單例,用于監(jiān)聽整個(gè)應(yīng)用程序的網(wǎng)絡(luò)狀態(tài).

ActivityManager也是一個(gè)單例,用來管理應(yīng)用程序的活動(dòng)棧,原理Application注冊(cè)關(guān)于活動(dòng)的生命周期監(jiān)聽.

基于MVP模式,給presenter的抽象基類定義一個(gè)refresh的方法,當(dāng)斷網(wǎng)時(shí)間超過XX秒的時(shí)候,調(diào)用在棧頂?shù)腶ctivity的presenter進(jìn)行刷新頁(yè)面.

如有不足請(qǐng)各位大佬指正:

NetStatusMonitor.setNetStatusListener(object: NetStatusMonitor.Listener {

var lostTime = 0L

override fun onLost() {

lostTime = System.currentTimeMillis()

}

override fun onAvailable() {

with(ActivityManager.peek() as BaseView<*>){

//當(dāng)棧頂活動(dòng)位于前臺(tái)

if(this.lifecycle.currentState == Lifecycle.State.RESUMED){

// 獲取ForegroundActivity進(jìn)行刷新

// 斷線時(shí)間超過30秒重連再刷新一次

if(System.currentTimeMillis() - lostTime > 1000 * 30){

// 通知presenter刷新數(shù)據(jù)

this.presenter.refresh()

}

}

}

}

override fun onNetStateChange(oldState: Int, newState: Int) {

if(newState == NetStatusMonitor.MOBILE){

showToast("正在使用移動(dòng)網(wǎng)絡(luò)")

}

}

})

object NetStatusMonitor {

interface Listener{

fun onLost()

fun onAvailable()

fun onNetStateChange(oldState: Int, newState: Int)

}

val WIFI = 1;

val MOBILE = 2;

val WIFI_MOBILE = 3;

val UNKNOW = 0

var available = false

var netState: Int by Delegates.observable(UNKNOW) { property, oldValue, newValue ->

listener?.onNetStateChange(oldValue, newValue)

}

private var listener : Listener? = null

fun setNetStatusListener(listener: Listener){

this.listener = listener

}

init {

val cm = Utils.app.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

fun setType() {

val activeNetwork = cm.activeNetworkInfo

val isMobile = activeNetwork.type == ConnectivityManager.TYPE_MOBILE

val isWifi = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isAvailable

if (isWifi && isMobile)

netState = WIFI_MOBILE

else if (isWifi && !isMobile)

netState = WIFI

else if (isMobile && !isWifi)

netState = MOBILE

else

netState = UNKNOW

}

cm.requestNetwork(NetworkRequest.Builder().build(), object : ConnectivityManager.NetworkCallback() {

override fun onAvailable(network: Network?) {

available = true

setType()

listener?.onAvailable()

}

override fun onLost(network: Network?) {

available = false

listener?.onLost()

}

})

}

}

專題首頁(yè)|財(cái)金網(wǎng)首頁(yè)

原創(chuàng)
新聞

精彩
互動(dòng)

獨(dú)家
觀察

京ICP備2021034106號(hào)-38   營(yíng)業(yè)執(zhí)照公示信息  財(cái)金網(wǎng)  版權(quán)所有  cfenews.com  投稿郵箱:362293157@qq.com  業(yè)務(wù)QQ:362293157立即發(fā)帖