澳门皇冠金沙网站-澳门皇冠844网站

热门关键词: 澳门皇冠金沙网站,澳门皇冠844网站

源码解读之离线缓存策略的实现,Okhttp基本用法

相关代码已上传至 GitHub,已开源库,请移步:| 优雅的给 Retrofit 加上缓存 RetrofitCache

最近在写一个信息流的项目,整个架构是基于 MVP Retrofit Rxjava 实现的,由于刚刚使用 RxJava Retrofit,对它理解不深,所以在一开始做数据缓存的时候还是用常规思维来设计的。

本文基于compile 'com.squareup.okhttp3:okhttp:3.0.1'

Retrofit 是square公司开发的一款网络框架,也是至今Android网络请求中最火的一个,配合OkHttp RxJava Retrofit三剑客更是如鱼得水,公司项目重构时,我也在第一时间使用了RxJava Retrofit,使用过程中遇到的一些问题,也会在后续的博客中,一点点分享出来,供大家参考!

想到的缓存处理方式:

图片 1

在项目的过程中,项目需求需要在离线的情况下能够继续浏览app内容,第一时间想到缓存,于是经过各种google搜索,得出以下结论(使用Retrofit 2.0)

  • 使用 sharedpreferences
  • 使用 SqLite 数据库

OkHttp的时序图

-参考stackoverflow地址 ,Retrofit 2.0开始,底层的网络连接全都依赖于OkHttp,故要设置缓存,必须从OkHttp下手

但是有一个问题:

1.基本用法


这里我是将Okhttp和Retrofit一起使用的
Okhttp执行底层的网络请求,Retrofit负责网络调度,有关Retrofit的更多可参考我的另一篇博文:Retrofit基本用法和流程分析
1.初始化

    public static void init() {
        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        logging.setLevel(HttpLoggingInterceptor.Level.BODY);

        sClient = new OkHttpClient.Builder()
                .retryOnConnectionFailure(true)
                .connectTimeout(15, TimeUnit.SECONDS)
                .readTimeout(300, TimeUnit.SECONDS)
                .writeTimeout(300, TimeUnit.SECONDS)
                .cache(new Cache(Constants.HTTP_CACHE_DIR, Constants.CACHE_SIZE))
                .addInterceptor(logging)//第三方的日志拦截器
                .addInterceptor(appIntercepter)//自定义的应用拦截器
                .addNetworkInterceptor(netIntercepter)//自定义的网络拦截器
                .build();
    }

2.添加应用拦截器

    //应用拦截器:主要用于设置公共参数,头信息,日志拦截等,有点类似Retrofit的Converter
    private static Interceptor appIntercepter = new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = processRequest(chain.request());
            Response response = processResponse(chain.proceed(request));
            return response;
        }
    };

    //访问网络之前,处理Request(这里统一添加了Cookie)
    private static Request processRequest(Request request) {
        String session = CacheManager.restoreLoginInfo(BaseApplication.getContext()).getSession();
        return request
                .newBuilder()
                .addHeader("Cookie", "JSESSIONID="   session)
                .build();
    }

    //访问网络之后,处理Response(这里没有做特别处理)
    private static Response processResponse(Response response) {
        return response;
    }

这里再奉上一个缓存拦截器:离线读取本地缓存,在线获取最新数据(读取单个请求的请求头,亦可统一设置)。
这里用到了一个网络状态工具类

    //应用拦截器:设置缓存策略
    private static Interceptor cacheIntercepter = new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();

            //无网的时候强制使用缓存
            if (NetUtil.getNetState() == NetUtil.NetState.NET_NO) {
                request = request.newBuilder()
                        .cacheControl(CacheControl.FORCE_CACHE)
                        .build();
            }

            Response response = chain.proceed(request);

            //有网的时候读接口上的@Headers里的配置,你可以在这里进行统一的设置
            if (NetUtil.getNetState() != NetUtil.NetState.NET_NO) {
                String cacheControl = request.cacheControl().toString();
                return response.newBuilder()
                        .header("Cache-Control", cacheControl)
                        .removeHeader("Pragma")
                        .build();
            } else {
                int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
                return response.newBuilder()
                        .header("Cache-Control", "public, only-if-cached, max-stale="   maxStale)
                        .removeHeader("Pragma")
                        .build();
            }
        }

    };

