網(wǎng)絡(luò)篇章(二) 全面解析OkHttp3源碼 (文末附視頻教程)


1. HttpClient與HttpUrlConnection的區(qū)別

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

httpclient 和httpUrlConnection都支持https協(xié)議, 都是以流的形式進行傳輸數(shù)據(jù).支持IPv6以及連接池等功能.

httpclient擁有很多API 保證它的兼容性進行拓展很難,google在6.0的時候廢棄Httpclient AS想用這個類可以使用:org.apach.http.legacy

HttpUrlConnection:輕量級,API較少,易拓展,滿足大部分的android數(shù)據(jù)傳輸 .例如Volley框架

2. OKHttp源碼分析

  1. 使用步驟: 首先創(chuàng)建OKhttpClient對象, 再創(chuàng)建一個 request請求 ,給request添加url,header請求方式等參數(shù), 用OKhttpclient.newCall方法返回一個call對象.同步方法: call.execute()來請求數(shù)據(jù),異步:call.euque();
  2. dispatcher任務(wù)調(diào)度器,
    • 它定義了三個雙向任務(wù)隊列,二個異步隊列:runningAsyncCalls和readyASyncCalls和一個運行在同步請求的runningSyncCalls隊列
    • 一個標準線程池 executorServic 無界線程池,60s回收,用于大量耗時較短的異步任務(wù)
  3. Request請求
    • Request的builder方法默認請求方式為GET

    • 通過OkHttpclient和request構(gòu)造一個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中構(gòu)造了一個RetryAndFollowUpInterceptor攔截器用于處理請求錯誤和重定向等,這個是Okhttp框架精髓 interceptor chain中的一環(huán), 默認情況下的第一個攔截器,除非調(diào)用OKhttpclient.builder.addInterceptor來添加全局攔截器,在RealCall.getResponseWithInterceptorChain()中添加默認的5個攔截器

    1. RealCall
      • enque(callBack方法)

        // RealCall.java
        public void enqueue(Callback responseCallback) {
            synchronized (this) {
                //每個請求只能之執(zhí)行一次
                if (executed) throw new IllegalStateException("Already Executed");
                executed = true;
            }
            captureCallStackTrace();
            eventListener.callStart(this);
            client.dispatcher().enqueue(new AsyncCall(responseCallback));
        }
        
      • 一個call只能執(zhí)行一次,否則會出現(xiàn)異常,這里創(chuàng)建一個AsyncCall并將CallBack穿入,接著交給任務(wù)分發(fā)器Dispatcher來處理

        // dispatcher.java
        synchronized void enqueue(AsyncCall call) {
            //正在執(zhí)行的任務(wù)數(shù)量小于最大值(64)往衷,并且此任務(wù)所屬主機的正在執(zhí)行任務(wù)小于最大值(5)
            if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
                runningAsyncCalls.add(call);
                executorService().execute(call);
            } else {
                readyAsyncCalls.add(call);
            }
        }
        
      • 從dispatcher的enqueue方法看出,對于入隊做了限制,dispatcher類定義了請求數(shù)量的最大值為64個,請求的主機正再執(zhí)行任務(wù)小于5臺,滿足以上要求的線程就可以加入隊列,通過線程池執(zhí)行該任務(wù),否則加入readyAsyncCalls線程池中等待.

    2. AsyncCall
      • 它繼承于NameRunnable 而NameRunnable實現(xiàn)runnable接口. 作用1. 采用模板方法的設(shè)計模式,讓子類具體操作放在execute()中, 作用2. 給線程指定一個名稱

          @Override protected void execute() {
              boolean signalledCallback = false;
              try {
                //調(diào)用 getResponseWithInterceptorChain()獲得響應(yīng)內(nèi)容
                Response response = getResponseWithInterceptorChain(); 
                if (retryAndFollowUpInterceptor.isCanceled()) {
                  //這個標記為主要是避免異常時2次回調(diào)
                  signalledCallback = true;
                  //回調(diào)Callback告知失敗
                  responseCallback.onFailure(RealCall.this, new IOException("Canceled")); 
                } else {
                  signalledCallback = true;
                  //回調(diào)Callback忠藤,將響應(yīng)內(nèi)容傳回去
                  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中的任務(wù)移動到runningAsyncCalls中烹骨,并交給線程池執(zhí)行
            for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
              AsyncCall call = i.next();
            
              if (runningCallsForHost(call) < maxRequestsPerHost) {
                i.remove();
                runningAsyncCalls.add(call);
                executorService().execute(call);
              }
              //當(dāng)runningAsyncCalls滿了熙掺,直接退出迭代
              if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
            }
        }
        
      • promoteCalls方法推動了下個任務(wù)的執(zhí)行,邏輯: 判斷正在請求的隊列是否大于64個,判斷正在準備的隊列是否為空,若條件滿足, readyAsyncCall的任務(wù)移動到runningAsyncCalls中,并且交給線程池去執(zhí)行

    3. interceptorChain

      • 重點就是interceptors這個集合,是將前面用戶自己創(chuàng)建的攔截器,OkHttp自帶的攔截器組合成一個攔截器鏈

          1. 錯誤重定向攔截器,(retryAndFollowUpInterceptor)

          2. 橋接攔截器 (BridgeInterceptor)

          3. 緩存攔截器(CacheInterceptor)

          4. 連接攔截器(ConnectInterceptor)

          5. 網(wǎng)絡(luò)攔截器 (networkInterceptors)

            最后通過RealInterceptorChain#proceed(Request)來執(zhí)行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); //錯誤伞辛、重定向攔截器
           //橋接攔截器,橋接應(yīng)用層與網(wǎng)絡(luò)層夯缺,添加必要的頭蚤氏、
            interceptors.add(new BridgeInterceptor(client.cookieJar())); 
            //緩存處理,Last-Modified踊兜、ETag竿滨、DiskLruCache等
            interceptors.add(new CacheInterceptor(client.internalCache())); 
            //連接攔截器
            interceptors.add(new ConnectInterceptor(client));
            //從這就知道,通過okHttpClient.Builder#addNetworkInterceptor()傳進來的攔截器只對非網(wǎng)頁的請求生效
            if (!forWebSocket) {
              interceptors.addAll(client.networkInterceptors());
            }
            //真正訪問服務(wù)器的攔截器
            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集合的順序,逐個往下調(diào)用攔截器的intercept方法,所以最先的攔截器RetryAndFollowUpInterceptor 被調(diào)用

        // 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();
            //創(chuàng)建一個StreamAllocation
            StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
                createAddress(request.url()), call, eventListener, callStackTrace);
            this.streamAllocation = streamAllocation;
        
            //統(tǒng)計重定向次數(shù)捏境,不能大于20
            int followUpCount = 0; 
            Response priorResponse = null;
            while (true) {
              if (canceled) {
                streamAllocation.release();
                throw new IOException("Canceled");
              }
        
              Response response;
              boolean releaseConnection = true;
              try {
                //調(diào)用下一個interceptor的來獲得響應(yīng)內(nèi)容
                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;
            }
        }
        
      • 這個攔截器主要用于錯誤處理和重定向等問題,

        總結(jié):

        攔截器鏈.png

