网络篇章(二) 全面解析OkHttp3源码 (文末附视频教程)


1. HttpClient与HttpUrlConnection的区别

简而言之:就是Volley的的请求方式(API2.3之前用的httpClient 2.3之后用的HttpUrlconnection)

httpclient 和httpUrlConnection都支持https协议, 都是以流的形式进行传输数据.支持IPv6以及连接池等功能.

httpclient拥有很多API 保证它的兼容性进行拓展很难,google在6.0的时候废弃Httpclient AS想用这个类可以使用:org.apach.http.legacy

HttpUrlConnection:轻量级,API较少,易拓展,满足大部分的android数据传输 .例如Volley框架

2. OKHttp源码分析

  1. 使用步骤: 首先创建OKhttpClient对象, 再创建一个 request请求 ,给request添加url,header请求方式等参数, 用OKhttpclient.newCall方法返回一个call对象.同步方法: call.execute()来请求数据,异步:call.euque();
  2. dispatcher任务调度器,
    • 它定义了三个双向任务队列,二个异步队列:runningAsyncCalls和readyASyncCalls和一个运行在同步请求的runningSyncCalls队列
    • 一个标准线程池 executorServic 无界线程池,60s回收,用于大量耗时较短的异步任务
  3. Request请求
    • Request的builder方法默认请求方式为GET

    • 通过OkHttpclient和request构造一个call对象,然后把这个call封装到RealCall对象中

      public Call newCall(Request request) {
          return RealCall.newRealCall(this, request, false /* for web socket */);
      }
      
      static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket){
          // Safely publish the Call instance to the EventListener.
          RealCall call = new RealCall(client, originalRequest, forWebSocket);
          call.eventListener = client.eventListenerFactory().create(call);
          return call;
      }
      
      private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
          this.client = client;
          this.originalRequest = originalRequest;
          this.forWebSocket = forWebSocket;
          this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
      }
      
    • 在realCall中构造了一个RetryAndFollowUpInterceptor拦截器用于处理请求错误和重定向等,这个是Okhttp框架精髓 interceptor chain中的一环, 默认情况下的第一个拦截器,除非调用OKhttpclient.builder.addInterceptor来添加全局拦截器,在RealCall.getResponseWithInterceptorChain()中添加默认的5个拦截器

    1. RealCall
      • enque(callBack方法)

        // RealCall.java
        public void enqueue(Callback responseCallback) {
            synchronized (this) {
                //每个请求只能之执行一次
                if (executed) throw new IllegalStateException("Already Executed");
                executed = true;
            }
            captureCallStackTrace();
            eventListener.callStart(this);
            client.dispatcher().enqueue(new AsyncCall(responseCallback));
        }
        
      • 一个call只能执行一次,否则会出现异常,这里创建一个AsyncCall并将CallBack穿入,接着交给任务分发器Dispatcher来处理

        // dispatcher.java
        synchronized void enqueue(AsyncCall call) {
            //正在执行的任务数量小于最大值(64),并且此任务所属主机的正在执行任务小于最大值(5)
            if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
                runningAsyncCalls.add(call);
                executorService().execute(call);
            } else {
                readyAsyncCalls.add(call);
            }
        }
        
      • 从dispatcher的enqueue方法看出,对于入队做了限制,dispatcher类定义了请求数量的最大值为64个,请求的主机正再执行任务小于5台,满足以上要求的线程就可以加入队列,通过线程池执行该任务,否则加入readyAsyncCalls线程池中等待.

    2. AsyncCall
      • 它继承于NameRunnable 而NameRunnable实现runnable接口. 作用1. 采用模板方法的设计模式,让子类具体操作放在execute()中, 作用2. 给线程指定一个名称

          @Override protected void execute() {
              boolean signalledCallback = false;
              try {
                //调用 getResponseWithInterceptorChain()获得响应内容
                Response response = getResponseWithInterceptorChain(); 
                if (retryAndFollowUpInterceptor.isCanceled()) {
                  //这个标记为主要是避免异常时2次回调
                  signalledCallback = true;
                  //回调Callback告知失败
                  responseCallback.onFailure(RealCall.this, new IOException("Canceled")); 
                } else {
                  signalledCallback = true;
                  //回调Callback,将响应内容传回去
                  responseCallback.onResponse(RealCall.this, response);
                }
              } catch (IOException e) {
                if (signalledCallback) {
                  // Do not signal the callback twice!
                  Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
                } else {
                  eventListener.callFailed(RealCall.this, e);
                  responseCallback.onFailure(RealCall.this, e);
                }
              } finally {
                //不管请求成功与否,都进行finished()操作
                client.dispatcher().finished(this);
              }
            }
        
      • client.dispatcher().finished(this)方法

        private void promoteCalls() {
            if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
            if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
            
            //若条件允许,将readyAsyncCalls中的任务移动到runningAsyncCalls中,并交给线程池执行
            for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
              AsyncCall call = i.next();
            
              if (runningCallsForHost(call) < maxRequestsPerHost) {
                i.remove();
                runningAsyncCalls.add(call);
                executorService().execute(call);
              }
              //当runningAsyncCalls满了,直接退出迭代
              if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
            }
        }
        
      • promoteCalls方法推动了下个任务的执行,逻辑: 判断正在请求的队列是否大于64个,判断正在准备的队列是否为空,若条件满足, readyAsyncCall的任务移动到runningAsyncCalls中,并且交给线程池去执行

    3. interceptorChain

      • 重点就是interceptors这个集合,是将前面用户自己创建的拦截器,OkHttp自带的拦截器组合成一个拦截器链

          1. 错误重定向拦截器,(retryAndFollowUpInterceptor)

          2. 桥接拦截器 (BridgeInterceptor)

          3. 缓存拦截器(CacheInterceptor)

          4. 连接拦截器(ConnectInterceptor)

          5. 网络拦截器 (networkInterceptors)

            最后通过RealInterceptorChain#proceed(Request)来执行interceptor chain

        Response getResponseWithInterceptorChain() throws IOException {
            // Build a full stack of interceptors.
            List<Interceptor> interceptors = new ArrayList<>(); //这是一个List,是有序的
            interceptors.addAll(client.interceptors());//首先添加的是用户添加的全局拦截器
            interceptors.add(retryAndFollowUpInterceptor); //错误、重定向拦截器
           //桥接拦截器,桥接应用层与网络层,添加必要的头、
            interceptors.add(new BridgeInterceptor(client.cookieJar())); 
            //缓存处理,Last-Modified、ETag、DiskLruCache等
            interceptors.add(new CacheInterceptor(client.internalCache())); 
            //连接拦截器
            interceptors.add(new ConnectInterceptor(client));
            //从这就知道,通过okHttpClient.Builder#addNetworkInterceptor()传进来的拦截器只对非网页的请求生效
            if (!forWebSocket) {
              interceptors.addAll(client.networkInterceptors());
            }
            //真正访问服务器的拦截器
            interceptors.add(new CallServerInterceptor(forWebSocket));
            
            Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
                originalRequest, this, eventListener, client.connectTimeoutMillis(),
                client.readTimeoutMillis(), client.writeTimeoutMillis());
            
            return chain.proceed(originalRequest);
        }
        
      • RealInterceptorChain#proceed()

        public Response proceed(Request request) throws IOException {
            return proceed(request, streamAllocation, httpCodec, connection);
        }
        
        public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
                                //...异常处理
            // Call the next interceptor in the chain.
            RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
                connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
                writeTimeout);
            Interceptor interceptor = interceptors.get(index);
            Response response = interceptor.intercept(next);
                                //...异常处理
            return response;
          }
        }
        
      • 是按照interceptors集合的顺序,逐个往下调用拦截器的intercept方法,所以最先的拦截器RetryAndFollowUpInterceptor 被调用

        // RetryAndFollowUpInterceptor .java
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            RealInterceptorChain realChain = (RealInterceptorChain) chain;
            Call call = realChain.call();
            EventListener eventListener = realChain.eventListener();
            //创建一个StreamAllocation
            StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
                createAddress(request.url()), call, eventListener, callStackTrace);
            this.streamAllocation = streamAllocation;
        
            //统计重定向次数,不能大于20
            int followUpCount = 0; 
            Response priorResponse = null;
            while (true) {
              if (canceled) {
                streamAllocation.release();
                throw new IOException("Canceled");
              }
        
              Response response;
              boolean releaseConnection = true;
              try {
                //调用下一个interceptor的来获得响应内容
                response = realChain.proceed(request, streamAllocation, null, null);
                releaseConnection = false;
              } catch (RouteException e) {
                // The attempt to connect via a route failed. The request will not have been sent.
                if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
                  throw e.getLastConnectException();
                }
                releaseConnection = false;
                continue;
              } catch (IOException e) {
                // An attempt to communicate with a server failed. The request may have been sent.
                boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
                if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
                releaseConnection = false;
                continue;
              } finally {
                // We're throwing an unchecked exception. Release any resources.
                if (releaseConnection) {
                  streamAllocation.streamFailed(null);
                  streamAllocation.release();
                }
              }
        
              // Attach the prior response if it exists. Such responses never have a body.
              if (priorResponse != null) {
                response = response.newBuilder()
                    .priorResponse(priorResponse.newBuilder()
                            .body(null)
                            .build())
                    .build();
              }
            
             //重定向处理    
              Request followUp = followUpRequest(response, streamAllocation.route());
        
              if (followUp == null) {
                if (!forWebSocket) {
                  streamAllocation.release();
                }
                return response;
              }
        
              closeQuietly(response.body());
        
              if (++followUpCount > MAX_FOLLOW_UPS) {
                streamAllocation.release();
                throw new ProtocolException("Too many follow-up requests: " + followUpCount);
              }
        
              if (followUp.body() instanceof UnrepeatableRequestBody) {
                streamAllocation.release();
                throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
              }
        
              if (!sameConnection(response, followUp.url())) {
                streamAllocation.release();
                streamAllocation = new StreamAllocation(client.connectionPool(),
                    createAddress(followUp.url()), call, eventListener, callStackTrace);
                this.streamAllocation = streamAllocation;
              } else if (streamAllocation.codec() != null) {
                throw new IllegalStateException("Closing the body of " + response
                    + " didn't close its backing stream. Bad interceptor?");
              }
        
              request = followUp;
              priorResponse = response;
            }
        }
        
      • 这个拦截器主要用于错误处理和重定向等问题,

        总结:

        拦截器链.png