OkHttp3中有一个Cache类是用来定义缓存的,此类详细介绍了几种缓存策略,具体可看此类源码。

  1. noCache :不使用缓存,全部走网络
  1. noStore : 不使用缓存,也不存储缓存
  2. onlyIfCached : 只使用缓存
  3. maxAge :设置最大失效时间,失效则不使用
  4. maxStale :设置最大失效时间,失效则不使用
  5. minFresh :设置最小有效时间,失效则不使用
  6. FORCE_NETWORK : 强制走网络
  7. FORCE_CACHE :强制走缓存

关于max-age和max-stale
max-stale在请求头设置有效,在响应头设置无效。
max-stale和max-age同时设置的时候,缓存失效的时间按最长的算。

我这里借用了别人的一个测试(太懒了,你有兴趣可以自己测试下):

测试结果:
在请求头中设置了:Cache-Control: public, max-age=60,max-stale=120,响应头的Cache-Control和请求头一样。

  1. 在第一次请求数据到一分钟之内,响应头有:Cache-Control: public, max-age=60,max-stale=120
  2. 在1分钟到3分钟在之间,响应头有:Cache-Control: public, max-age=60,max-stale=120
    Warning: 110 HttpURLConnection "Response is stale"
    可以发现多了一个Warning。
  3. 三分钟的时候:重新请求了数据,如此循环,如果到了重新请求的节点此时没有网,则请求失败。
    另外关于缓存有一个rxcache也可以试试。

3.添加网络拦截器

    //网络拦截器:主要用于重试或重写
    private static Interceptor netIntercepter = new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Response response = chain.proceed(request);
            int tryCount = 0;
            while (!response.isSuccessful() && tryCount < sMaxTryCount) {
                tryCount  ;
                response = chain.proceed(request);
            }
            return response;
        }
    };

关于OkHttp的拦截机制,我觉得这是OkHttp最牛逼的地方之一!
先给大家看个概览图,之后会在OkHttp的特性中详细介绍下。

图片 2

OkHttp的拦截机制

4.简单的异步请求

    private static void testAsync() {
        Request request = new Request.Builder()
                .url("http://baidu.com")
                .build();
        sClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                LogUtil.print(e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (!response.isSuccessful()) throw new IOException("Unexpected code "   response);
                Headers responseHeaders = response.headers();
                for (int i = 0, size = responseHeaders.size(); i < size; i  ) {
                    LogUtil.print(responseHeaders.name(i)   ": "   responseHeaders.value(i));
                }
                LogUtil.print(response.body().string());
            }
        });
    }

-具体的使用过程为:1.先开启OkHttp缓存

  • Retrofit RxJava 之所以强大,有一点,是可以直接将返回的JSON数据转化为我们的 JavaBean 对象,直接操作。
  • 如果使用常规方式处理,我们只是缓存JSON数据,在操作的时候还是要通过GSON转化为对象。
  • 那这样,我们就没有体现出 Retrofit 的强大之处,所以我想如果Retrofit能够做缓存处理就好了。

2.OkHttp的特性


Q1:为什么使用OkHttp?

  1. 支持HTTP2/SPDY黑科技
  1. socket自动选择最好路线,并支持自动重连
  2. 拥有自动维护的socket连接池,减少握手次数
  3. 拥有队列线程池,轻松写并发
  4. 拥有Interceptors轻松处理请求与响应(比如透明GZIP压缩,LOGGING)
  5. 基于Headers的缓存策略

Q2:应用拦截器和网络拦截器的区别?

addInterceptor:设置应用拦截器,主要用于设置公共参数,头信息,日志拦截等
addNetworkInterceptor:设置网络拦截器,主要用于重试或重写

图片 3

OkHttp的应用拦截器和网络拦截器

例如:初始请求http://baidu.com 会重定向1次到https://baidu.com 。
则应用拦截器会执行1次,返回的是https的响应
网络拦截器会执行2次,第一次返回http的响应,第二次返回https的响应

应用拦截器:

  1. 不需要担心中间过程的响应,如重定向和重试.
  2. 总是只调用一次,即使HTTP响应是从缓存中获取.
  3. 观察应用程序的初衷. 不关心OkHttp注入的头信息如: If-None-Match.
  4. 允许短路而不调用 Chain.proceed(),即中止调用.
  5. 允许重试,使 Chain.proceed()调用多次.

