Cache-Control與retrofit緩存

Cache-Control

HTTP中這個字段用於指定所有緩存機制在整個請求/響應鏈中必須服從的指令。緩存指令是單向的,即請求中存在一個指令並不意味着響應中將存在同一個指令。常見的取值有privateno-cachemax-agemust-revalidate等,默認爲private

常用 cache-directive 值

Cache-directive

說明

public

所有內容都將被緩存(客戶端和代理服務器都可緩存)

private

內容只緩存到私有緩存中(僅客戶端可以緩存,代理服務器不可緩存)

no-cache

必須先與服務器確認返回的響應是否被更改,然後才能使用該響應來滿足後續對同一個網址的請求。因此,如果存在合適的驗證令牌 (ETag),no-cache 會發起往返通信來驗證緩存的響應,如果資源未被更改,可以避免下載。

no-store

所有內容都不會被緩存到緩存或 Internet 臨時文件中

must-revalidation/proxy-revalidation

如果緩存的內容失效,請求必須發送到服務器/代理以進行重新驗證

max-age=xxx (xxx is numeric)

緩存的內容將在 xxx 秒後失效, 這個選項只在HTTP 1.1可用, 並如果和Last-Modified一起使用時, 優先級較高

Retrofit實現網絡緩存

首先,配置OkHttp中Cache

OkHttpClient okHttpClient = new OkHttpClient();

File cacheFile = new File(context.getCacheDir(), "[緩存目錄]");

Cache cache = new Cache(cacheFile, 1024 * 1024 * 100);//100Mb

okHttpClient.setCache(cache);


配置請求頭中的Cache-Control

@FormUrlEncoded

    @Headers("Cache-Control: public,max-age:3600")

    @POST("?method=app.system.init")

    Observable<HttpResult<AppInit>>getAppInit(@Field("ucode") String ucode);

雲端配合設置響應頭或者自己寫攔截器修改響應頭responsecache-control

到這一步緩存就已經待在你的緩存目錄了。
如果雲端有處裏cache的話,就已經可以了。
但是很可能雲端沒有處理,所以返回的響應頭中cache-controlno-cache,這時候你還是無法做緩存,大家可以用okhttp的寫日誌攔截器查看響應頭的內容。

如果雲端現在不方便處理的話,你也可以自己搞定緩存的,那就是寫攔截器修改響應頭中的cache-control

設置攔截器:

CacheControlInterceptor cacheControlInterceptor = newCacheControlInterceptor();

return new OkHttpClient

               .Builder()

               .addInterceptor(loggingInterceptor)

               .addInterceptor(paramsInterceptor)

                .addInterceptor(cacheInterceptor)

               .cache(cache)

               .build();

攔截器的代碼如下:

public class CacheControlInterceptor implementsInterceptor {

 

    @Override

    public Responseintercept(Chain chain) throws IOException {

       CacheControl.Builder cacheBuilder = new CacheControl.Builder();

       cacheBuilder.maxAge(0, TimeUnit.SECONDS);

       cacheBuilder.maxStale(365, TimeUnit.DAYS);

       CacheControl cacheControl = cacheBuilder.build();

        Requestrequest = chain.request();

        if(!NetworkStateUtils.isNetworkAvailable()) {

            request= request.newBuilder()

                   .cacheControl(cacheControl)

                   .build();

        }

        ResponseoriginalResponse = chain.proceed(request);

        if(NetworkStateUtils.isNetworkAvailable()) {

            intmaxAge = 0; // read from cache

            returnoriginalResponse.newBuilder()

                   .removeHeader("Pragma")

                   .header("Cache-Control", "public ,max-age=" +maxAge)

                   .build();

        } else {

            intmaxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale

            returnoriginalResponse.newBuilder()

                   .removeHeader("Pragma")

                   .header("Cache-Control", "public, only-if-cached,max-stale=" + maxStale)

                   .build();

        }

    }

 

Retrofit支持緩存post請求

不過,因爲Retrofit和OkHttp是以支持RestfulAPI爲前提的,所以,只對get請求緩存。如果服務器不是標準的RestfulAPI,比如全部採用post請求,那麼如何實現緩存呢?

Retrofit本身的緩存是通過DiskLRUCache實現的,我們可以仿照它來實現自己的緩存來支持post請求。

首先,引入DiskLRUCache

寫一個工具類來設置和獲取緩存:

public final class CacheManager {

 