3. OKHttp架構(gòu)分析

  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自動回收線程,用于大量耗時較短的任務(wù)
    • dispatcher提供修改最大異步任務(wù)數(shù)和最多主機的接口.默認是64個任務(wù)5臺主機
    • 通過雙端隊列來維護準備執(zhí)行的任務(wù)和正在執(zhí)行的任務(wù),readyAsyncCalls,runningAsyncCalls
    • 在每個任務(wù)結(jié)束后,都會檢查readyAsyncCalls是否有任務(wù),在滿足的情況下,按照先進先出的原則將任務(wù)移動到runningAsyncCalls隊列中,并在線程池中執(zhí)行
  2. 連接池清理線程池- ConnectionPool

    • 該線程池是用來清理長時間閑置的和泄露的連接

    • 該線程池本身無上限,閑置60s回收

    • 雖然無限制,但是通過clearRunning標記來控制只有一個線程在運行,當(dāng)連接池中沒有連接時才會重新設(shè)置為fasle

      void put(RealConnection connection) {
          assert (Thread.holdsLock(this));
          if (!cleanupRunning) {
              cleanupRunning = true;
              executor.execute(cleanupRunnable);
          }
          connections.add(connection);
      }
      
    • 工作線程會不斷地被清理,當(dāng)清理一遍完成后,根據(jù)線程池中的空閑超時連接計算出一個阻塞時間并阻塞,直到線程池中沒有任何連接才結(jié)束,并將clearRunning設(shè)置為fasle

    • 每次有連接加入線程池中,如果當(dāng)前沒有清理任務(wù)運行,會加入一個清理任務(wù)到線程池中運行,

      void put(RealConnection connection) {
          assert (Thread.holdsLock(this));
          if (!cleanupRunning) {
              cleanupRunning = true;
              executor.execute(cleanupRunnable);
          }
          connections.add(connection);
        }
      
      連接池相關(guān)原理.png
    1. 緩存整理線程池 DisLruCache
    • 該線程池用于整理本地請求緩存數(shù)據(jù)
    • 緩存的整理包含,達到閾值大小的文件,刪除最近最少使用的記錄,在有關(guān)操作達到一定數(shù)量以后對記錄進行重建
    • 最大運行數(shù)量為1,無需考慮線程安全問題,自動回收閑置60s的線程
    1. Http2異步事務(wù)線程池, http2Connection

