開源框架 | OkHttp 請求流程源碼解析

1. 基本使用

1.1 創(chuàng)建 OkHttpClient

首先創(chuàng)建 OkHttpClient 用于配置網(wǎng)絡(luò)請求時連接時長,讀/寫數(shù)據(jù)時長,緩存路徑等參數(shù)信息:

        OkHttpClient mOkHttpClient;
        OkHttpClient.Builder builder = new OkHttpClient.Builder()
                .connectTimeout(15, TimeUnit.SECONDS) //連接最大時長
                .writeTimeout(20, TimeUnit.SECONDS) //客戶端寫數(shù)據(jù)最大時長
                .readTimeout(20, TimeUnit.SECONDS) //服務(wù)端讀數(shù)據(jù)最大時長
                .cache(new Cache(sdCache.getAbsoluteFile(), cacheSize)); //配置緩存路徑即緩存大小限制
        mOkHttpClient = builder.build();
1.2 創(chuàng)建 Request

創(chuàng)建 Request 用于設(shè)置連接的地址 url,請求方法(post/get),請求頭等信息:

        //get請求
        Request request = new Request.Builder()
                .method("GET",null)
                .url(url)
                .build();

        //post請求
        Request request = new Request.Builder()
                .url(url)
                .post(requestBody)
                .build();
1.3 創(chuàng)建 Call

通過 OkHttpClient 和 Request 創(chuàng)建 Call 用于處理請求的回調(diào):

        Call call = mOkHttpClient.newCall(request);
1.4 發(fā)起請求
  • 同步請求
        try {
            final Response execute = call.execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
  • 異步請求
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                    //處理請求失敗
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                  //處理請求成功
            }
        });

2. 源碼流程分析

OkHttp請求流程
2.1 同步請求

