天天看點

Cache-Control與retrofit緩存

Cache-Control

HTTP中这个字段用于指定所有缓存机制在整个请求/响应链中必须服从的指令。缓存指令是单向的,即请求中存在一个指令并不意味着响应中将存在同一个指令。常见的取值有private、no-cache、max-age、must-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);

云端配合设置响应头或者自己写拦截器修改响应头response中cache-control

到这一步缓存就已经待在你的缓存目录了。

如果云端有处里cache的话,就已经可以了。

但是很可能云端没有处理,所以返回的响应头中cache-control是no-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, 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;

    }

 

   

    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);

    }

 

   

    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"));

       

       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,并返回缓存数据。

 

繼續閱讀