3. OKHttp架构分析

  1. 异步请求线程池, Dispather

    public synchronized ExecutorService executorService() {
        if (executorService == null) {
          executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
              new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
        }
        return executorService;
      }
    
    • 类似于线程池的newCacheThreadPool
    • 无上限,60s自动回收线程,用于大量耗时较短的任务
    • dispatcher提供修改最大异步任务数和最多主机的接口.默认是64个任务5台主机
    • 通过双端队列来维护准备执行的任务和正在执行的任务,readyAsyncCalls,runningAsyncCalls
    • 在每个任务结束后,都会检查readyAsyncCalls是否有任务,在满足的情况下,按照先进先出的原则将任务移动到runningAsyncCalls队列中,并在线程池中执行
  2. 连接池清理线程池- ConnectionPool

    • 该线程池是用来清理长时间闲置的和泄露的连接

    • 该线程池本身无上限,闲置60s回收

    • 虽然无限制,但是通过clearRunning标记来控制只有一个线程在运行,当连接池中没有连接时才会重新设置为fasle

      void put(RealConnection connection) {
          assert (Thread.holdsLock(this));
          if (!cleanupRunning) {
              cleanupRunning = true;
              executor.execute(cleanupRunnable);
          }
          connections.add(connection);
      }
      
    • 工作线程会不断地被清理,当清理一遍完成后,根据线程池中的空闲超时连接计算出一个阻塞时间并阻塞,直到线程池中没有任何连接才结束,并将clearRunning设置为fasle

    • 每次有连接加入线程池中,如果当前没有清理任务运行,会加入一个清理任务到线程池中运行,

      void put(RealConnection connection) {
          assert (Thread.holdsLock(this));
          if (!cleanupRunning) {
              cleanupRunning = true;
              executor.execute(cleanupRunnable);
          }
          connections.add(connection);
        }
      
      连接池相关原理.png
    1. 缓存整理线程池 DisLruCache
    • 该线程池用于整理本地请求缓存数据
    • 缓存的整理包含,达到阈值大小的文件,删除最近最少使用的记录,在有关操作达到一定数量以后对记录进行重建
    • 最大运行数量为1,无需考虑线程安全问题,自动回收闲置60s的线程
    1. Http2异步事务线程池, http2Connection

