Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> HttpCache in android

HttpCache in android

編輯:關於android開發

HttpCache in android


 

概述

http Cache指的是web浏覽器所具有的復用本地已緩存的文檔”副本”的能力。我們知道,通過網絡獲取內容有時候成本很高,因而
緩存和重用以前獲取的資源的能力成為優化性能很關鍵的一個方面。http協議本身提供了緩存的支持。

緩存的優勢

1. 減少冗余數據傳輸
2. 緩解網絡帶寬瓶頸
3. 降低距離時延
4. 減少服務器負擔

http協議對緩存的支持

Expires & Cache-Control

Expires響應首部給出了響應失效的絕對時間,這樣,像浏覽器這樣的客戶端就可以緩存一份副本,在這個時間到期之前,
不用去詢問服務器它是否有效了。http1.0引入。 例如:Expires: Thu, 03 Oct 1997 17:15:00 GMT

Cache-Control首部用於傳輸對象的緩存信息。http1.1引入。它的值是一個緩存指令,給出了與某個對象可緩存性有關的
緩存特有指令。這個首部可以出現在請求或者響應頭中。例如:Cache-Control: no-cache

ExpiresCache-Control作用一致,都是指當前資源的有效期,控制浏覽器是否直接從浏覽器緩存獲取數據還是重新發送
請求到服務器取數據。

Cache-Control可以設置如下值:

(1)Request:

no-cache —- 不要讀取緩存中的文件,要求向WEB服務器重新請求,http1.0時代還有pragma: no-cache,與此效果一樣. no-store —- 請求和響應都禁止被緩存 max-age: —- 表示當訪問此網頁後的max-age秒內再次訪問不會去服務器請求,其功能與Expires類似,只是Expires是根據某個特定日期值做比較。 max-stale —- 允許讀取過期時間必須小於max-stale 值的緩存對象。 min-fresh —- 接受其max-age生命期大於其當前時間 跟 min-fresh 值之和的緩存對象 only-if-cached —- 告知緩存者,我希望內容來自緩存,我並不關心被緩存響應,是否是新鮮的. no-transform —- 告知代理,不要更改媒體類型,比如jpg,被你改成png.

(2)Response:

public —- 數據內容皆被儲存起來,就連有密碼保護的網頁也儲存,安全性很低 private —- 數據內容只能被儲存到私有的cache,僅對某個用戶有效,不能共享 no-cache —- 可以緩存,但是只有在跟WEB服務器驗證了其有效後,才能返回給客戶端 no-store —- 請求和響應都禁止被緩存 max-age: —– 本響應包含的對象的過期時間 Must-revalidate —- 如果緩存過期了,會再次和原來的服務器確定是否為最新數據,而不是和中間的proxy max-stale —- 允許讀取過期時間必須小於max-stale 值的緩存對象。 s-maxage —- 與max-age的唯一區別是,s-maxage僅僅應用於共享緩存.而不應用於用戶代理的本地緩存等針對單用戶的緩存. 另外,s-maxage的優先級要高於max-age. no-transform —- 告知代理,不要更改媒體類型,比如jpg,被你改成png.

注意:Expires首部的值是絕對時間,容易受本地時鐘偏差影響,而Cache-Control是相對時間。因而,Cache-Control
優先級高於Expires

最優的Cache-Control策略
最優的<codeCache-Control策略 title=>

ETag & If-None-Match

ETag告知浏覽器當前資源在服務器的唯一標識符,由響應頭攜帶

If-None-Match檢查資源實體是否發生了改變,由請求頭攜帶

這兩個首部通常結合使用。首先浏覽器請求某個文檔資源,服務端會對這個資源進行計算,生成指紋(md5或者其他..),然後將指紋作為ETag令牌
放到響應頭中作為ETag的值發送給浏覽器,浏覽器收到響應後,將資源緩存。浏覽器第二次訪問此資源時,會先從緩存中尋找此資源,
並將資源的ETag令牌作為If-None-Match請求頭的值發往服務端,服務端拿到請求頭後,將If-None-Match首部中包含的ETag
令牌與該資源的指紋進行校驗比對,如果沒有發生變化,則返回304,告訴浏覽器可以復用緩存,否則重新請求,並根據請求結果,
返回200或者4xx。

Last-Modified & if-Modified-Since

Last-Modified首部提供資源最後一次被修改的日期,由響應頭攜帶

if-Modified-Since首部檢查資源是否在此時間後被修改過,由請求頭攜帶

這兩個首部通常結合使用。首先浏覽器請求某個文檔資源,服務端找到該資源,並在響應頭中加上Last-Modified標識該資源最近
一次改動的時間,浏覽器收到響應後,將資源緩存。浏覽器第二次訪問此資源時,會先從緩存中尋找此資源,並將資源的last-Modified
的日期值作為if-Modified-Since首部的值發往服務器,服務端拿到請求頭後,將’if-Modified-Since’的日期值與該資源最近
一次改動時間進行比對,如果相同將直接返回304,說明可以復用該資源,否則重新請求,並根據請求結果,返回200或者4xx等。注:不是
所有的浏覽器都支持if-Modified-Since字段