同步請求直接執(zhí)行 RealCall 的 execute() 方法,然后調(diào)用調(diào)度器 Dispatcher 的 execute() 方法將當(dāng)前同步請求加入同步請求隊(duì)列 runningSyncCalls 中,接著調(diào)用 getResponseWithInterceptorChain() 方法進(jìn)行攔截器的鏈?zhǔn)秸{(diào)用。

2.2 異步請求

異步請求會執(zhí)行 RealCall 的 enqueue() 方法,然后通過調(diào)度器 Dispatcher 調(diào)度異步請求,調(diào)度器中有三個隊(duì)列 readyAsyncCalls、runningAsyncCalls、runningSyncCalls 分別用于存儲將要執(zhí)行的異步請求、正在執(zhí)行的異步請求以及正在執(zhí)行的同步請求,如果當(dāng)前正在執(zhí)行的最大請求數(shù)小于最大請求數(shù) maxRequests(默認(rèn)為64)且未達(dá)到同一個主機(jī)名的最大請求數(shù) maxRequestsPerHost(默認(rèn)為5)則將當(dāng)前異步請求加入正在執(zhí)行的異步請求隊(duì)列 runningAsyncCalls 中,然后通過線程池執(zhí)行當(dāng)前異步請求。

異步請求執(zhí)行的是 RealCall 的內(nèi)部類 AsyncCall 的 run() 方法,該方法中調(diào)用 getResponseWithInterceptorChain() 方法進(jìn)行攔截器的鏈?zhǔn)秸{(diào)用。

2.3 總結(jié):
  • 不同:同步請求是在當(dāng)前線程執(zhí)行,而異步請求會在線程池中使用子線程執(zhí)行;
  • 相同:相同的是同步請求和異步請求都會調(diào)用 getResponseWithInterceptorChain() 方法進(jìn)行攔截器的鏈?zhǔn)秸{(diào)用實(shí)現(xiàn)發(fā)起請求、失敗重連、處理緩存、建立網(wǎng)絡(luò)連接、接受響應(yīng)等任務(wù)。

3. 攔截器

3.1 RetryAndFollowUpInterceptor

實(shí)現(xiàn)失敗重連和重定向的請求:


RetryAndFollowUpInterceptor攔截流程.png
  • 注意
    RetryAndFollowUpInterceptor 之前的攔截器 interceptors,在客戶端發(fā)起請求后只會被調(diào)用一次,而 RetryAndFollowUpInterceptor 之后添加的攔截器,比如 BridgeInterceptor、CacheInterceptor、ConnectInterceptor、networkInterceptor、CallServerInterceptor 在重定向或者重連時都會重復(fù)調(diào)用,這也是OkHttpClient 中 interceptors 和 networkInterceptor 兩類攔截器的區(qū)別 。
3.2 BridgeInterceptor

將用戶構(gòu)造的請求轉(zhuǎn)換為發(fā)送到服務(wù)器的請求,把服務(wù)器返回的響應(yīng)轉(zhuǎn)換為用戶友好的請求,是從程序代碼到網(wǎng)絡(luò)代碼的橋梁,主要實(shí)現(xiàn)了請 求頭 header 的封裝和響應(yīng)內(nèi)容的解壓:

  1. 設(shè)置請求內(nèi)容類型: Content-Type、內(nèi)容長度:Content-Length以及請求內(nèi)容編碼格式:Transfer-Encoding;
  2. 設(shè)置 Host、User-Agent ,設(shè)置 Connection 為 Keep-Alive;
  3. 添加 Cookie;
  4. 設(shè)置接受內(nèi)容編碼格式為 gzip,并在接受到響應(yīng)內(nèi)容后進(jìn)行解壓,省去了用戶處理數(shù)據(jù)的麻煩;
3.3 CacheInterceptor

OkHttp 的緩存使用的是 DiskLruCache,在 CacheInterceptor 的攔截方法 intercept() 中如果在 OkHttpClient 中配置了緩存,首先會從磁盤中獲取當(dāng)前請求的緩存 Cache;

  @Override public Response intercept(Chain chain) throws IOException {
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null; //從磁盤文件中返回當(dāng)前請求的緩存

    long now = System.currentTimeMillis();
    //創(chuàng)建緩存策略
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;
...
    // 不進(jìn)行網(wǎng)絡(luò)請求也不使用緩存
    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)")
          ...
          .build();
    }

    // 不使用網(wǎng)絡(luò)請求,使用緩存,直接返回緩存
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
    //開始網(wǎng)絡(luò)請求
    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    // 網(wǎng)絡(luò)請求和緩存都使用,返回的請求碼為 304,表示重定向
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        //根據(jù) cacheResponse 構(gòu)建新的響應(yīng),將 networkResponse 合并進(jìn)來
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            ...
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

        // 更新緩存 Cache
        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response; //返回由 cacheResponse 和 networkResponse 合并的響應(yīng)
      } else {
        closeQuietly(cacheResponse.body());
      }
    }
    //不使用緩存,根據(jù) networkResponse 構(gòu)建響應(yīng),將 cacheResponse 合并進(jìn)來
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (cache != null) { //磁盤中有該請求的緩存文件
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // 存入緩存 Cache
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }
      //請求方法不可使用緩存
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest); //移除緩存
        } catch (IOException ignored) {
        }
      }
    }
    return response;
  }
  1. 根據(jù)當(dāng)前時間、Request、從磁盤中獲取的緩存, 創(chuàng)建一個緩存策略 CacheStrategy;

緩存策略包含 networkRequest 和 cacheResponse 兩個變量,可用來判斷本次請求的響應(yīng)內(nèi)容是由網(wǎng)絡(luò)請求返回還是使用緩存內(nèi)容,還是兩者都使用,networkRequest 為空表示不使用網(wǎng)絡(luò)請求,cacheResponse 為空表示不使用緩存。

  1. 緩存策略中不使用網(wǎng)絡(luò)請求也不使用緩存,創(chuàng)建一個包含異常信息的 Response并返回,注意返回碼為 504
  2. 不使用網(wǎng)絡(luò)請求,但使用緩存直接返回緩存;
  3. 需要使用網(wǎng)絡(luò)請求,調(diào)用 chain.proceed() 執(zhí)行后續(xù)攔截器進(jìn)行網(wǎng)絡(luò)請求,如果緩存策略中有 cacheResponse 且網(wǎng)絡(luò)請求返回碼為 304(表明是重定向請求),使用緩存 cacheResponse 構(gòu)建 Response,更新緩存 Cache 并返回 Response;
  4. cacheResponse 為空,即不使用緩存,構(gòu)建網(wǎng)絡(luò)請求的 Response;
  5. 該請求在磁盤上有緩存即 Cache 不為空,把這個 Response 寫入緩存并返回。
