前言
–
在 Android 开发过程中,Retrofit 的出现绝对是里程碑式的,说起 Retrofit 不得不说 square,这绝对是家l好公司,业界良心。
回到今天的主题,OKHttp3 网络层 缓存实现与分析。有人可能会说,你这怎么又扯到 OKHttp3 了,好了废话不扯。
完整项目 托管在 github 上,直达电梯:豆瓣电影客户端 github
代码分析
本文主要还是关于实现层面的讲解,只是豆瓣电影客户端其中一个功能。先贴代码(经过测试的代码)。
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;
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;
return originalResponse.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", "public, max-age=" + maxAge)
.build();
}
};
File httpCacheDirectory = new File(context.getExternalCacheDir(), "responses");
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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
代码中有详细的注释,下面简要说下 此代码的逻辑:
上面 提到了 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;
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);
}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
其中 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;
return originalResponse.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", "public, max-age=" + maxAge)
.build();
}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
注意 如果设置了允许缓存,只有在 网络拦截器 层面,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()添加,否则缓存会有异常.