從官方示例看OkHttp——OkHttp 3.9.1 源碼淺析

年后就是跳槽的高峰期者吁,年前參加了幾場面試柳骄,基本上都會問對于常用的第三方框架的了解病梢,由于之前主要從事系統(tǒng)開發(fā)的工作,所以對于常用的網(wǎng)絡框架不是很了解辰晕。借此機會學習總結(jié)一下OkHttp網(wǎng)絡請求框架蛤迎。
本文從官方提供的示例入手,嘗試分析學習OkHttp框架(3.9.1版本)的源碼含友,

OkHttp簡介

An HTTP & HTTP/2 client for Android and Java applications

根據(jù)官網(wǎng)介紹替裆,OkHttp是一個用于java和Android的HTTP&HTTP/2請求的客戶端。
而在Android系統(tǒng)的原生庫類中窘问,有兩個用于http請求的庫類辆童,在Android2.2之前,推薦使用HttpClient惠赫,而在Android2.2之后推薦使用HttpURLConnection把鉴。

優(yōu)點

相比于官方的原生網(wǎng)絡請求庫,OkHttp有以下優(yōu)點:

  1. 支持HTTP/2, HTTP/2通過使用多路復用技術(shù)在一個單獨的TCP連接上支持并發(fā),通過在一個連接上一次性發(fā)送多個請求來發(fā)送或接收數(shù)據(jù)
  2. 通過連接池復用技術(shù)(Http)減少延時
  3. 支持Gzip降低下載大小
  4. 支持響應緩存避免了重復請求的網(wǎng)絡

get流程

官方示例

//1.創(chuàng)建OkHttpClient
OkHttpClient client = new OkHttpClient();

//2.創(chuàng)建Request儿咱,并填入url信息
String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  //3.通過OkHttpClient的newcall進行同步請求
  Response response = client.newCall(request).execute();
  //4.返回請求結(jié)果
  return response.body().string();
}

流程分析

1.創(chuàng)建OkHttpClient

構(gòu)建了一個OkHttpClient

//1.創(chuàng)建OkHttpClient
OkHttpClient client = new OkHttpClient();

public OkHttpClient() {
    //通過默認builder構(gòu)建一個新的OkHttpClient
    this(new Builder());
  }
  

//初始化builder用于配置各種參數(shù)
public Builder() {
      //異步請求的執(zhí)行策略調(diào)度器
      dispatcher = new Dispatcher();
      //默認的協(xié)議列表
      protocols = DEFAULT_PROTOCOLS;
      //默認的連接規(guī)范
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      //指標事件的監(jiān)聽器
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      //默認的代理選擇器
      proxySelector = ProxySelector.getDefault();
      //默認不管理cookie
      cookieJar = CookieJar.NO_COOKIES;
      //默認的socket工廠
      socketFactory = SocketFactory.getDefault();
      //默認的主機名驗證
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      //固定證書后雷,默認不開啟
      certificatePinner = CertificatePinner.DEFAULT;
      //響應服務器身份驗證質(zhì)詢苞慢,默認不進行響應
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;
      //初始化連接池
      connectionPool = new ConnectionPool();
      //默認DNS
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      retryOnConnectionFailure = true;
      //超時時間
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }

2.創(chuàng)建Request,并填入url信息

通過構(gòu)造器模式創(chuàng)建Request對象

//2.創(chuàng)建Request,并填入url信息
String run(String url) throws IOException {
    Request request = new Request.Builder()
      .url(url)
      .build();

首先通過Builder()方法構(gòu)建一個Builder對象

//Builder 
public Builder() {
      //默認方法為get
      this.method = "GET";
      //初始化一個空的請求頭
      this.headers = new Headers.Builder();
    }

然后通過url(url)方法對url進行了設(shè)置

public Builder url(String url) {
      //檢測傳入的url是否為空
      if (url == null) throw new NullPointerException("url == null");

      //將socket url替換為Http url
      if (url.regionMatches(true, 0, "ws:", 0, 3)) {
        url = "http:" + url.substring(3);
      } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
        url = "https:" + url.substring(4);
      }

      //根據(jù)傳入的url生成一個HttpUrl
      HttpUrl parsed = HttpUrl.parse(url);
      //如果為空則說明傳入的不是一個格式正確的http/https url
      if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
      //將http傳入url,返回Builder
      return url(parsed);
    }

