前言
本文重点讲述Http缓存在OkHttp中的实现,同时也分析了Retrofit+ OkHttp是如何实现网络请求的。
本文基于OkHttp3.9.0及Retrofit2.3.0
你猜的没错,本文很长很长
了解Http缓存机制再看本文更佳,这里可以看看我的《不一样的HTTP缓存体验》。
本文为了更好地理清思路,夹杂其它的源码分析,如RxJava。
分析
简单使用
上文也讲述了在Android中的使用,这里再简单回顾一遍。
- 设置缓存策略
1 | @Headers("Cache-Control: public, max-age=300")//缓存时间为5分钟 |
- 设置缓存目录
1 | Cache cache = new Cache(new File(context.getExternalCacheDir(), CacheConstant.CACHE_DIR_API), 20 * 1024 * 1024); |
那么,OkHttp是如何拿到Retrofit的Header,又是如何根据我们的缓存策略进行缓存的,又是如何保存在我们的缓存目录。预知后事如何,请看下文分解。
第一问
Retrofit源码比较短,但是精辟!最核心的肯定是动态代理。这里简单分析是如何拿到Header然后传给OkHttp。
这是我们的常规写法:
1 | mGankioService = new Retrofit.Builder() |
那么主要就是构造方法的client和create方法。跟进:
1 | //Retrofit |
OK,简单的赋值,再来看看create方法。
1 | //Retrofit |
loadServiceMethod方法肯定是解析方法参数,然后一堆赋值什么的,不妨进去看看:
1 | //Retrofit |
到这里已经结束了,因为已经产生了headers,如何产生以及架构设计并不是我们关心的。Retrofit生产了headers,那么得给OkHttp消费,回顾Retrofit的create方法,Retrofit会把ServiceMethod传入OkHttpCall,而OkHttpCall只是一个网络操作接口,那么其实不难猜到具体的还是ServiceMethod去完成。毕竟ServiceMethod手握Retrofit,而Retrofit又有最重要的OkHttp。而最后一句无非只是适配,比如我们可以用RxJava。
1 | //Retrofit |
OK,我知道大家很好奇这是怎么回事,其实并不难,如果用RxJava,是不是subscribe貌似就可以进行网络请求了?有看过源码吗?
1 | //Observable |
既然是适配,那么核心方法肯定在adapt中,这里以上文添加的适配器RxJava2CallAdapter为例。
1 | //RxJava2CallAdapter |
这里挑选同步的查看:
1 | //CallExecuteObservable |
这时候心应该平静下来了,但这只是个开始,我们的核心缓存还并没有讲。我们再回过神来去看看execute方法。其实如果直接用OkHttp就没有这么多事。
1 | //OkHttpCall |
没有ServiceMethod?这不可能,跟进createRawCall:
1 | //OkHttpCall |
这两句很重要,第一句是我们分析了半天的header的封装,第二句是我们的主角OkHttp。简单看一下:
1 | //ServiceMethod |
至此,我们解决了第一个问题,OkHttp是如何拿到Retrofit的Header。
第二问
接下来我们来分析第二问,OkHttp是如何根据我们的缓存策略进行缓存的。
链接上文OkHttpCall的execute方法,最后其实调了okhttp3.Call的execute方法。
1 | //OkHttpCall |
execute方法最关键肯定是这里了,得到我们的结果,那么处理请求以及解析等等都在这了。跟进getResponseWithInterceptorChain方法。
1 | //RealCall |
OK,如果仔细阅读的同学,不难知道originalRequest其实就是serviceMethod封装的Request。
1 | Request request = serviceMethod.toRequest(args); |
在分析拦截器链执行前,我们要搞清楚4个东西。
- client.interceptors()
- CacheInterceptor
- ConnectInterceptor
- client.networkInterceptors()
1 | //OkHttpClient |
OK,我想你已经了解了。而关于CacheInterceptor我们先放放,因为这是主角,ConnectInterceptor是扩展知识,到时候会简单介绍。
我们再来玩玩RealInterceptorChain。
1 | //RealInterceptorChain |
常规操作,一堆赋值。
1 | //RealInterceptorChain |
OK,也很简单,每次取一个拦截器,然后调用其intercept方法,而这个请求链的关键就是在intercept调用了chain.proceed,如果不调用了,也就不会执行下一个拦截器。既然我们已经了解这个机制,就了解了首先它会执行我们设置的Interceptor,然后调用CacheInterceptor,接下来是ConnectInterceptor,最后是NetworkInterceptor,而如何打断,我相信你也清楚了。
我们详细地来看看CacheInterceptor吧。
1 | //CacheInterceptor |
下面的一系列代码都跟这几个有疑问的变量挂钩,如果不理解是没法玩了。我们一个一个来解答。
首先是client.internalCache(),
1 | //OkHttpClient |
我们貌似写过这样的代码:
1 | Cache cache = new Cache(new File(context.getExternalCacheDir(), CacheConstant.CACHE_DIR_API), 20 * 1024 * 1024); |
会不会有关系呢?走进去看看:
1 | //OkHttpClient.Builder |
现在我们要明白的是Cache这个类到底干嘛用的,internalCache这个变量又是怎么产生的。
1 | //Cache |
好吧,真相大白,然后再了解下数据结构就差不多了。
1 | //Cache |
再来看看关键的internalCache。
1 | //Cache |
好吧,其实都是调Cache的方法,而Cache又是DiskLruCache去处理的。大致了解到这儿。到这儿我们已经了解到cacheCandidate就是我们的本地缓存。
那么,理解CacheStrategy就成了关键点。我们根据这句代码一步一步去了解:
1 | //CacheInterceptor |
1 | //CacheStrategy.Factory |
如果看过我写的Http缓存机制《不一样的HTTP缓存体验》肯定对这几个很熟悉,Expires、Last-Modified和ETag等等。那么我们可以大胆地猜测这个类是真正的缓存策略管理类。
OK,再看看get方法。
1 | //CacheStrategy.Factory |
貌似还不能下结论,再走进getCandidate方法:
1 | //CacheStrategy.Factory |
OK,再来看看构造函数:
1 | //CacheStrategy |
是的,就这么简单。再来回顾get方法。
1 | //CacheStrategy.Factory |
跟我们想的一样,这个类遵循Http的缓存机制。现在我们可以继续阅读CacheInterceptor下面的代码了。
1 | //CacheInterceptor |
到这里Http的缓存机制算是结束了,我们得知是遵循Http协议的,因此大体流程依然可以看我的《不一样的HTTP缓存体验》,这里就不画流程图了,偷个懒。
第三问
第二问的分析已经为我们铺垫了许多,例如缓存的底层用的是DiskLruCache。
第二问我们记下了如下代码:
1 | //CacheInterceptor |
先来看更新缓存:
1 | //Cache |
额,其实是一脸懵逼的。因为不知道Entry和DiskLruCache的关系。可能这个你得再了解一下Okio,到时候我有空会写一篇介绍。反正这里就是会去除原来缓存的内容然后重新写入。
我们最后看一下正常写入吧。
1 | //Cache |
可能有同学好奇何时建立缓存文件的。看看这个应该就清除了。
1 | //Cache |
我觉得你已经搞清楚了。
扩展
为啥要扩展一下,讲解ConnectInterceptor呢?主要还是这张图:
我们简单过一遍。
1 | //ConnectInterceptor |
到这里差不多是网络较差的情况,也就是无法正常连接网络,就会抛出异常,一般来说dns是系统实现,看不了。而抛出异常就会打断拦截链导致后续的Interceptor无法执行,这也证明了NetworkInterceptor为啥只有网络正常时才会执行,当然还有各种情况,比如刚刚我们分析的,用了缓存,那么肯定是不会执行的。
总结
我们分析了一遍OkHttp的缓存实现,由于其遵守HTTP协议,我就不画流程图了,具体实现的理解可以跟着我的思路自己再看一遍源码,另外,OkHttp的源码可不止这些,即使全部看懂了,也没什么用,最重要的还是创意。真的很佩服这些大牛。