    public staticfinal String TAG = "CacheManager";

 

    //max cachesize 100mb

    private static final long DISK_CACHE_SIZE =1024 * 1024 * 100;

 

    private staticfinal int DISK_CACHE_INDEX = 0;

 

    private staticfinal String CACHE_DIR = "responses";

    privatevolatile static CacheManager mCacheManager;

    privateDiskLruCache mDiskLruCache;

 

    privateCacheManager() {

        FilediskCacheDir = getDiskCacheDir(App.getContext(), CACHE_DIR);

        if(!diskCacheDir.exists()) {

            booleanb = diskCacheDir.mkdirs();

           Log.d(TAG, "!diskCacheDir.exists() --- diskCacheDir.mkdirs()="+ b);

        }

        if(diskCacheDir.getUsableSpace() > DISK_CACHE_SIZE) {

            try {

               mDiskLruCache = DiskLruCache.open(diskCacheDir,

                       getAppVersion(App.getContext()), 1/*一個key對應多少個文件*/, DISK_CACHE_SIZE);

               Log.d(TAG, "mDiskLruCache created");

            } catch(IOException e) {

               e.printStackTrace();

            }

        }

    }

 

    public staticCacheManager getInstance() {

        if (mCacheManager == null) {

           synchronized (CacheManager.class) {

                if(mCacheManager == null) {

                   mCacheManager = new CacheManager();

                }

            }

        }

        returnmCacheManager;

    }

 

    /**

     * 對字符串進行MD5編碼

     */

    private staticString encryptMD5(String string) {

        try {

            byte[]hash = MessageDigest.getInstance("MD5").digest(

                   string.getBytes("UTF-8"));

           StringBuilder hex = new StringBuilder(hash.length * 2);

            for(byte b : hash) {

                if((b & 0xFF) < 0x10) {

                   hex.append("0");

                }

               hex.append(Integer.toHexString(b & 0xFF));

            }

            returnhex.toString();

        } catch(NoSuchAlgorithmException | UnsupportedEncodingException e) {

           e.printStackTrace();

        }

        returnstring;

    }

 

    /**

     * 同步設置緩存

     */

    public voidputCache(String key, String value) throws IOException {

        if(mDiskLruCache == null) return;

       OutputStream os = null;

        try {

           DiskLruCache.Editor editor = mDiskLruCache.edit(encryptMD5(key));

            os = editor.newOutputStream(DISK_CACHE_INDEX);

           os.write(value.getBytes());

           os.flush();

           editor.commit();

           mDiskLruCache.flush();

        } catch(IOException e) {

            throwe;

        } finally {

            if (os != null) {

                try{

                   os.close();

                }catch (IOException e) {

                   e.printStackTrace();

                }

            }

        }

    }

 

    /**

     * 同步獲取緩存

     */

    public StringgetCache(String key) throws IOException {

        if(mDiskLruCache == null) {

            returnnull;

        }

       FileInputStream fis = null;

       ByteArrayOutputStream bos = null;

        try {

           DiskLruCache.Snapshot snapshot = mDiskLruCache.get(encryptMD5(key));

            if(snapshot != null) {

                fis= (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);

                bos= new ByteArrayOutputStream();

               byte[] buf = new byte[1024];

                intlen;

               while ((len = fis.read(buf)) != -1) {

                   bos.write(buf, 0, len);

                }

               byte[] data = bos.toByteArray();

               return new String(data);

            }

        } catch(IOException e) {

            throwe;

        } finally {

            if (fis!= null) {

                try{

                   fis.close();

                }catch (IOException e) {

                   e.printStackTrace();

                }

            }

            if (bos!= null) {

                try{

                   bos.close();

                }catch (IOException e) {

                   e.printStackTrace();

                }

            }

        }

        return"";

    }

 