舉個栗子,比如我訪問這個圖片:

http://img.alicdn.com/bao/uploaded/i4/2433767071/TB2hL7dfXXXXXaeXXXXXXXXXXXX_!!2433767071-0-paimai.jpg

第一次請求和響應:

第一次請求和響應

第二次請求和響應:<喎?http://www.Bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxpbWcgYWx0PQ=="第二次請求和響應" src="http://www.bkjia.com/uploads/allimg/151112/04151G1H-2.png" title="\" />

第一次請求因為本地沒有緩存副本,所以沒有攜帶if-Last-Modified請求頭,而服務端會將資源最後修改時間通過Last-Modified
返回,第二次因為緩存了副本,所以會攜帶if-Last-Modified.而且從響應碼也能看出來,第一次是200,第二次是304。

區分緩存命中還是未命中有點”困難”,因為http沒有為用戶提供一種手段來區分響應是緩存命中的,還是訪問原始服務器得到的。
在這兩種情況下,響應碼都是200 ok。客戶端有一種方法可以判斷響應是否來自緩存,就是使用Date首部,將響應中Date首部的值
和當前事件進行比較,如果響應中的日期值比較早,客戶端通常就可以認為這是一條緩存的響應,或者客戶端也可以通過Age首部來檢測
緩存的響應。

android下的httpcache方案

HttpResponseCache類,android4.0以後.

HttpResponseCache兼容版,https://github.com/candrews/HttpResponseCache.

直接使用Square推出的OkHttp,google在4.0之後直接將Okhttp項目集成到了android項目中,作為HttpUrlConnection的實現引擎.
而方案一中的HttpResponseCache也是依賴於Okhttp的,然而我們無法直接使用Okhttp因為它是hide的,使用的話需要單獨依賴。

以android提供的HttpResponseCache為例說明。
我的代碼演示了如何使用HttpResponseCache緩存一張網絡圖片,NetworkUtils類提供了getBitmapasyncGetBitmap方法,用於獲取
網絡圖片:

代碼1:NetworkUtils#getBitmap()

public static HttpResponse getBitmap(Context context,Uri uri,Policy policy){
        if(uri == null){
            throw new IllegalArgumentException(uri is null);
        }
        installCacheIfNeeded(context);
        try {
            HttpURLConnection connection = (HttpURLConnection) new URL(MainActivity.URL).openConnection();
            connection.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
            connection.setReadTimeout(DEFAULT_READ_TIMEOUT);

            if(policy == Policy.Cache){
                connection.setUseCaches(true);
            }else{
                connection.setUseCaches(false);
                connection.addRequestProperty(Cache-Control, no-cache);
                connection.addRequestProperty(Cache-Control,max-age=0);
            }

            int contentLen = connection.getContentLength();
            int responseCode = connection.getResponseCode();
            HttpResponse response = new HttpResponse(responseCode,null,contentLen, BitmapFactory.decodeStream(connection.getInputStream()));
            connection.getInputStream().close();
            return response;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

這裡面我通過客戶端的設定Policy來判斷是否需要緩存圖片,如果不需要就設置no-cache頭。installCacheIfNeeded方法
用於初始化緩存,內部實際上調用了HttpCache#install()方法:

代碼2:HttpCache#install()

 Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            try {
                if ((cache = HttpResponseCache.getInstalled()) != null) {
                    return cache;
                }
                cache = HttpResponseCache.install(cacheDir, CACHE_SIZE);
            } catch (IOException e) {
                e.printStackTrace();
                Log.e(TAG, http response installation failed,code = 0);
            }
        } else {
            try {
                if ((cache = (HttpResponseCache) Class.forName(android.net.http.HttpResponseCache).getMethod(getInstalled).invoke(null)) != null) {
                    return cache;
                }
                Method method = Class.forName(android.net.http.HttpResponseCache).getMethod(install, File.class, long.class);
                cache = (HttpResponseCache) method.invoke(null, cacheDir, CACHE_SIZE);
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, http response installation failed,code = 1);
            }
        }
        return cache;
    } data-snippet-id=ext.2119c294a5428b71b1001e58f12468cb data-snippet-saved=false data-csrftoken=RvI0acnw-08Q46livGiVM05M4uTurSwGW5Pk data-codota-status=done>static HttpResponseCache install(Context context) {
        if (context == null) {
            throw new IllegalArgumentException(context must not be null);
        }

        File cacheDir = new File(context.getApplicationContext().getCacheDir(), CACHE_DIR);
        if (!cacheDir.exists()) {
            cacheDir.mkdirs();
        }

        HttpResponseCache cache = null;
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            try {
                if ((cache = HttpResponseCache.getInstalled()) != null) {
                    return cache;
                }
                cache = HttpResponseCache.install(cacheDir, CACHE_SIZE);
            } catch (IOException e) {
                e.printStackTrace();
                Log.e(TAG, http response installation failed,code = 0);
            }
        } else {
            try {
                if ((cache = (HttpResponseCache) Class.forName(android.net.http.HttpResponseCache).getMethod(getInstalled).invoke(null)) != null) {
                    return cache;
                }
                Method method = Class.forName(android.net.http.HttpResponseCache).getMethod(install, File.class, long.class);
                cache = (HttpResponseCache) method.invoke(null, cacheDir, CACHE_SIZE);
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, http response installation failed,code = 1);
            }
        }
        return cache;
    }