//將傳入的HttpUrl賦值為成員變量后返回Builder
public Builder url(HttpUrl url) {
      if (url == null) throw new NullPointerException("url == null");
      this.url = url;
      return this;
    }

最后調(diào)用build()方法完成Request的創(chuàng)建

public Request build() {
      //檢測url是否為空置森,若為空則拋出異常
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
    }
    
//Request
Request(Builder builder) {
    //請求地址
    this.url = builder.url;
    //請求方法
    this.method = builder.method;
    //請求頭
    this.headers = builder.headers.build();
    //請求體
    this.body = builder.body;
    //tag標記愕难,可用來統(tǒng)一刪除
    this.tag = builder.tag != null ? builder.tag : this;
  }

可見url是創(chuàng)建Request時不可缺少的一個部分丰捷,一個Request中必須填入其url
而Request中包換五個部分篡石,除tag外分別與Http請求中的請求地址、請求方法使套、請求頭和請求體四部分別對應罐呼。至于Http請求所需的請求協(xié)議,Okhttp是通過使用請求協(xié)議的協(xié)商升級來進行確定的侦高。

3.通過OkHttpClient的newcall進行同步請求

第三步是整個網(wǎng)絡請求中的重中之重嫉柴,它通過對我們的Request進行解析生成相應的call來獲取我們所需的Response。

Response response = client.newCall(request).execute();

首先通過newCall(request)方法根據(jù)請求創(chuàng)建了一個call

@Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }


static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    //傳入?yún)?shù)生成Realcall
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    //為生成call創(chuàng)建一個eventListener實例奉呛,用于監(jiān)聽請求的各個階段
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
  }

//三個參數(shù)分別對應之前創(chuàng)建的OkHttpClient计螺,傳入的Request,已經(jīng)是否為WebSocket此時為false
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    //將傳入的參數(shù)賦值給對應的變量
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    //生成一個RetryAndFollowUpInterceptor,用于失敗重試以及重定向
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
  }

然后通過execute()進行同步請求