网络拦截器

  1. 能够操作中间过程的响应,如重定向和重试.
  2. 当网络短路而返回缓存响应时不被调用.
  3. 只观察在网络上传输的数据.
  4. 携带请求来访问连接.
File httpCacheDirectory = new File(UIUtils.getContext().getExternalCacheDir(), "responses");client.setCache(new Cache(httpCacheDirectory,10 * 1024 * 1024));

这里要吐槽一句,网上关于 Retrofit 和 RxJava 的相关资料真的很少,而且大部分都是重复或只写了一个片段,但功夫不负有心人,还是找到了一些解决方法。

3.OkHttp的流程分析


二话不说,先上几张图:

图片 4

OkHttp的请求流程

图片 5

OkHttp的总体设计

1.先看初始化时,通过建造者模式创建OkHttpClient

图片 6

图片 7

2.再看执行请求时,创建Request的方法

图片 8

图片 9

3.再看异步请求Request的方法,先通过newCall(),将Request转成Call

图片 10

图片 11

4.然后加入请求队列

图片 12

5.Call.enqueue()是抽象方法,实现在这:

图片 13

6.调用了Dispatcher.enqueue(),通过一个线程池来执行,超过最大请求数后则先加入准备请求的队列中

图片 14

7.这里的call是一个AsynCall,继承自NamedRunnable

图片 15

8.在NamedRunnable的run()方法中,调用了execute()抽象方法,于是我们去找它的实现类AsyncCall的execute()方法

图片 16

9.调用了getResponseWithInterceptorChain()方法

图片 17

10.调用了ApplicationInterceptorChain.proceed(),如果有其他的应用拦截器的话,就会遍历拦截器集合,执行每一个拦截的intercept()方法

而通过前面的自定义应用拦截器,我们知道intercept()中其实也会调用proceed(),这样迭代多次后,最终还是会执行getResponse方法()

图片 18

13.getResponse()这个方法有点长,一次截图截不完

图片 19

图片 20

图片 21

14.调用httpEngine.sendRequest()方法,这个方法有点长,一次截图截不完

图片 22

图片 23

图片 24

15.先从 Cache 中判断当前请求是否可以从缓存中返回

图片 25

16.没有Cache则连接网络

图片 26

图片 27

17.调用streamAllocation.newStream()

图片 28

18.寻找可用的socket连接

图片 29

图片 30

19.连接到socket连接层

图片 31

20.创建了FramedConnection的实例

图片 32

图片 33

21.调用了Reader的execute()

图片 34

苍天!越往下面越难找到头绪了,先暂且告一段落吧,以后再来好好深入。

我们可以看到 先获取系统外部存储的缓存路径,命名为response,此文件夹可以在android/data/<包名>/cache/resposes看到里面的内容,具体OkHttp是如何做到离线缓存的呢?

有一篇文章是这样说的:

参考目录:

  1. 带你学开源项目:OkHttp--自己动手实现okhttp
  2. Okhttp-wiki 之 Interceptors 拦截器
  3. OkHttp3源码分析[综述]
  4. OKHttp源码解析
  5. OkHttp使用教程
  6. OKHttp源码浅析与最佳实践
  7. OkHttp官方文档
  8. OkHttp3之Cookies管理及持久化
  9. Retrofit2.0使用总结及注意事项
  10. 使用Retrofit和Okhttp实现网络缓存。无网读缓存,有网根据过期时间重新请求

我们进入Cache类,有重大发现,首先是它的注释,极其详细

减少服务器负荷,降低延迟提升用户体验。复杂的缓存策略会根据用户当前的网络情况采取不同的缓存策略,比如在2g网络很差的情况下,提高缓存使用的时间;不用的应用、业务需求、接口所需要的缓存策略也会不一样,有的要保证数据的实时性,所以不能有缓存,有的你可以缓存5分钟,等等。你要根据具体情况所需数据的时效性情况给出不同的方案。当然你也可以全部都一样的缓存策略,看你自己。