    /**

     * 移除緩存

     */

    public booleanremoveCache(String key) {

        if(mDiskLruCache != null) {

            try {

               return mDiskLruCache.remove(encryptMD5(key));

            } catch(IOException e) {

               e.printStackTrace();

            }

        }

        returnfalse;

    }

 

    /**

     * 獲取緩存目錄

     */

    private FilegetDiskCacheDir(Context context, String uniqueName) {

        StringcachePath = context.getCacheDir().getPath();

        return new File(cachePath + File.separator +uniqueName);

    }

 

    /**

     * 獲取APP版本號

     */

    private intgetAppVersion(Context context) {

       PackageManager pm = context.getPackageManager();

        try {

           PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0);

            returnpi == null ? 0 : pi.versionCode;

        } catch(PackageManager.NameNotFoundException e) {

           e.printStackTrace();

        }

        return 0;

    }

}


實現一個EnhancedCacheInterceptor,攔截post並緩存post請求。實現無網絡時讀取緩存。

public class EnhancedCacheInterceptor implementsInterceptor {

    @Override

    public Responseintercept(Chain chain) throws IOException {

       LogUtils.e("EnhancedCacheInterceptor");

        Requestrequest = chain.request();

 

        HttpUrlhttpUrl = request.url();

        String url= httpUrl.toString();

        RequestBodyrequestBody = request.body();

        Charsetcharset = Charset.forName("UTF-8");

       StringBuilder sb = new StringBuilder();

       sb.append(httpUrl.queryParameter("method"));

       /*sb.append(url);

        if(request.method().equals("POST")) {

           MediaType contentType = requestBody.contentType();

            if(contentType != null) {

               charset = contentType.charset(Charset.forName("UTF-8"));

            }

            Bufferbuffer = new Buffer();

            try {

               requestBody.writeTo(buffer);

            } catch(IOException e) {

               e.printStackTrace();

            }

           sb.append(buffer.readString(charset));

           buffer.close();

        }*/

       Log.e(CacheManager.TAG, "EnhancedCacheInterceptor -> key:"+ sb.toString());

 

        if(NetworkStateUtils.isNetworkAvailable()) {

           Response response = chain.proceed(request);

           CacheControl cacheControl = request.cacheControl();

           LogUtils.e(cacheControl.toString());

           LogUtils.e("response: " + response.toString());

           LogUtils.e("responseBody : " + response.body().toString());

            if(!cacheControl.noStore()) {

               ResponseBody responseBody = response.body();

               MediaType contentType = responseBody.contentType();

 

               BufferedSource source = responseBody.source();

               source.request(Long.MAX_VALUE);

               Buffer buffer = source.buffer();

 

                if(contentType != null) {

                   charset = contentType.charset(Charset.forName("UTF-8"));

                }

               String key = sb.toString();

                //服務器返回的json原始數據

               String json = buffer.clone().readString(charset);

 

               CacheManager.getInstance().putCache(key, json);

                Log.e(CacheManager.TAG, "putcache-> key:" + key + "-> json:" + json);

            }

            returnresponse;

        } else {

           CacheControl cacheControl = request.cacheControl();

            if(!cacheControl.noStore()) {

                request = request.newBuilder()

                       .cacheControl(CacheControl.FORCE_CACHE)

                       .build();

               String key = sb.toString();

               String cache = CacheManager.getInstance().getCache(key);

                Response originalResponse =chain.proceed(request);

 

               return originalResponse

                       .newBuilder()

                       .code(200)

                       .message("OK")

                       .body(ResponseBody.create(originalResponse.body().contentType(), cache))

                       .build();

            } else{

               return chain.proceed(request);

            }

        }

    }

}


注意,默認在無網絡情況下,會返回504 error。這裏hack response,強制改回200,並返回緩存數據。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章