@Override public Response execute() throws IOException {
    synchronized (this) {
      //如果該請求正在運行拋出異常瞧壮,否則將運行標志位置為true登馒,防止重復請求
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    //捕獲調(diào)用堆棧的跟蹤
    captureCallStackTrace();
    //告知eventlisten請求開始
    eventListener.callStart(this);
    try {
      //通過dispatcher的executed來實際執(zhí)行
      client.dispatcher().executed(this);
      //經(jīng)過一系列"攔截"操作后獲取結(jié)果
      Response result = getResponseWithInterceptorChain();
      //如果result為空拋出異常
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      //告知eventlisten請求失敗
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      //通知dispatcher執(zhí)行完畢
      client.dispatcher().finished(this);
    }
  }

在這一步中 client.dispatcher().executed(this) 僅僅是將call加入一個隊列,并沒有真正開始進行網(wǎng)絡請求

synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

真正開始進行網(wǎng)絡請求的方法是getResponseWithInterceptorChain()咆槽,這也是此次網(wǎng)絡請求中最為重要的一個方法

Response getResponseWithInterceptorChain() throws IOException {
    //創(chuàng)建一個攔截器數(shù)組用于存放各種攔截器
    List<Interceptor> interceptors = new ArrayList<>();
    //向數(shù)組中添加用戶自定義的攔截器
    interceptors.addAll(client.interceptors());
    //1.向數(shù)組中添加retryAndFollowUpInterceptor用于失敗重試和重定向 
    interceptors.add(retryAndFollowUpInterceptor);
    //2.向數(shù)組中添加BridgeInterceptor用于把用戶構(gòu)造的請求轉(zhuǎn)換為發(fā)送給服務器的請求陈轿,把服務器返回的響應轉(zhuǎn)換為對用戶友好的響應。
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //3.向數(shù)組中添加CacheInterceptor用于讀取緩存以及更新緩存
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //4.向數(shù)組中添加ConnectInterceptor用于與服務器建立連接
    interceptors.add(new ConnectInterceptor(client));
    //如果不是webSocket添加networkInterceptors
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    //5.向數(shù)組中添加CallServerInterceptor用于從服務器讀取響應的數(shù)據(jù)
    interceptors.add(new CallServerInterceptor(forWebSocket));
    //根據(jù)上述的攔截器數(shù)組生成一個攔截鏈
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
    //通過攔截鏈的proceed方法開始整個攔截鏈事件的傳遞
    return chain.proceed(originalRequest);
  }

在getResponseWithInterceptorChain()方法中我們可以發(fā)現(xiàn)有許多不同功能的攔截器,主要列舉一下默認已經(jīng)實現(xiàn)的幾個攔截器的作用:

  1. retryAndFollowUpInterceptor 負責失敗重試和重定向
  2. BridgeInterceptor 負責把用戶構(gòu)造的請求轉(zhuǎn)換為發(fā)送給服務器的請求麦射,把服務器返回的響應轉(zhuǎn)換為對用戶友好的響應蛾娶。
  3. CacheInterceptor 負責讀取緩存以及更新緩存
  4. ConnectInterceptor 負責建立連接
  5. CallServerInterceptor 負責發(fā)送和讀取數(shù)據(jù)

而這些攔截器的具體實現(xiàn)我們后續(xù)在看,按照流程在這個方法中通過new RealInterceptorChain()生成了一個攔截鏈潜秋,然后通過它proceed()方法開始運行這條攔截鏈

//RealInterceptorChain的構(gòu)造函數(shù)主要是將傳入的參數(shù)用變量記錄下來蛔琅,其中的index參數(shù)用來記錄當前的攔截器
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
      EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
    this.call = call;
    this.eventListener = eventListener;
    this.connectTimeout = connectTimeout;
    this.readTimeout = readTimeout;
    this.writeTimeout = writeTimeout;
  }
  
  //調(diào)用proceed傳入request
  @Override public Response proceed(Request request) throws IOException {
    //在本例中streamAllocation、httpCodec半等、connection均為null
    return proceed(request, streamAllocation, httpCodec, connection);
  }

  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    //當index大于攔截器數(shù)組的大小時拋出異常
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // 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);
    //調(diào)用當前攔截器的intercept(),并傳入下一個攔截器的攔截鏈
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    if (response.body() == null) {
      throw new IllegalStateException(
          "interceptor " + interceptor + " returned a response with no body");
    }

    return response;
  }

每個攔截器的intercept()方法各不相同呐萨,下面按照前文的添加順序具體分析其實現(xiàn)與功能
1.RetryAndFollowUpInterceptor

@Override public Response intercept(Chain chain) throws IOException {
    //從傳入攔截鏈中獲取request杀饵、call、eventListener
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();

    //創(chuàng)建一個StreamAllocation谬擦,傳遞給后面的攔截鏈切距,用于管理Connections、Streams惨远、Calls三者之間的關(guān)系
    streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
        call, eventListener, callStackTrace);

    //記錄重試次數(shù)
    int followUpCount = 0;
    Response priorResponse = null;
    //開啟循環(huán)
    while (true) {
      //判斷是否取消谜悟,如果取消則通過streamAllocation釋放連接并拋出IOException
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;
      try {
        //調(diào)用傳入的攔截鏈的proceed方法,執(zhí)行下一個攔截器北秽,捕獲拋出的異常并進行處理
        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(), false, request)) {
          throw e.getLastConnectException();
        }
        releaseConnection = false;
        continue;
      } catch (IOException e) {
         //捕獲到IO異常贺氓,判斷是否要恢復蔚叨,否的話拋出異常
        // An attempt to communicate with a server failed. The request may have been sent.
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, requestSendStarted, request)) throw e;
        releaseConnection = false;
        continue;
      } finally {
        // We're throwing an unchecked exception. Release any resources.
        //根據(jù)標志位releaseConnection判斷是否需要釋放連接
        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();
      }

      //根據(jù)response來生成一個Request對象用于重定向和重連
      Request followUp = followUpRequest(response);

      //如果followUp為空,則說明無須重連或重定向辙培,直接釋放連接返回response
      if (followUp == null) {
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }

      //調(diào)用ResponseBody的close方法蔑水,關(guān)閉stream和相關(guān)資源
      closeQuietly(response.body());

      //重連次數(shù)高于限定次數(shù)(20)直接釋放連接拋出異常
      if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      //Request的請求體屬于不可重復提交的請求體則關(guān)閉連接拋出異常
      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);
      } 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;
    }
  }