Caches HTTP and HTTPS responses to the filesystem so they may be reused, saving time and bandwidth.Cache OptimizationTo measure cache effectiveness, this class tracks three statistics:Request Count: the number of HTTP requests issued since this cache was created.Network Count: the number of those requests that required network use.Hit Count: the number of those requests whose responses were served by the cache.Sometimes a request will result in a conditional cache hit. If the cache contains a stale copy of the response, the client will issue a conditional GET. The server will then send either the updated response if it has changed, or a short 'not modified' response if the client's copy is still valid. Such responses increment both the network count and hit count.The best way to improve the cache hit rate is by configuring the web server to return cacheable responses. Although this client honors all HTTP/1.1  cache headers, it doesn't cache partial responses.Force a Network ResponseIn some situations, such as after a user clicks a 'refresh' button, it may be necessary to skip the cache, and fetch data directly from the server. To force a full refresh, add the no-cache directive: Request request = new Request.Builder() .cacheControl(new CacheControl.Builder().noCache) .url("http://publicobject.com/helloworld.txt") .build();If it is only necessary to force a cached response to be validated by the server, use the more efficient max-age=0 directive instead: Request request = new Request.Builder() .cacheControl(new CacheControl.Builder() .maxAge(0, TimeUnit.SECONDS) .build .url("http://publicobject.com/helloworld.txt") .build(); Force a Cache ResponseSometimes you'll want to show resources if they are available immediately, but not otherwise. This can be used so your application can show something while waiting for the latest data to be downloaded. To restrict a request to locally-cached resources, add the only-if-cached directive: Request request = new Request.Builder() .cacheControl(new CacheControl.Builder() .onlyIfCached() .build .url("http://publicobject.com/helloworld.txt") .build(); Response forceCacheResponse = client.newCall.execute(); if (forceCacheResponse.code { // The resource was cached! Show it. } else { // The resource was not cached. } This technique works even better in situations where a stale response is better than no response. To permit stale cached responses, use the max-stale directive with the maximum staleness in seconds: Request request = new Request.Builder() .cacheControl(new CacheControl.Builder() .maxStale(365, TimeUnit.DAYS) .build .url("http://publicobject.com/helloworld.txt") .build(); The CacheControl class can configure request caching directives and parse response caching directives. It even offers convenient constants CacheControl.FORCE_NETWORK and CacheControl.FORCE_CACHE that address the use cases above.
  • 在响应请求之后在 data/data/<包名>/cache 下建立一个response 文件夹,保持缓存数据。
  • 这样我们就可以在请求的时候,如果判断到没有网络,自动读取缓存的数据。
  • 同样这也可以实现,在我们没有网络的情况下,重新打开App可以浏览的之前显示过的内容。
  • 也就是:判断网络,有网络,则从网络获取,并保存到缓存中,无网络,则从缓存中获取。

文档详细说明了此类的作用,支持OkHttp直接使用缓存,然后罗列出了各种具体的用法,可惜的是我们这里使用的是Retrofit,无法直接用OkHttp;但是如果有直接用OkHttp的童鞋们,可以根据上面的提示,完成具体的缓存操作,so easy !。

  1. 先开启OkHttp缓存

    在Retrofit2.0版本之后,Retrofit底层自动依赖了OkHttp,所以我们不用重复依赖Okhttp了

    File httpCacheDirectory = new File(MyApp.mContext.getCacheDir(), "responses");int cacheSize = 10 * 1024 * 1024; // 10 MiBCache cache = new Cache(httpCacheDirectory, cacheSize);OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(REWRITE_CACHE_CONTROL_INTERCEPTOR) .cache.build();
    

    这一步是设置缓存路径,以及缓存大小,其中addInterceptor是我们第二步的内容。

  2. 设置 OkHttp 拦截器

    主要是拦截操作,包括控制缓存的最大生命值,控制缓存的过期时间

    两个操作都是在 Interceptor 中进行的

    • 通过 CacheControl 控制缓存数据

      CacheControl.Builder cacheBuilder = new CacheControl.Builder();cacheBuilder.maxAge(0, TimeUnit.SECONDS);//这个是控制缓存的最大生命时间cacheBuilder.maxStale(365,TimeUnit.DAYS);//这个是控制缓存的过时时间CacheControl cacheControl = cacheBuilder.build();
      

本文由澳门皇冠金沙网站发布于编辑程序,转载请注明出处:源码解读之离线缓存策略的实现,Okhttp基本用法