前言
—
完整项目 托管在 github 上,直达电梯:豆瓣电影客户端 github
想了解项目的整个概况可以查看 先前写的 Android 豆瓣电影客户端开源
在 Android 开发过程中,Retrofit 的出现绝对是里程碑式的,说起 Retrofit 不得不说 square,这绝对是家l好公司,业界良心。
回到今天的主题,OKHttp3 网络层 缓存实现与分析。有人可能会说,你这怎么又扯到 OKHttp3 了,好了废话不扯。
代码分析
本文主要还是关于实现层面的讲解,只是豆瓣电影客户端其中一个功能。先贴代码(经过测试的代码)。
class Factory {
private static String TAG = "factory";
public static MovieApiService createService(final Context context) {
//日志拦截器
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
/**
* 获取缓存
*/
Interceptor baseInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!AppUtil.isNetWorkAvailable(context)) {
/**
* 离线缓存控制 总的缓存时间=在线缓存时间+设置离线缓存时间
*/
int maxStale = 60 * 60 * 24 * 28; // 离线时缓存保存4周,单位:秒
CacheControl tempCacheControl = new CacheControl.Builder()
.onlyIfCached()
.maxStale(maxStale, TimeUnit.SECONDS)
.build();
request = request.newBuilder()
.cacheControl(tempCacheControl)
.build();
Log.i(TAG, "intercept:no network ");
}
return chain.proceed(request);
}
};
//只有 网络拦截器环节 才会写入缓存写入缓存,在有网络的时候 设置缓存时间
Interceptor rewriteCacheControlInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response originalResponse = chain.proceed(request);
int maxAge = 1 * 60; // 在线缓存在1分钟内可读取 单位:秒
return originalResponse.newBuilder()
.removeHeader("Pragma")// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
.removeHeader("Cache-Control")
.header("Cache-Control", "public, max-age=" + maxAge)
.build();
}
};
//设置缓存路径 内置存储
//File httpCacheDirectory = new File(context.getCacheDir(), "responses");
//外部存储
File httpCacheDirectory = new File(context.getExternalCacheDir(), "responses");
//设置缓存 10M
int cacheSize = 10 * 1024 * 1024;
Cache cache = new Cache(httpCacheDirectory, cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(logging)
.addInterceptor(baseInterceptor)
.addNetworkInterceptor(rewriteCacheControlInterceptor)
.connectTimeout(15, TimeUnit.SECONDS)
.build();
return new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()
.baseUrl(BASEURL)
.client(client)
.build()
.create(MovieApiService.class);
}
}
代码中有详细的注释,下面简要说下 此代码的逻辑:
上面 提到了 OKHttpClient 拦截器的概念,可以参看 okhttp 。
代码中实现了3个拦截器(Interceptor),分别是logging 、baseInterceptor 和 rewriteCacheControlInterceptor。三个拦截器分别有不同的作用,logging 实现是把请求参数和 response 作为控制台日志方式打印出来;baseInterceptor 是根据请求环境进行不同策略的操作(比如断网环境下的策略);rewriteCacheControlInterceptor 是网络层面的操作。
1、logging
logging 是 HttpLoggingInterceptor 不再过多的介绍。
2、baseInterceptor
代码实现:
/**
* 获取缓存
*/
Interceptor baseInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!AppUtil.isNetWorkAvailable(context)) {
/**
* 离线缓存控制 总的缓存时间=在线缓存时间+设置离线缓存时间
*/
int maxStale = 60 * 60 * 24 * 28; // 离线时缓存保存4周,单位:秒
CacheControl tempCacheControl = new CacheControl.Builder()
.onlyIfCached()
.maxStale(maxStale, TimeUnit.SECONDS)
.build();
request = request.newBuilder()
.cacheControl(tempCacheControl)
.build();
Log.i(TAG, "intercept:no network ");
}
return chain.proceed(request);
}
};
其中 AppUtil.isNetWorkAvailable(context)
是判断网络是否可用的方法。此处的 maxStale 值是在无网络的情况下缓存时间。在这时间内,如果你没钱也一直没蹭到网络,则一直读取缓存的数据。
其中非常重要的一点:
离线缓存控制 总缓存时间=在线缓存时间+设置离线时的缓存时间
即 maxStale和下文提到的 maxAge 的和。
3、rewriteCacheControlInterceptor
Interceptor rewriteCacheControlInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response originalResponse = chain.proceed(request);
int maxAge = 1 * 60; // 在线缓存在1分钟内可读取 单位:秒
return originalResponse.newBuilder()
.removeHeader("Pragma")// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
.removeHeader("Cache-Control")
.header("Cache-Control", "public, max-age=" + maxAge)
.build();
}
};
注意 **如果设置了允许缓存,只有在 网络拦截器 层面,OKHttp的缓存机制才会起作用。 **
设置缓存文件路径
//设置缓存路径 内置存储
//File httpCacheDirectory = new File(context.getCacheDir(), "responses");
//外部存储
File httpCacheDirectory = new File(context.getExternalCacheDir(), "responses");
//设置缓存 10M
int cacheSize = 10 * 1024 * 1024;
Cache cache = new Cache(httpCacheDirectory, cacheSize);
代码中提到 内置存储和外部存储的问题,对一些容积比较大持久化数据,Android 官方推荐保存在外部存储空间内,通过 context.getExternalCacheDir()
的方式,获取外部存储空间的路径。可以放心的是 此路径是系统维护的路径(大概是这样子 “`/storage/emulated/0/android/data/包名/cache/“ , 在应用卸载的情况下,此目录也会随着一并删除)。
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(logging)
.addInterceptor(baseInterceptor)
.addNetworkInterceptor(rewriteCacheControlInterceptor)
.connectTimeout(15, TimeUnit.SECONDS)
.build();
注意 rewriteCacheControlInterceptor 要通过 addNetworkInterceptor()添加,否则缓存会有异常.