Retrofit和OkHttp使用網(wǎng)絡(luò)緩存數(shù)據(jù)

OkHttp緩存優(yōu)化你的應(yīng)用

Okhttp緩存原理

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

請求頭/返回頭 含義
Cache-Control 這個字段用于指定所有緩存機制在整個請求/響應(yīng)鏈中
必須服從的指令。
Pragma 與Cache-Control一樣,是兼容HTTP1.0的頭部
Expires 資源過期時間
Last-Modified 資源最后修改的時間
If-Modified-Since 在請求頭中指定一個日期,若資源最后更新時間超過該日期,
則服務(wù)器接受請求,相反的頭為If-Unmodified-Since
ETag 識別內(nèi)容版本的唯一字符串,與資源關(guān)聯(lián)的記號

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

在請求頭中Cache-Control的指令

指令 參數(shù) 說明
no-cache 緩存必須向服務(wù)器確認是否過期候才能使用,
即不接受過期緩存,并非不緩存
no-store 真正意義上的不緩存
max-age=[秒] 必須 響應(yīng)的最大age值
max-stale=[秒] 可忽略 可接受的最大過期時間
min-fresh=[秒] 必須 詢問再過[秒]時間后資源是否過期,若過期則不返回
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=[秒] 可忽略 可接受的最大過期時間
min-fresh=[秒] 必須 詢問再過[秒]時間后資源是否過期,若過期則不返回
only-if-cached 只獲取緩存的資源而不聯(lián)網(wǎng)獲取

假設(shè)Okhttp完全遵守HTTP協(xié)議(實際上應(yīng)該也是),利用Cache-Control我們可以緩存某些必要的資源.
1.有網(wǎng)絡(luò)的時候:短時間內(nèi)頻繁的請求,后面的請求使用緩存中的資源.
2.無網(wǎng)絡(luò)的時候:獲取之前緩存的數(shù)據(jù)進行暫時的頁面顯示,當(dāng)網(wǎng)絡(luò)更新時對當(dāng)前activity的數(shù)據(jù)進行刷新,刷新界面,避免界面空白的場景.

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

class CacheNetworkInterceptor implements Interceptor {
    public Response intercept(Interceptor.Chain chain) throws IOException {
        //無緩存,進行緩存
        return chain.proceed(chain.request()).newBuilder()
                .removeHeader("Pragma")
                //對請求進行最大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.SECONDS)
                            .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進行網(wǎng)絡(luò)請求, 就實現(xiàn)上述利用緩存優(yōu)化應(yīng)用的需求

在retrofit中使用只要將retrofit的okhttpclient換成這個帶緩存的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)建一個請求,
該請求能獲取一個10秒內(nèi)未過期的緩存,否則強制獲取一個緩存(過期了30天也允許).
而CacheNetworkInterceptor 主要是在緩存沒命中的情況下,請求網(wǎng)絡(luò)后,修改返回頭,加上Cache-Control,告知OKHTTP對該請求進行一個60秒的緩存.

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


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

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


Cache.png
Response getResponseWithInterceptorChain() throws IOException {
    // Okhttp獲取Response的入口
    // 采用責(zé)任鏈模式,一層層按順序轉(zhuǎn)交Request并處理Response
    List<Interceptor> 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ā)起實際請求的攔截器
    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的實現(xiàn)
CacheInterceptor代碼比較長,我們分段來解釋


 @Override public Response intercept(Chain chain) throws IOException {
 
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;
    // 實際上是類似map,將返回內(nèi)容的URL的MD5的值當(dāng)key,返回內(nèi)容當(dāng)response
    // 然后從cache文件里面查詢是否存在該緩存

    long now = System.currentTimeMillis();
    //根據(jù)當(dāng)前的時間,以及緩存策略,來獲取response
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;
    // 根據(jù)策略得到cacheReposne 與 NetworkRequest
    // 之后的代碼就是根據(jù)這兩個東西設(shè)置返回頭

    // 不進行網(wǎng)絡(luò)請求,且緩存以及過期了,返回504錯誤
    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();
    }

    // 不進行網(wǎng)絡(luò)請求,此時緩存命中,直接返回緩存,后面的攔截器也不會調(diào)用了
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
    
    // 否則需要請求網(wǎng)絡(luò),繼續(xù)調(diào)用責(zé)任鏈后面的攔截器,請求網(wǎng)絡(luò)并獲取response
    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // 請求異常,關(guān)閉緩存避免泄漏
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }
    
    // 請求了網(wǎng)絡(luò)的同時,緩存其實也找到的情況
    // (比如 需要向服務(wù)器確認緩存是否可用的情況)
    if (cacheResponse != null) {
    // 返回了304, 我們都知道304的返回時不帶body的,此時必須向獲取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)請求的協(xié)議是https的時候,如果cache沒有hansake就丟棄緩存
      if (request.isHttps() && cacheResponse.handshake() == null) {
        return new CacheStrategy(request, null);
      }
      
      /// -- 省略一些代碼
      
      // 根據(jù)緩存的緩存時間,緩存可接受最大過期時間等等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());
      }
    }

      // 請求條件, 當(dāng)etag,lastModified,servedDate這三種屬性存在時
      //需要向服務(wù)器確認緩存的有效性
      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); // 不存在的時候,按流程進行請求
      }

      Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
      Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

      // 構(gòu)造一個請求詢問服務(wù)器資源是否過期
      Request conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build();
      return new CacheStrategy(conditionalRequest, cacheResponse);
    

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

image

流程也很清晰明了了,簡單的說及時通過Request創(chuàng)建RealCall對象,
經(jīng)過層層interceptor之后最終產(chǎn)生一個response.
不過值得注意的是,當(dāng)CacheInterceptor命中緩存之后, 后面的攔截器將不再執(zhí)行.
這也是addInterceptor 與 addNetworkInterceptor之間的區(qū)別


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

NetStatusMonitor是一個單例,用于監(jiān)聽整個應(yīng)用程序的網(wǎng)絡(luò)狀態(tài)
ActivityManager也是一個單例,用來管理應(yīng)用程序的活動棧,原理Application注冊關(guān)于活動的生命周期監(jiān)聽.

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

如有不足請各位大佬指正

    NetStatusMonitor.setNetStatusListener(object: NetStatusMonitor.Listener {
        var lostTime = 0L
        override fun onLost() {
            lostTime = System.currentTimeMillis()
        }
        
        override fun onAvailable() {
            with(ActivityManager.peek() as BaseView<*>){
                //當(dāng)棧頂活動位于前臺
                if(this.lifecycle.currentState == Lifecycle.State.RESUMED){
                    // 獲取ForegroundActivity進行刷新
                    // 斷線時間超過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("正在使用移動網(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()
            }
        })

    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容