3.4 ConnectInterceptor

建立客戶端和服務(wù)器之間的連接,為客戶端和服務(wù)器之間的通信做準(zhǔn)備。

  • StreamAllocation
    1. 作用:協(xié)調(diào) Connections、Streams、Calls 三個類之間的關(guān)系;
    2. newStream() 創(chuàng)建一個新的 Stream;
    3. findConnection():找到一個 RealConnection 用于管理新的 Stream:
      a. 如果已經(jīng)分配了連接,返回已分配的連接;
      b. 沒有分配過連接則從連接池 ConnectionPool 中返回一個可用的連接(這里的可用即和當(dāng)前連接的 Adress 主機(jī)名一致的連接 );
      c. 沒有可用的連接,為當(dāng)前請求創(chuàng)建一個 Route;
      d. 此時有了 Route,第二次從連接池 ConnectionPool 中找到一個匹配的連接并返回(在第一次基礎(chǔ)上加上對 Route 的判斷);
      e. 如果仍然沒有可用的連接,直接新建一個連接 RealConnection;
      f . 創(chuàng)建的新連接通過 RealConnection#connect() 執(zhí)行 TCP+TLS 握手
      g. 將這個新的連接存入連接池中,如果這個新的連接是多路復(fù)用(HTTP2.0支持多路復(fù)用,即多個請求可共用一個連接)的且和當(dāng)前連接是連接的同一個地址,釋放這個多路復(fù)用的連接并返回當(dāng)前連接;
    4. 回到 newStream() 中,根據(jù) findConnection() 返回的 RealConnection 創(chuàng)建一個新的 HttpCodec 即 HTTP編解碼器,用于編碼HTTP請求和解碼HTTP響應(yīng);
  • HttpCodec

    HttpCodec 是一個接口,它有兩個實(shí)現(xiàn)類 Http1Codec 和 Http2Codec:

    1. Http1Codec:基于 HTTP1.1協(xié)議,實(shí)現(xiàn) Socket 連接并通信;
    2. Http2Codec:基于 HTTP2.0協(xié)議,實(shí)現(xiàn)編碼請求和解碼響應(yīng);
  • RealConnection

    真正實(shí)現(xiàn)連接建立的類,調(diào)用 connect() 建立連接,下圖是連接建立的流程分析,從圖中可以看出,OkHttp 是使用 Socket 進(jìn)行網(wǎng)絡(luò)通信的,首先判斷是否有連接到代理服務(wù)器,有則創(chuàng)建代理請求然后連接到代理的服務(wù)器,沒有則直接連接到原始的服務(wù)器;接著根據(jù)連接地址 URL 是 http 還是 https,如果是 https 需要建立 tls 連接進(jìn)行身份驗(yàn)證:


    RealConnection連接建立流程.png
  • http 和 https 的區(qū)別:
    https 需要配證書,ssl 層用于驗(yàn)證證書,tls 是 ssl 3.0 以后的版本,可以認(rèn)為是 ssl 3.1。

  • ConnectionPool
    連接池,通過一個隊(duì)列 Deque<RealConnection> connections 存儲所有的已創(chuàng)建的連接,實(shí)現(xiàn)連接的復(fù)用,如果多個請求是請求的同一個主機(jī)地址,就不需要重復(fù)創(chuàng)建連接(三次握手),直接使用連接池中已有的指向同一個主機(jī)地址的連接;
  public ConnectionPool() {
    this(5, 5, TimeUnit.MINUTES);
  }
  public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {...}

