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

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

作者: Sincerity_ | 来源:发表于2019-04-30 10:01 被阅读0次

    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源码相关知识

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/rqlmnqtx.html