我在MainActivity中編寫了一個測試用例來分別測試了允許緩存和不允許緩存情況下的結果。加載的圖片來自阿裡cdn圖片服務器,
這裡的圖片的過期時間是一年,所以可以確保能夠緩存。

Cache-Control:max-age=31536000
Content-Type:image/jpeg
Date:Thu, 22 Oct 2015 05:41:18 GMT
Expires:Fri, 21 Oct 2016 05:41:18 GMT

測試結果很完美,在允許緩存的情況下,只需要第一次請求server,拿到數據後會緩存到本地預設的目錄,以後的請求都會直接走
緩存.

demo

我們來觀察下緩存的數據格式,這裡我預先設定了緩存目錄位於包名/cache/http/下(相關代碼參考HttpCache.java),ddms打開這個
目錄,pull出來,發現三個文件,其中有個叫journal的,很顯然,這是使用了DiskLruCache類,另外兩個文件有一個大小是185458字節,
肯定是這張圖片,另一個文件用sublime打開後內容如下:

http://img.alicdn.com/bao/uploaded/i4/2433767071/TB2hL7dfXXXXXaeXXXXXXXXXXXX_!!2433767071-0-paimai.jpg
GET
0
HTTP/1.1 200 OK
18
Server: Tengine
Content-Type: image/jpeg
Content-Length: 185458
Connection: keep-alive
Date: Thu, 22 Oct 2015 05:41:18 GMT
Last-Modified: Sat, 12 Sep 2015 11:24:37 GMT
Expires: Fri, 21 Oct 2016 05:41:18 GMT
Cache-Control: max-age=31536000
Access-Control-Allow-Origin: *
Via: cache7.l2cm12[0,200-0,H], cache60.l2cm12[16,0], cache1.cn406[0,200-0,H], cache8.cn406[1,0]
Age: 1501380
X-Cache: HIT TCP_MEM_HIT dirn:2:377253066
X-Swift-SaveTime: Sun, 08 Nov 2015 03:15:26 GMT
X-Swift-CacheTime: 30075952
Timing-Allow-Origin: *
X-Android-Sent-Millis: 1446993858366
X-Android-Received-Millis: 1446993858403
X-Android-Response-Source: NETWORK 200

這其實是一個響應頭,X-Android-Response-Source首部是由othttp追加的,可以判斷響應數據是從disk cache拿的還是
從網絡拿的。代碼如下:
代碼3:

//返回true說明從緩存拿的
static boolean parseResponseSourceHeader(String header) {
    if (header == null) {
      return false;
    }
    String[] parts = header.split( , 2);
    if (CACHE.equals(parts[0])) {
      return true;
    }
    if (parts.length == 1) {
      return false;
    }
    try {
      return CONDITIONAL_CACHE.equals(parts[0]) && Integer.parseInt(parts[1]) == 304;
    } catch (NumberFormatException e) {
      return false;
    }
  }

通過parseResponseSourceHeader(connection.getHeaderField(X-Android-Response-Source))即可判定.

如果使用OkHttp的話,緩存的控制更加方便,只需創建OkHttpClient實例的時候設置緩存即可。具體代碼可以參考OkHttpUtils類.

代碼4:

 try {
        mHttpClient.setCache(new Cache(new File(context.getApplicationContext().getCacheDir(), CACHE_DIR), CACHE_SIZE));
    } catch (Exception e) {
    }

緩存的策略選擇可通過CacheControl來控制。

代碼5:

  CacheControl cacheControl;
        if (policy == Policy.Cache) {
            cacheControl = CacheControl.FORCE_CACHE;
        } else {
            CacheControl.Builder builder = new CacheControl.Builder();
            cacheControl = builder.noCache().build();
        }

http cache的應用

像volley、picasso等開源庫都利用了http緩存來作為DiskCache的方案。具體代碼可參見Volley#HttpHeaderParser類以及
Picasso#URLConnectionDownloader類等.

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved