CacheInterceptor源码分析

开头

首先我们回忆下在Okhttp中如何使用缓存:

   //缓存目录
File file = new File(getCacheDir(), "http");
//创建缓存,这里指定了缓存为100M,如果查出就情况该目录
Cache cache = new Cache(file, 1024 * 1024 * 100);
//然后使用cache方法添加一个cache
OkHttpClient okHttpClient = new OkHttpClient.Builder()
    .cache(cache)
    .addNetworkInterceptor(FORCE_CACHE_NETWORK_DATA_INTERCEPTOR)
    .build();
Request request = new Builder()
    .url("https://api.github.com/users/lifengsofts")
//            .url("http://me.woblog.cn/")
    .build();
try {
  Response response = okHttpClient.newCall(request).execute();
  if (response.isSuccessful()) {
    Log.d(TAG, "response: " + bodyString(response));
    //缓存的response
    //使用了缓存,他返回null
    Log.d(TAG, "networkResponse: " + bodyString(response.networkResponse()));
    //如果没有缓存,或者不适用缓存,缓存过期返回null
    Log.d(TAG, "cacheResponse: " + bodyString(response.cacheResponse()));
  } else {
    Log.d(TAG, "error1: " + response.code());
  }
} catch (IOException e) {
  e.printStackTrace();
}

可以发现首先需要配置缓存目录,缓存大小,然后就按照正常的方法请求网络,这样就开启了默认缓存策略。

同时我们还可以配置单个请求的缓存:

   //强制使用缓存
request = new Builder()
    .url("https://api.github.com/users/lifengsofts")
    .cacheControl(CacheControl.FORCE_CACHE)
    .build();
    
//强制使用网络
request = new Builder()
  .url("https://api.github.com/users/lifengsofts")
  .cacheControl(CacheControl.FORCE_NETWORK)
  .build();

后面我们就基于上面的使用入门来分析Okhttp的缓存实现。

RealCall#getResponseWithInterceptorChain

   Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    //添加了OkhttpClient在初始化添加的拦截器
    //前面我们使用的日志拦截器就添加到这里的
    interceptors.addAll(client.interceptors());
    //重试,重定向
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //处理缓存的拦截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
        interceptors.addAll(client.networkInterceptors());
    }
    //在这个拦截器才发起请求网络的请求
    interceptors.add(new CallServerInterceptor(forWebSocket));

    //创建一个拦截器链,并传入了所有的拦截器。
    Interceptor.Chain chain = new RealInterceptorChain(
            interceptors, null, null, null, 0, originalRequest, this, eventListener);
    //最后调用了拦截器链的proceed来处理
    //在这里我们可以才到拦截器的调用应该就在改方法里面
    return chain.proceed(originalRequest);
}

在getResponseWithInterceptorChain方法中我们看到添加CacheInterceptor时传入了client.internalCache(),我们先来看看client.internalCache()是什么:

2.点击跳转

-->点击跳转: internalCache
   InternalCache internalCache() {
  return cache != null ? cache.internalCache : internalCache;
}

//这个cache是什么: Cache对象,是一个成员变量
从哪里传递进来到的呢?