4. OKHTTP內(nèi)部緩存

  1. 使用場景: 數(shù)據(jù)更新不頻繁的查詢操作,客戶端緩存可以減少服務(wù)器的訪問次數(shù),無網(wǎng)絡(luò)時候也可以顯示歷史數(shù)據(jù),

    max-age:這個參數(shù)告訴瀏覽器將頁面緩存多長時間于游,超過這個時間后才再次向服務(wù)器發(fā)起請求檢查頁面是否有更新。對于靜態(tài)的頁面垫言,比如圖片贰剥、CSS、Javascript筷频,一般都不大變更蚌成,因此通常我們將存儲這些內(nèi)容的時間設(shè)置為較長的時間,這樣瀏覽器會不會向瀏覽器反復(fù)發(fā)起請求凛捏,也不會去檢查是否更新了担忧。
    s-maxage:這個參數(shù)告訴緩存服務(wù)器(proxy,如Squid)的緩存頁面的時間坯癣。如果不單獨指定涵妥,緩存服務(wù)器將使用max-age。對于動態(tài)內(nèi)容(比如文檔的查看頁面),我們可告訴瀏覽器很快就過時了(max-age=0)蓬网,并告訴緩存服務(wù)器(Squid)保留內(nèi)容一段時間(比如窒所,s-maxage=7200)。一旦我們更新文檔帆锋,我們將告訴Squid清除老的緩存版本吵取。
    must-revalidate:這告訴瀏覽器,一旦緩存的內(nèi)容過期锯厢,一定要向服務(wù)器詢問是否有新版本皮官。
    proxy-revalidate:proxy上的緩存一旦過期,一定要向服務(wù)器詢問是否有新版本实辑。
    no-cache:不做緩存捺氢。
    no-store:數(shù)據(jù)不在硬盤中臨時保存,這對需要保密的內(nèi)容比較重要剪撬。
    public:告訴緩存服務(wù)器, 即便是對于不該緩存的內(nèi)容也緩存起來摄乒,比如當(dāng)用戶已經(jīng)認證的時候。所有的靜態(tài)內(nèi)容(圖片残黑、Javascript馍佑、CSS等)應(yīng)該是public的。
    private:告訴proxy不要緩存梨水,但是瀏覽器可使用private cache進行緩存拭荤。一般登錄后的個性化頁面是private的。
    no-transform: 告訴proxy不進行轉(zhuǎn)換疫诽,比如告訴手機瀏覽器不要下載某些圖片舅世。
    max-stale指示客戶機可以接收超出超時期間的響應(yīng)消息。如果指定max-stale消息的值奇徒,那么客戶機可以接收超出超時期指定值之內(nèi)的響應(yīng)消息歇终。
    
  2. okhttp的緩存設(shè)置 設(shè)置緩存的目錄

    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 {
      
          //判斷是否設(shè)置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 進行計數(shù)
          if (cache != null) {
            cache.trackResponse(strategy);
          }
      
          //如果緩存不適用逼龟,則關(guān)閉IO流
          if (cacheCandidate != null && cacheResponse == null) {
            closeQuietly(cacheCandidate.body()); 
          }
      
          // 如果網(wǎng)絡(luò)被禁止并且無緩存评凝,則返回失敗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();
          }
      
          // 在無網(wǎng)絡(luò)時從緩存中獲取
          if (networkRequest == null) {
            return cacheResponse.newBuilder()
                .cacheResponse(stripBody(cacheResponse))
                .build();
          }
      
          Response networkResponse = null;
          try {
            //調(diào)用下一個攔截器,訪問網(wǎng)絡(luò)
            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());
            }
          }
      
          // 如果緩存中已經(jīng)存在對應(yīng)的Response的處理
          if (cacheResponse != null) {
            //表示數(shù)據(jù)未做修改
            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頭部數(shù)據(jù)
              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內(nèi)部分裝了網(wǎng)絡(luò)請求對象newWorkRequest和CacheResponse(開始獲取的候選緩存對象CacheCandie),它是Okhttp的緩存策略核心.
      • 回到代碼, 首先判斷是否已設(shè)置了cache,如果已經(jīng)設(shè)置,根據(jù)chain.request()返回的request查找cache中對應(yīng)的response對象,然后創(chuàng)建一個CacheStrategy對象strategy,
      • 再通過對網(wǎng)絡(luò)狀態(tài)的判斷和緩存狀態(tài)的判斷,如果是網(wǎng)絡(luò)獲取未緩存的,得到response對象后,會更新寫入到緩存中,再返回, 而上述代碼就是對cache的操作(get,put,update,remove)
    2. Cache的產(chǎn)生

      cache是internalCache類型的對象,internalCache是okhttp的內(nèi)部緩存接口,

      • Cache類的構(gòu)造方法

         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);
          }
        
      • 創(chuàng)建了DiskLruCache類型的cache實例,這里的FileSystem.SYSTEM是FileSystem的實現(xiàn),內(nèi)部是基于OKio的singk/source對緩存文件進行流的操作,DiskLRUCache.entry內(nèi)部維護了二個數(shù)組,保存每個Url請求對應(yīng)文件的引用.然后通過DiskLruCache.editor操作數(shù)組,并為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中奕短,調(diào)用DiskLruCache的edit()返回editor
            Entry entry = new Entry(response);
            DiskLruCache.Editor editor = null;
            try {
              //把url進行 md5()湘今,并轉(zhuǎn)換成十六進制格式
              //將轉(zhuǎn)換后的key作為DiskLruCache內(nèi)部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請求時返回的數(shù)據(jù)進行緩存,官方解釋:非Get請求返回的Response也可以進行緩存,但是這樣做復(fù)雜性搞高,且效益低 DiskLruCache.editor獲取editor對象后,調(diào)用writeTo吧url,請求方法,響應(yīng)頭部字段等寫入緩存,返回一個CacheRequestImpl實例,

        public @Nullable Editor edit(String key) throws IOException {
            return edit(key, ANY_SEQUENCE_NUMBER);
          }
        
          synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
            //內(nèi)部主要是利用FileSystem處理文件瀑焦,如果這里出現(xiàn)了異常,
            //在最后會構(gòu)建新的日志文件丧枪,如果文件已存在之斯,則替換
            initialize();
            //檢測緩存是否已關(guān)閉
            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;
            }
        
            // 把當(dāng)前key在對應(yīng)文件中標記DIRTY狀態(tài)莉擒,表示正在修改酿炸,
            //清空日志緩沖區(qū),防止泄露
            journalWriter.writeUtf8(DIRTY).writeByte(' ').writeUtf8(key).writeByte('\n');
            journalWriter.flush();
        
            if (hasJournalErrors) {
              return null; // 如果日志文件不能編輯
            }
            
            //為請求的url創(chuàng)建一個新的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對應(yīng)的文件列表,并且內(nèi)部currentEditor不為null,表示當(dāng)前實例處于編譯狀態(tài),返回得到editor后,調(diào)用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方法的內(nèi)部調(diào)用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);
                //在內(nèi)部實現(xiàn)了response的body的寫入
                return cacheWritingResponse(cacheRequest, response);
              }
            ... ... //省略代碼
        )
        
        Cache相關(guān).png
      • get操作

        @Nullable 
          Response get(Request request) {
            //把url轉(zhuǎn)換成key
            String key = key(request.url());
            DiskLruCache.Snapshot snapshot;
            Entry entry;
            try {
              //通過DiskLruCache的get()根據(jù)具體的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;
            }
            
            //根據(jù)snapshot獲取緩存中的response
            Response response = entry.response(snapshot);
        
            if (!entry.matches(request, response)) {
              Util.closeQuietly(response.body());
              return null;
            }
            return response;
          }
        

        snapshor是DiskLruCache.Entry的一個快照,內(nèi)部封裝了DiskLruCache.entry對應(yīng)文件的source,簡單來說,根據(jù)條件從DiskLruCache.entry找到對應(yīng)的緩存文件,并生成source文件,封裝在snapshot內(nèi)部,通過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 {
                 //遍歷已緩存的文件扁眯,生成相應(yīng)的sources
                for (int i = 0; i < valueCount; i++) {
                  sources[i] = fileSystem.source(cleanFiles[i]);
                }
                //創(chuàng)建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. 總結(jié) : Cache只是一個上層的執(zhí)行者,內(nèi)部真正的緩存是由DiskLruCache實現(xiàn)的,在DiskLruCache里面通過FileStem,基于okio的sink/source對文件進行流的操作.

福利

??由于篇幅有限很多細節(jié)無法具體分析 如想了解更多OkHttp有關(guān)的知識, 我推薦一部詳細的開源框架視頻教程 視頻地址在youtub上 當(dāng)然本人也是小白一個 只能做資源的搬運..

下篇文章我們會繼續(xù)學(xué)習(xí)Retrofit2源碼相關(guān)知識

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市翅帜,隨后出現(xiàn)的幾起案子姻檀,更是在濱河造成了極大的恐慌,老刑警劉巖涝滴,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绣版,死亡現(xiàn)場離奇詭異,居然都是意外死亡狭莱,警方通過查閱死者的電腦和手機僵娃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門概作,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腋妙,“玉大人,你說我怎么就攤上這事讯榕≈杷兀” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵愚屁,是天一觀的道長济竹。 經(jīng)常有香客問我,道長霎槐,這世上最難降的妖魔是什么送浊? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮丘跌,結(jié)果婚禮上袭景,老公的妹妹穿的比我還像新娘。我一直安慰自己闭树,他們只是感情好耸棒,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著报辱,像睡著了一般与殃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天幅疼,我揣著相機與錄音米奸,去河邊找鬼。 笑死衣屏,一個胖子當(dāng)著我的面吹牛躏升,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播狼忱,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼膨疏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了钻弄?” 一聲冷哼從身側(cè)響起佃却,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎窘俺,沒想到半個月后饲帅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡瘤泪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年灶泵,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片对途。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡赦邻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出实檀,到底是詐尸還是另有隱情惶洲,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布膳犹,位于F島的核電站恬吕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏须床。R本人自食惡果不足惜铐料,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望豺旬。 院中可真熱鬧钠惩,春花似錦、人聲如沸哈垢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽耘分。三九已至举塔,卻和暖如春绑警,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背央渣。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工计盒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芽丹。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓北启,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拔第。 傳聞我的和親對象是個殘疾皇子咕村,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345