上述代碼即為RetryAndFollowUpInterceptor intercept的實現(xiàn)流程,它主要負責失敗重試和重定向扬蕊。簡化流程如下:

  1. 實例化StreamAllocation傳入到接下來的攔截鏈中
  2. 開啟循環(huán)搀别,執(zhí)行下一個調(diào)用鏈(攔截器),等待響應(Response)
  3. 如果等待響應(Response)的過程中拋出異常尾抑,根據(jù)異常決定是否進行重連或重定向歇父,否:退出
  4. 根據(jù)響應生成的followUp決定是否進行重連或重定向,否:返回響應(Response)
  5. 關(guān)閉響應結(jié)果
  6. 判斷重連數(shù)是否達到最大值再愈,是:釋放連接庶骄、退出
  7. 判斷followUp的請求體是否能重復提交,否:釋放連接践磅、退出
  8. 檢測是否為相同連接单刁,否:重新實例化StreamAllocation
  9. 循環(huán)以上步驟

2.BridgeInterceptor
根據(jù)攔截鏈的proceed方法可知,會調(diào)用到BridgeInterceptor的intercept()方法

@Override public Response intercept(Chain chain) throws IOException {
    //從傳入攔截鏈中獲取request以及requestBuilder
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();

    //獲取request的請求體,若不為空則添加部分請求頭信息
    RequestBody body = userRequest.body();
    if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {
        //添加contentType
        requestBuilder.header("Content-Type", contentType.toString());
      }

      //根據(jù)contentLength確定添加Content-Length還是Transfer-Encoding
      long contentLength = body.contentLength();
      if (contentLength != -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }
    }

    //若無自定義host,添加默認host
    if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }

    //若無自定義Connection,添加默認Connection(Keep-Alive)
    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive");
    }

    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }

    //如果在創(chuàng)建OKHttpClient時創(chuàng)建的cookieJar不為NO_COOKIE羔飞,且cookie不為空則添加Cookie
    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }

    //若User-Agent為空肺樟,則添加默認User-Agent,默認為OkHttp版本號逻淌,該例為okhttp/3.9.1
    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", Version.userAgent());
    }

    //初始化添加了頭信息的request并傳入下一個攔截鏈中
    Response networkResponse = chain.proceed(requestBuilder.build());

    //請求完成后么伯,根據(jù)返回的response存儲cookies(如果需要,否則該方法不作任何操作)
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);

   //判斷服務器是否支持gzip壓縮格式,如果支持則交給kio壓縮
    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }

    //返回處理后的response
    return responseBuilder.build();
  }

上述代碼就是BridgeInterceptor intercept的實現(xiàn)流程卡儒,它主要用于負責把用戶構(gòu)造的請求轉(zhuǎn)換為發(fā)送給服務器的請求田柔,把服務器返回的響應轉(zhuǎn)換為對用戶友好的響應。簡化流程如下:

  1. 根據(jù)request信息骨望,為請求添加頭信息
  2. 將封裝好的request傳入下一個攔截鏈,并返回Response
  3. 根據(jù)返回的response進行cookie硬爆、Gzip處理
  4. 返回處理好的Gzip

3.CacheInterceptor

@Override public Response intercept(Chain chain) throws IOException {
    //讀取配置中的候選緩存,讀取序列依次為OkHttpClient中的cache擎鸠、internalCache和null缀磕。
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();

    //根據(jù)cacheCandidate創(chuàng)建緩存策略
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;

    //緩存監(jiān)測
    if (cache != null) {
      cache.trackResponse(strategy);
    }

    //若未找到合適的緩存關(guān)閉stream和相關(guān)資源
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    //若根據(jù)緩存策略,不適用網(wǎng)絡請求即networkRequest為null劣光,且無相應緩存袜蚕,即cacheResponse為null,直接報錯返回504
    // If we're forbidden from using the network and the cache is insufficient, fail.
    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)絡绢涡,緩存有效牲剃。直接返回緩存的Response
    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    //若需要使用網(wǎng)絡則通過攔截鏈啟動下一個攔截器發(fā)起網(wǎng)絡請求
    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    //當本地有緩存
    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      //網(wǎng)絡請求返回304,即緩存數(shù)據(jù)未過期雄可,根據(jù)本地緩存響應和網(wǎng)絡請求響應生成Response
      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();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        //否則緩存數(shù)據(jù)過期颠黎,關(guān)閉緩存。
        closeQuietly(cacheResponse.body());
      }
    }

    //根據(jù)網(wǎng)絡請求返回的數(shù)據(jù)生成response
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    //如果需要OKHttpClient需要使用緩存
    if (cache != null) {
      //如果response存在body且允許緩存滞项,則進行本地化緩存
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }

      //有的請求方法會使緩存無效狭归,此時清除緩存
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    //返回響應
    return response;
  }