通过源码可以看到是Builder中的Cache方法传递进来的

    public Builder cache(@Nullable Cache cache) {
      this.cache = cache;
      //这里把原来的OkhttpClien.Builder中的internalCache置为null
      //从上面的internalCache方法可以看出,既然调用了cache,那么就用不
      //internalCachel了,用的是cache.internalCache
      this.internalCache = null;
      return this;
    }

  /** Sets the response cache to be used to read and write cached     responses. */
  //这个setInternalCache可以不用关心在哪里调用的,
    void setInternalCache(@Nullable InternalCache internalCache) {
      this.internalCache = internalCache;
      this.cache = null;
    }
    
   ====================================================
   
  --》回到方法调用原处,并点击跳转到CacheInterceptor
  
  public final class CacheInterceptor implements Interceptor {
    //InternalCache 是个接口在哪里实现了这个接口呢,可以查看Cache类里面实现了这个接口
    final InternalCache cache;

    public CacheInterceptor(InternalCache cache) {
        this.cache = cache;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        //首先根据cache看看有没有缓存的response
        Response cacheCandidate = cache != null
                ? cache.get(chain.request())
                : null;

        long now = System.currentTimeMillis();

        //然后根据当前时间戳,request,上次缓存的response创建一个cache策略
        CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
        //根据这个策略用网络的请求还是用缓存(逻辑放到这CacheStrategy策略里面去了)
        Request networkRequest = strategy.networkRequest;
        Response cacheResponse = strategy.cacheResponse;//上次缓存的response

        if (cache != null) {
           //这里其实是一个监听器,可以跟踪缓存的执行流程
            cache.trackResponse(strategy);
        }

        if (cacheCandidate != null && cacheResponse == null) {
            closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
        }

        // 如果禁止使用网络,同时获取的缓存也为空,就返回一个504错误
        //在学习时,说到这种情况,会得到504响应,那个504就是在这个地方返回的
        if (networkRequest == null && cacheResponse == null) {
            return new Response.Builder()
                    .request(chain.request())
                    //设置协议
                    .protocol(Protocol.HTTP_1_1)
                    //设置错误码
                    .code(504)
                    .message("Unsatisfiable Request (only-if-cached)")
                    .body(Util.EMPTY_RESPONSE)
                    //发送request的时间为 -1L
                    .sentRequestAtMillis(-1L)
                    //接收response的时间是当前的时间
                    .receivedResponseAtMillis(System.currentTimeMillis())
                    .build();
        }

        // 如果配置的缓存不需要网络,那就可以直接返回了,返回的body是空
        // 这个可以理解为:如果networkrequest为空,表示不需要请求网络,所以说就返回缓存的响应
        if (networkRequest == null) {
            return cacheResponse.newBuilder()
                     //-->点击stripBody详情看下面
                     //stripBody:缓存可能没空,所以说就用这个方法处理一下
                    .cacheResponse(stripBody(cacheResponse))
                    .build();
        }

        Response networkResponse = null;
        try {
            //调用下一个拦截器
            networkResponse = chain.proceed(networkRequest);
        } finally {
            // If we're crashing on I/O or otherwise, don't leak the cache body.
            if (networkResponse == null && cacheCandidate != null) {
                //关闭这个流
                closeQuietly(cacheCandidate.body());
            }
        }

        // 如果有缓存的响应,那需要做进一步处理,比如:更新响应头
        if (cacheResponse != null) {
            if (networkResponse.code() == HTTP_NOT_MODIFIED) {//304
                 //这个值没有修改的话
                //这里主要就是将缓存的头和网络响应头组合
                Response response = cacheResponse.newBuilder()
                        //将缓存的headers和网络的headers进行一个合并
                        -->跳转到combine
                        .headers(combine(cacheResponse.headers(), networkResponse.headers()))
                        .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
                        .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
                        .cacheResponse(stripBody(cacheResponse))
                        .networkResponse(stripBody(networkResponse))
                        .build();
                networkResponse.body().close();

                // Update the cache after combining headers but before stripping the
                // Content-Encoding header (as performed by initContentStream()).
                cache.trackConditionalCacheHit(); //用来跟踪缓存命中率,后面分析cache实现在分析
                 //关键点是这个  将cacheResponse(上次缓存的response)和response(本次缓存的)更新了
                //分析的逻辑,后面再分析
                --》跳转cache.update
                //cache:  InternalCache cache是一个接口,实现类在Cache中,
                --》跳转到Cache类
                cache.update(cacheResponse, response);
                return response;
            } else {
                closeQuietly(cacheResponse.body());
            }
        }
        //从if可以看出,走到了这一步,说明缓存内容为空,那么这个时候就需要去请求网络了

        Response response = networkResponse.newBuilder()
                .cacheResponse(stripBody(cacheResponse))
                .networkResponse(stripBody(networkResponse))
                .build();

        if (cache != null) {
            if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
                // 如果当前这个http有返回的body,并且可以缓存,那就缓存他
                CacheRequest cacheRequest = cache.put(response);
                //cacheWritingResponse:将缓存的response写入到文件里面
                //-->点击跳转:cacheWritingResponse
                return cacheWritingResponse(cacheRequest, response);
            }

            //如果http不能缓存,也就是像post,PATCH等这类请求不能缓存,就移除缓存
            if (HttpMethod.invalidatesCache(networkRequest.method())) {
                try {
                    cache.remove(networkRequest);
                } catch (IOException ignored) {
                    // The cache cannot be written.
                }
            }
        }
        //走了这一步,这个response不管是从本地还是网络,都是有值的
        return response;
    }