構(gòu)造方法中指定了最大空閑連接數(shù) maxIdleConnections 默認(rèn)為 5,以及連接的最大存活時長 keepAliveDurationNs 默認(rèn)為 5 分鐘,連接池中連接的清理工作交給了線程池去處理;

  1. put():存入一個新的連接,存入連接之前會通過線程池清理掉不必要的連接;
  void put(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if (!cleanupRunning) {
      cleanupRunning = true;
      executor.execute(cleanupRunnable);
    }
    connections.add(connection);
  }
  1. cleanup():清理不必要的連接,這里的不必要是指超出了最大空閑連接數(shù) maxIdleConnections 或者超出了連接存活時長 keepAliveDurationNs 的連接;
  long cleanup(long now) {
    int inUseConnectionCount = 0;
    int idleConnectionCount = 0;
    RealConnection longestIdleConnection = null;
    long longestIdleDurationNs = Long.MIN_VALUE;

    synchronized (this) {
      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();

        if (pruneAndGetAllocationCount(connection, now) > 0) {
          inUseConnectionCount++; //記錄正在使用的連接數(shù)
          continue; //結(jié)束本次循環(huán)操作
        }

        idleConnectionCount++; //記錄空閑連接數(shù)
        long idleDurationNs = now - connection.idleAtNanos;
        if (idleDurationNs > longestIdleDurationNs) { //記錄空閑時長最長的連接
          longestIdleDurationNs = idleDurationNs; 
          longestIdleConnection = connection; 
        }
      }
        //拿到的這條連接空閑時長大于了連接默認(rèn)存活的最長時間或者空閑連接數(shù)大于了最大空閑連接數(shù)
      if (longestIdleDurationNs >= this.keepAliveDurationNs
          || idleConnectionCount > this.maxIdleConnections) {
        connections.remove(longestIdleConnection); //從連接隊(duì)列中清除這條連接
      } else if (idleConnectionCount > 0) { //空閑連接還沒達(dá)到最大存活時長,等待一段時間后再清除
        return keepAliveDurationNs - longestIdleDurationNs;
      } else if (inUseConnectionCount > 0) {//所有連接都在使用中,等待5分鐘后再處理清除操作
        return keepAliveDurationNs;
      } else {//連接隊(duì)列中沒有連接,不需要清理
        cleanupRunning = false;
        return -1;
      }
    }
    closeQuietly(longestIdleConnection.socket());
    // Cleanup again immediately.
    return 0;
  }

a. 遍歷連接隊(duì)列,記錄空閑連接數(shù),找到連接隊(duì)列中空閑時長最長的連接;
b. 如果空閑連接數(shù)大于了 maxIdleConnections 或者連接存活時間大于了 keepAliveDurationNs,從連接隊(duì)列中移除這個空閑連接;
c. 有空閑連接但沒必要清除,等待一段時間(達(dá)到最大存活時間)再清除;
d. 所有連接都在使用中,5分鐘 后再清理;

  1. get():從連接隊(duì)列中返回一個 Adress的主機(jī)名 一致或者 Route 匹配的連接,沒有則返回為null;
3.5 CallServerInterceptor

通過 HttpCodec 實(shí)現(xiàn)客戶端和服務(wù)器之間的通信:

  1. writeRequestHeaders() 向服務(wù)器發(fā)送 Request 的 header;
  2. 如果有 body 通過 createRequestBody(),向服務(wù)器發(fā)送 body;
  3. readResponseHeaders(),讀取服務(wù)器返回的 header 并構(gòu)造一個新的 Response,構(gòu)造 Response 時斷開客戶端和服務(wù)器的連接;
  4. 如果服務(wù)器返回的 Response 中有 body,通過 openResponseBody() 讀取返回的 body,在步驟3 的 Response 基礎(chǔ)上加上這里的 body 并構(gòu)建一個新的 Response 。

4. 總結(jié):

OkHttp 具有以下優(yōu)勢:
  1. 失敗自動重連:在 RetryAndFollowUpInterceptor 失敗重連重定向攔截器中,連接失敗時會自動嘗試重新連接,也可以處理訪問的重定向;
  2. 可以解壓編碼類型為 gzip 的響應(yīng):在 BridgeInterceptor 橋接攔截器中默認(rèn)支持解壓編碼類型為 gzip 的響應(yīng);
  3. 支持緩存:在 CacheInterceptor 緩存攔截器中,使用緩存避免頻繁的重復(fù)請求;
  4. 連接可復(fù)用:在 ConnectionPool 中實(shí)現(xiàn)連接復(fù)用,避免頻繁創(chuàng)建和斷開連接;
  5. OkHttp 使用 Socket 發(fā)送請求:Socket 由 RealConnection 維護(hù);
  6. 同主機(jī)名的請求共享一個 Socket:參考第4條,同主機(jī)名的請求共享同一條連接,同一條連接及共享同一個 Socket;

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容