上述代碼即為CacheInterceptor的intercept()方法的運行流程,主要負責讀取緩存以及更新緩存文判,簡化流程如下:

  1. 讀取OkhttpClient配置緩存过椎,可能為null
  2. 生成相應的緩存策略
  3. 根據(jù)緩存策略如果不使用網(wǎng)絡且無相應緩存,則直接返回504
  4. 根據(jù)緩存策略如果不使用網(wǎng)絡但相應緩存戏仓,則直接返回緩存響應
  5. 根據(jù)緩存策略如果使用網(wǎng)絡疚宇,則通過攔截鏈啟動下一個攔截器發(fā)起網(wǎng)絡請求
  6. 根據(jù)網(wǎng)絡響應,確定緩存是否過期赏殃,若未過期(返回304)則返回緩存
  7. 如果緩存過期敷待,關(guān)閉緩存并生成網(wǎng)絡請求的response
  8. 根據(jù)緩存要求進行本地緩存
  9. 返回網(wǎng)絡請求的response

4.ConnectInterceptor 負責建立連接

@Override public Response intercept(Chain chain) throws IOException {
    //從傳入的攔截鏈中獲取request和streamAllocation(RetryAndFollowUpInterceptor中初始化傳入)
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    //初始化HttpCodec,在newStream方法中會通過findHealthyConnection()方法依次嘗試從當前連接仁热、連接池榜揖、其他線路的連接池、新建連接的順序中獲取到RealConnection,然后通過RealConnection的newCodec方法分別根據(jù)Http2举哟、Http協(xié)議生成httpCodec
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    //獲取RealConnection
    RealConnection connection = streamAllocation.connection();
Connection
    //通過攔截鏈啟動下一個攔截器,并將httpCodec和connection傳入
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }

上述代碼即為ConnectInterceptor的intercept()方法的運行流程思劳,負責連接的建立,簡化流程如下:

  1. 讀取OkhttpClient配置request和streamAllocation
  2. 初始化HttpCodec
  3. 獲取RealConnection
  4. 通過攔截鏈啟動下一個攔截器,并將2妨猩、3步的對象傳入

5.CallServerInterceptor

@Override public Response intercept(Chain chain) throws IOException {
    // 通過攔截鏈獲取在ConnectInterceptor中完成初始化的HttpCodec和RealConnection潜叛,以及streamAllocation和request
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    HttpCodec httpCodec = realChain.httpStream();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    RealConnection connection = (RealConnection) realChain.connection();
    Request request = realChain.request();

    long sentRequestMillis = System.currentTimeMillis();
    //通知eventListener
    realChain.eventListener().requestHeadersStart(realChain.call());
    //寫請求頭
    httpCodec.writeRequestHeaders(request);
    realChain.eventListener().requestHeadersEnd(realChain.call(), request);

    Response.Builder responseBuilder = null;
    //若請求方法允許傳輸請求體,且request的請求體不為空
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      //如果在請求頭中存在"Expect:100-continue"壶硅,說明該請求需要等待服務器回復是否能夠處理請求體威兜,服務器若不接受請求體則會返回一個非空的編碼
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        realChain.eventListener().responseHeadersStart(realChain.call());
        //接收服務器的返回請求,服務器若不接受請求體則會返回一個非空的響應
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      //若responseBuilder為null庐椒,則Expect不為100-continue或服務器接收請求體,開始寫入請求體
      if (responseBuilder == null) {
        // Write the request body if the "Expect: 100-continue" expectation was met.
        realChain.eventListener().requestBodyStart(realChain.call());
        long contentLength = request.body().contentLength();
        CountingSink requestBodyOut =
            new CountingSink(httpCodec.createRequestBody(request, contentLength));
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
        realChain.eventListener()
            .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
      } else if (!connection.isMultiplexed()) {
        // 如果服務器拒絕接收請求體,且不是http2椒舵,則禁止此連接被重新使用
        streamAllocation.noNewStreams();
      }
    }

    //完成請求寫入
    httpCodec.finishRequest();

    //通過httpCodec獲取響應頭
    if (responseBuilder == null) {
      realChain.eventListener().responseHeadersStart(realChain.call());
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    //通過responseBuilder填入信息創(chuàng)建Response
    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    realChain.eventListener()
        .responseHeadersEnd(realChain.call(), response);

    //獲取返回碼
    int code = response.code();
    //如果是101(升級到Http2協(xié)議),則返回一個EMPTY_RESPONSE的響應體
    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      //寫入服務器返回的響應體
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

    //若請求或者服務器要求斷開連接扼睬,則斷開
    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    //若返回204/205(服務器均未返回響應體)且響應體長度大于)則拋出異常
    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    //返回響應
    return response;
  }