4. OKHTTP内部缓存

  1. 使用场景: 数据更新不频繁的查询操作,客户端缓存可以减少服务器的访问次数,无网络时候也可以显示历史数据,

    max-age:这个参数告诉浏览器将页面缓存多长时间,超过这个时间后才再次向服务器发起请求检查页面是否有更新。对于静态的页面,比如图片、CSS、Javascript,一般都不大变更,因此通常我们将存储这些内容的时间设置为较长的时间,这样浏览器会不会向浏览器反复发起请求,也不会去检查是否更新了。
    s-maxage:这个参数告诉缓存服务器(proxy,如Squid)的缓存页面的时间。如果不单独指定,缓存服务器将使用max-age。对于动态内容(比如文档的查看页面),我们可告诉浏览器很快就过时了(max-age=0),并告诉缓存服务器(Squid)保留内容一段时间(比如,s-maxage=7200)。一旦我们更新文档,我们将告诉Squid清除老的缓存版本。
    must-revalidate:这告诉浏览器,一旦缓存的内容过期,一定要向服务器询问是否有新版本。
    proxy-revalidate:proxy上的缓存一旦过期,一定要向服务器询问是否有新版本。
    no-cache:不做缓存。
    no-store:数据不在硬盘中临时保存,这对需要保密的内容比较重要。
    public:告诉缓存服务器, 即便是对于不该缓存的内容也缓存起来,比如当用户已经认证的时候。所有的静态内容(图片、Javascript、CSS等)应该是public的。
    private:告诉proxy不要缓存,但是浏览器可使用private cache进行缓存。一般登录后的个性化页面是private的。
    no-transform: 告诉proxy不进行转换,比如告诉手机浏览器不要下载某些图片。
    max-stale指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。
    
  2. okhttp的缓存设置 设置缓存的目录

    OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
    File cacheFile = new File(content.getExternalCacheDir(),"tangnuer");
    Cache cache = new Cache(cacheFile,1024*1024*50);
    //使用
    httpClientBuilder
        .cache(cache)
        .connectTimeout(20, TimeUnit.SECONDS)
        .readTimeout(20, TimeUnit.SECONDS)
    
  3. 源码分析:

    1. 源码入口CacheInterceptor#intercept

      public Response intercept(Chain chain) throws IOException {
      
          //判断是否设置cache
          Response cacheCandidate = cache != null
              ? cache.get(chain.request())
              : null;
      
          long now = System.currentTimeMillis();
           
          CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
          Request networkRequest = strategy.networkRequest;
          Response cacheResponse = strategy.cacheResponse;
          
          //如果cache 不为null,从strategy中追踪Response
          //主要是对networkRequest 或cacheResponse 进行计数
          if (cache != null) {
            cache.trackResponse(strategy);
          }
      
          //如果缓存不适用,则关闭IO流
          if (cacheCandidate != null && cacheResponse == null) {
            closeQuietly(cacheCandidate.body()); 
          }
      
          // 如果网络被禁止并且无缓存,则返回失败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)
                .sentRequestAtMillis(-1L)
                .receivedResponseAtMillis(System.currentTimeMillis())
                .build();
          }
      
          // 在无网络时从缓存中获取
          if (networkRequest == null) {
            return cacheResponse.newBuilder()
                .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());
            }
          }
      
          // 如果缓存中已经存在对应的Response的处理
          if (cacheResponse != null) {
            //表示数据未做修改
            if (networkResponse.code() == HTTP_NOT_MODIFIED) {
              Response response = cacheResponse.newBuilder()
                  .headers(combine(cacheResponse.headers(), networkResponse.headers()))
                  .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
                  .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
                  .cacheResponse(stripBody(cacheResponse))
                  .networkResponse(stripBody(networkResponse))
                  .build();
              networkResponse.body().close();
      
              //主要是更新response头部数据
              cache.trackConditionalCacheHit();
              cache.update(cacheResponse, response);
              return response;
            } else {
              closeQuietly(cacheResponse.body());
            }
          }
      
          Response response = networkResponse.newBuilder()
              .cacheResponse(stripBody(cacheResponse))
              .networkResponse(stripBody(networkResponse))
              .build();
      
          //写入缓存
          if (cache != null) {
            if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
              // Offer this request to the cache.
              CacheRequest cacheRequest = cache.put(response);
              return cacheWritingResponse(cacheRequest, response);
            }
            
            //移除networkRequest
            if (HttpMethod.invalidatesCache(networkRequest.method())) {
              try {
                cache.remove(networkRequest);
              } catch (IOException ignored) {
                // The cache cannot be written.
              }
            }
          }
      
          return response;
        }
      
      • CacheStrategy内部分装了网络请求对象newWorkRequest和CacheResponse(开始获取的候选缓存对象CacheCandie),它是Okhttp的缓存策略核心.
      • 回到代码, 首先判断是否已设置了cache,如果已经设置,根据chain.request()返回的request查找cache中对应的response对象,然后创建一个CacheStrategy对象strategy,
      • 再通过对网络状态的判断和缓存状态的判断,如果是网络获取未缓存的,得到response对象后,会更新写入到缓存中,再返回, 而上述代码就是对cache的操作(get,put,update,remove)
    2. Cache的产生

      cache是internalCache类型的对象,internalCache是okhttp的内部缓存接口,

      • Cache类的构造方法

         public Cache(File directory, long maxSize) {
            this(directory, maxSize, FileSystem.SYSTEM);
          }
        
          Cache(File directory, long maxSize, FileSystem fileSystem) {
            this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
          }
        
      • 创建了DiskLruCache类型的cache实例,这里的FileSystem.SYSTEM是FileSystem的实现,内部是基于OKio的singk/source对缓存文件进行流的操作,DiskLRUCache.entry内部维护了二个数组,保存每个Url请求对应文件的引用.然后通过DiskLruCache.editor操作数组,并为Cache.entry提供Sink/source,对文件流进行操作,

    3. Cache的操作

      • put操作

        CacheRequest put(Response response) {
            //获取请求方法
            String requestMethod = response.request().method();
        
            if (HttpMethod.invalidatesCache(response.request().method())) {
              try {
                remove(response.request());
              } catch (IOException ignored) {
                // The cache cannot be written.
              }
              return null;
            }
        
            //如果不是GET请求时返回的response,则不进行缓存
            if (!requestMethod.equals("GET")) {    
              return null;
            }
        
            if (HttpHeaders.hasVaryAll(response)) {
              return null;
            }
        
            //把response封装在Cache.Entry中,调用DiskLruCache的edit()返回editor
            Entry entry = new Entry(response);
            DiskLruCache.Editor editor = null;
            try {
              //把url进行 md5(),并转换成十六进制格式
              //将转换后的key作为DiskLruCache内部LinkHashMap的键值
              editor = cache.edit(key(response.request().url()));
              if (editor == null) {
                return null;
              }
        
              //用editor提供的Okio的sink对文件进行写入
              entry.writeTo(editor);
              //利用CacheRequestImpl写入body
              return new CacheRequestImpl(editor);
            } catch (IOException e) {
              abortQuietly(editor);
              return null;
            }
          }
        

        OkHttp只针对Get请求时返回的数据进行缓存,官方解释:非Get请求返回的Response也可以进行缓存,但是这样做复杂性搞高,且效益低 DiskLruCache.editor获取editor对象后,调用writeTo吧url,请求方法,响应头部字段等写入缓存,返回一个CacheRequestImpl实例,

        public @Nullable Editor edit(String key) throws IOException {
            return edit(key, ANY_SEQUENCE_NUMBER);
          }
        
          synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
            //内部主要是利用FileSystem处理文件,如果这里出现了异常,
            //在最后会构建新的日志文件,如果文件已存在,则替换
            initialize();
            //检测缓存是否已关闭
            checkNotClosed();
            //检测是否为有效key
            validateKey(key);
            //lruEntries是LinkHashMap的实例,先查找lruEntries是否存在
            Entry entry = lruEntries.get(key);
             
            if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
                || entry.sequenceNumber != expectedSequenceNumber)) {
              return null; // Snapshot is stale.
            }
        
            //如果有Editor在操作entry,返回null
            if (entry != null && entry.currentEditor != null) {
              return null; 
            }
            //如果需要,进行clean操作
            if (mostRecentTrimFailed || mostRecentRebuildFailed) {    
              executor.execute(cleanupRunnable);
              return null;
            }
        
            // 把当前key在对应文件中标记DIRTY状态,表示正在修改,
            //清空日志缓冲区,防止泄露
            journalWriter.writeUtf8(DIRTY).writeByte(' ').writeUtf8(key).writeByte('\n');
            journalWriter.flush();
        
            if (hasJournalErrors) {
              return null; // 如果日志文件不能编辑
            }
            
            //为请求的url创建一个新的DiskLruCache.Entry实例
            //并放入lruEntries中
            if (entry == null) {
              entry = new Entry(key);
              lruEntries.put(key, entry);
            }
            
            Editor editor = new Editor(entry);
            entry.currentEditor = editor;
            return editor;
          }
        

        最后的到DiskLruCache.Entry实例,这个实例主要维护Key对应的文件列表,并且内部currentEditor不为null,表示当前实例处于编译状态,返回得到editor后,调用Cache.Entry的writeTo对editor进行操作

        public void writeTo(DiskLruCache.Editor editor) throws IOException {
              BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA));
        
              sink.writeUtf8(url)
                  .writeByte('\n');
              sink.writeUtf8(requestMethod)
                  .writeByte('\n');
              sink.writeDecimalLong(varyHeaders.size())
                  .writeByte('\n');
             
              //... ...省略,都是利用sink进行写入操作
              sink.close();
            }
        

        editor.newSink为上层Cache.entry提供一个sink,然后进行文件写入操作,这里只是写入url,请求方法,头部字段等,并未写入body部分,body部分的写入在CacheInterceptor.intercept方法的内部调用CacheWritingResponse写入body

        @Override 
        public Response intercept(Chain chain) throws IOException {
            ... ... //省略代码
        
            if (cache != null) {
              if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
                // Offer this request to the cache.
                CacheRequest cacheRequest = cache.put(response);
                //在内部实现了response的body的写入
                return cacheWritingResponse(cacheRequest, response);
              }
            ... ... //省略代码
        )
        
        Cache相关.png
      • get操作

        @Nullable 
          Response get(Request request) {
            //把url转换成key
            String key = key(request.url());
            DiskLruCache.Snapshot snapshot;
            Entry entry;
            try {
              //通过DiskLruCache的get()根据具体的key获取DiskLruCache.Snapshot实例
              snapshot = cache.get(key);
              if (snapshot == null) {
                return null;
              }
            } catch (IOException e) {
              // Give up because the cache cannot be read.
              return null;
            }
        
            try {
              //通过snapshot.getSource()获取一个Okio的Source
              entry = new Entry(snapshot.getSource(ENTRY_METADATA));
            } catch (IOException e) {
              Util.closeQuietly(snapshot);
              return null;
            }
            
            //根据snapshot获取缓存中的response
            Response response = entry.response(snapshot);
        
            if (!entry.matches(request, response)) {
              Util.closeQuietly(response.body());
              return null;
            }
            return response;
          }
        

        snapshor是DiskLruCache.Entry的一个快照,内部封装了DiskLruCache.entry对应文件的source,简单来说,根据条件从DiskLruCache.entry找到对应的缓存文件,并生成source文件,封装在snapshot内部,通过snapshort.getsource获取source,对文件进行读取操作

         //DiskLruCache # get()
        public synchronized Snapshot get(String key) throws IOException {
            initialize();
        
            checkNotClosed();
            validateKey(key);
            //从lruEntries查找entry,
            Entry entry = lruEntries.get(key);
            if (entry == null || !entry.readable) return null;
            
            //得到Entry的快照值snapshot
            Snapshot snapshot = entry.snapshot();
            if (snapshot == null) return null;
        
            redundantOpCount++;
            journalWriter.writeUtf8(READ).writeByte(' ').writeUtf8(key).writeByte('\n');
        
            //如果redundantOpCount超过2000,且超过lruEntries的大小时,进行清理操作
            if (journalRebuildRequired()) {
              executor.execute(cleanupRunnable);
            }
         
            return snapshot;
          }
        
        //DiskLruCache.Entry # snapshot()
        Snapshot snapshot() {
              if (!Thread.holdsLock(DiskLruCache.this)) throw new AssertionError();
        
              Source[] sources = new Source[valueCount];
              // Defensive copy since these can be zeroed out.
              long[] lengths = this.lengths.clone(); 
              try {
                 //遍历已缓存的文件,生成相应的sources
                for (int i = 0; i < valueCount; i++) {
                  sources[i] = fileSystem.source(cleanFiles[i]);
                }
                //创建Snapshot并返回
                return new Snapshot(key, sequenceNumber, sources, lengths);
              } catch (FileNotFoundException e) {
                // A file must have been deleted manually!
                for (int i = 0; i < valueCount; i++) {
                  if (sources[i] != null) {
                    Util.closeQuietly(sources[i]);
                  } else {
                    break;
                  }
                }
                // Since the entry is no longer valid, remove it so the metadata is accurate (i.e. the cache
                // size.)
                try {
                  removeEntry(this);
                } catch (IOException ignored) {
                }
                return null;
              }
            }
        

        Cache.Entry # response()

        public Response response(DiskLruCache.Snapshot snapshot) {
              String contentType = responseHeaders.get("Content-Type");
              String contentLength = responseHeaders.get("Content-Length");
              Request cacheRequest = new Request.Builder()
                  .url(url)
                  .method(requestMethod, null)
                  .headers(varyHeaders)
                  .build();
              return new Response.Builder()
                  .request(cacheRequest)
                  .protocol(protocol)
                  .code(code)
                  .message(message)
                  .headers(responseHeaders)
                  .body(new CacheResponseBody(snapshot, contentType, contentLength))
                  .handshake(handshake)
                  .sentRequestAtMillis(sentRequestMillis)
                  .receivedResponseAtMillis(receivedResponseMillis)
                  .build();
            }
        
  4. 总结 : Cache只是一个上层的执行者,内部真正的缓存是由DiskLruCache实现的,在DiskLruCache里面通过FileStem,基于okio的sink/source对文件进行流的操作.

福利

  由于篇幅有限很多细节无法具体分析 如想了解更多OkHttp有关的知识, 我推荐一部详细的开源框架视频教程 视频地址在youtub上 当然本人也是小白一个 只能做资源的搬运..

下篇文章我们会继续学习Retrofit2源码相关知识

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