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