--》点击跳转:Cache类
   public final class Cache implements Closeable, Flushable {
    private static final int VERSION = 201105;
    private static final int ENTRY_METADATA = 0;
    private static final int ENTRY_BODY = 1;
    private static final int ENTRY_COUNT = 2;

    //可以发现他将真正的实现代理到外面的类,这个也和桥接类似,将实现交给他人
    //这样CacheInterceptor就不用直接依赖他的实现类
    final InternalCache internalCache = new InternalCache() {
        @Override
        public Response get(Request request) throws IOException {
            //这里调用Cache外层类的方法
            return Cache.this.get(request);
        }

        @Override
        public CacheRequest put(Response response) throws IOException {
            return Cache.this.put(response);
        }

        @Override
        public void remove(Request request) throws IOException {
            Cache.this.remove(request);
        }

        @Override
        public void update(Response cached, Response network) {
            Cache.this.update(cached, network);
        }

        @Override
        public void trackConditionalCacheHit() {
            Cache.this.trackConditionalCacheHit();
        }

        @Override
        public void trackResponse(CacheStrategy cacheStrategy) {
            Cache.this.trackResponse(cacheStrategy);
        }
    };

--》点击跳转:stripBody
        //缓存可能没空,所以说就用这个方法处理一下
        private static Response stripBody(Response response) {
        return response != null && response.body() != null
                ? response.newBuilder().body(null).build()
                : response;
    }

跳转到combine


    /**
     * Combines cached headers with a network headers as defined by RFC 2616, 13.5.3.
     *
     * networkHeaders: 就上面通过这个方法chain.proceed(networkRequest)获取到的的response
     */
    private static Headers combine(Headers cachedHeaders, Headers networkHeaders) {
        //最终的header存到这里
        Headers.Builder result = new Headers.Builder();

        //先遍历cachedHeaders,也就说说,后面的networkHeaders会覆盖相同的字段
        for (int i = 0, size = cachedHeaders.size(); i < size; i++) {
            //拿到名称和值
            String fieldName = cachedHeaders.name(i);
            String value = cachedHeaders.value(i);
            //删除一些请求头
            if ("Warning".equalsIgnoreCase(fieldName) && value.startsWith("1")) {
                continue; // Drop 100-level freshness warnings.
            }

            //如果不是端到端,或者networkHeaders中不存在的头添加到结果header中
            //所谓端到端简单理解为,如果有一个代理,有些请求头只从客户端发给代理,代理不用发给服务端的头

            //fieldName:就是请求头里面的key,
            //-->跳转:isEndToEnd(fieldName)
            if (!isEndToEnd(fieldName) || networkHeaders.get(fieldName) == null) {
                //Internal是个抽象类,它的子类在OkhttpClient static静态代码中
                //其实最终是添加到Headers.Builder中的集合中的
                //查看代码知道: builder.addLenient(name, value);--》
                /*
                   Builder addLenient(String name, String value) {
                   namesAndValues.add(name);
                   namesAndValues.add(value.trim());
                   return this;

                   而namesAndValues是 Headers.Builder里面的List<String>集合

                   其实是:把fieldName和value添加到result(Headers.Builder:上面创建的局部变量)中的
    }
                 */
                Internal.instance.addLenient(result, fieldName, value);
            }
        }

        //遍历网络请求头
        //网络请求也是上面的逻辑
        //networkHeaders: 就上面通过这个方法chain.proceed(networkRequest)获取到的的response
        for (int i = 0, size = networkHeaders.size(); i < size; i++) {
            String fieldName = networkHeaders.name(i);
            if ("Content-Length".equalsIgnoreCase(fieldName)) {
                //这种Content-Length直接跳过了,不用添加
                continue; // Ignore content-length headers of validating responses.
            }
            if (isEndToEnd(fieldName)) {
                //result: Headers.Builder
                Internal.instance.addLenient(result, fieldName, networkHeaders.value(i));
            }
        }

        return result.build();
    }

-->跳转:isEndToEnd(fieldName)
    /**
     * Returns true if {@code fieldName} is an end-to-end HTTP header, as defined by RFC 2616,
     * 13.5.1.
     * 
     * fieldName:就是请求头里面的key
     */
    static boolean isEndToEnd(String fieldName) {
        //如果请求头里面的key是下面的这些值,那么就是端到端
        return !"Connection".equalsIgnoreCase(fieldName)
                && !"Keep-Alive".equalsIgnoreCase(fieldName)
                && !"Proxy-Authenticate".equalsIgnoreCase(fieldName)
                && !"Proxy-Authorization".equalsIgnoreCase(fieldName)
                && !"TE".equalsIgnoreCase(fieldName)
                && !"Trailers".equalsIgnoreCase(fieldName)
                && !"Transfer-Encoding".equalsIgnoreCase(fieldName)
                && !"Upgrade".equalsIgnoreCase(fieldName);
    }

跳转到Cache# InternalCache# update

     --》跳转到Cache类
 可以看到internalCache 接口的实现类InternalCache
public final class Cache implements Closeable, Flushable {
    private static final int VERSION = 201105;
    private static final int ENTRY_METADATA = 0;
    private static final int ENTRY_BODY = 1;
    private static final int ENTRY_COUNT = 2;

    //可以发现他将真正的实现代理到外面的类,这个也和桥接类似,将实现交给他人
    //这样CacheInterceptor就不用直接依赖他的实现类
    final InternalCache internalCache = new InternalCache() {
        @Override
        public Response get(Request request) throws IOException {
            return Cache.this.get(request);
        }

        @Override
        public CacheRequest put(Response response) throws IOException {
            return Cache.this.put(response);
        }

        @Override
        public void remove(Request request) throws IOException {
            Cache.this.remove(request);
        }

        @Override
        public void update(Response cached, Response network) {
            //这里是调用了外部类Cache的update
            Cache.this.update(cached, network);
        }

        @Override
        public void trackConditionalCacheHit() {
            Cache.this.trackConditionalCacheHit();
        }

        @Override
        public void trackResponse(CacheStrategy cacheStrategy) {
            Cache.this.trackResponse(cacheStrategy);
        }
    };

-》跳转到Cache外部类update
      void update(Response cached, Response network) {
        Entry entry = new Entry(network);
        DiskLruCache.Snapshot snapshot = ((CacheResponseBody) cached.body()).snapshot;
        DiskLruCache.Editor editor = null;
        try {
            editor = snapshot.edit(); // Returns null if snapshot is not current.
            if (editor != null) {
                //将entry写入到缓存
                entry.writeTo(editor);
                editor.commit();
            }

        } catch (IOException e) {
            abortQuietly(editor);
        }
    }

跳转:CacheInterceptor#cacheWritingResponse

   
    /**
     * Returns a new source that writes bytes to {@code cacheRequest} as they are read by the source
     * consumer. This is careful to discard bytes left over when the stream is closed; otherwise we
     * may never exhaust the source stream and therefore not complete the cached response.
     */
    private Response cacheWritingResponse(final CacheRequest cacheRequest, Response response)
            throws IOException {
        // 做一些参数的校验
        if (cacheRequest == null) return response;
        Sink cacheBodyUnbuffered = cacheRequest.body();
        if (cacheBodyUnbuffered == null) return response;

        //获取到响应的body(是个输出流)
        final BufferedSource source = response.body().source();

        //相当于缓存的输入流
        final BufferedSink cacheBody = Okio.buffer(cacheBodyUnbuffered);

        //后面的逻辑就是将响应的流写入到缓存的body
        Source cacheWritingSource = new Source() {
            boolean cacheRequestClosed;

            @Override
            public long read(Buffer sink, long byteCount) throws IOException {
                long bytesRead;
                try {
                    //将响应全部流读取到sink中
                    bytesRead = source.read(sink, byteCount);
                } catch (IOException e) {
                    if (!cacheRequestClosed) {
                        cacheRequestClosed = true;
                        cacheRequest.abort(); // Failed to write a complete cache response.
                    }
                    throw e;
                }

                //如果读取失败,直接返回
                if (bytesRead == -1) {
                    if (!cacheRequestClosed) {
                        cacheRequestClosed = true;
                        cacheBody.close(); // The cache response is complete!
                    }
                    return -1;
                }

                //到这里,就表示读取到流,现在需要将这些流读取到缓存的body对应的buffer中
                sink.copyTo(cacheBody.buffer(), sink.size() - bytesRead, bytesRead);

                //这里还发布一个完成的消息,但该知识到了okio这个包中,后面在分析
                cacheBody.emitCompleteSegments();
                return bytesRead;
            }

            @Override
            public Timeout timeout() {
                return source.timeout();
            }

            @Override
            public void close() throws IOException {
                if (!cacheRequestClosed
                        && !discard(this, HttpCodec.DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
                    //丢弃上面read方法中读取到当前source的流
                    cacheRequestClosed = true;
                    cacheRequest.abort();
                }
                source.close();
            }
        };

        //最后重新构建一个响应返回
        return response.newBuilder()
                .body(new RealResponseBody(response.headers(), Okio.buffer(cacheWritingSource)))
                .build();
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342