上述代碼即為CallServerInterceptor的intercept()方法的運行流程逮栅,負責發(fā)送請求數(shù)據(jù)和讀取響應數(shù)據(jù)悴势,簡化流程如下:

  1. 讀取HttpCodec窗宇、RealConnection等對象
  2. 寫入請求頭
  3. 若需要(由請求方法和服務器決定),寫入請求體
  4. 讀取響應頭信息
  5. 若請求或響應要求斷開連接特纤,則斷開連接
  6. 根據(jù)響應碼讀取響應體
  7. 處理204/205的異常情況
  8. 返回響應

至此默認的五個攔截器的實現(xiàn)和功能都已經(jīng)分析完了军俊,但由于篇幅有限,所以其中有些對象并沒有深入分析捧存,如streamAllocation粪躬、HttpCodec等

4.獲取Response的響應結(jié)果

  //4.返回請求結(jié)果
  return response.body().string();

此時的response就是第三步中通過newCall獲取到的response

public @Nullable ResponseBody body() {
    return body;
  }

//以Content-Type標頭的字符集解碼的字符串形式返回響應,若未標明則用UTF-8
public final String string() throws IOException {
    BufferedSource source = source();
    try {
      Charset charset = Util.bomAwareCharset(source, charset());
      return source.readString(charset);
    } finally {
      Util.closeQuietly(source);
    }
  }

結(jié)語

本篇分析了官方示例中g(shù)et操作的流程昔穴,最大的特點則在于通過攔截器鏈來實現(xiàn)責任鏈鏈镰官,從而完成整個網(wǎng)絡請求的流程。
本篇文章是個人學習的總結(jié)吗货,本人能力有限泳唠,如果有錯誤歡迎斧正,謝謝宙搬。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末笨腥,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子勇垛,更是在濱河造成了極大的恐慌脖母,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闲孤,死亡現(xiàn)場離奇詭異谆级,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門哨苛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鸽凶,“玉大人,你說我怎么就攤上這事建峭〔=模” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵亿蒸,是天一觀的道長凑兰。 經(jīng)常有香客問我,道長边锁,這世上最難降的妖魔是什么姑食? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮茅坛,結(jié)果婚禮上音半,老公的妹妹穿的比我還像新娘。我一直安慰自己贡蓖,他們只是感情好曹鸠,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著斥铺,像睡著了一般彻桃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上晾蜘,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天邻眷,我揣著相機與錄音,去河邊找鬼剔交。 笑死肆饶,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的岖常。 我是一名探鬼主播驯镊,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼腥椒!你這毒婦竟也來了阿宅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤笼蛛,失蹤者是張志新(化名)和其女友劉穎洒放,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體滨砍,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡往湿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年妖异,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片领追。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡他膳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出绒窑,到底是詐尸還是另有隱情棕孙,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布些膨,位于F島的核電站蟀俊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏订雾。R本人自食惡果不足惜肢预,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望洼哎。 院中可真熱鬧烫映,春花似錦、人聲如沸噩峦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽壕探。三九已至冈钦,卻和暖如春郊丛,著一層夾襖步出監(jiān)牢的瞬間李请,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工厉熟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留导盅,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓揍瑟,卻偏偏與公主長得像白翻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子绢片,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容