淺析okHttp3的網(wǎng)絡(luò)請求流程

寫在開頭

okHttp目前可以稱的上是Android主流網(wǎng)絡(luò)框架,甚至連谷歌官方也將網(wǎng)絡(luò)請求的實現(xiàn)替換成okHttp.

網(wǎng)上也有很多人對okHttp的源碼進行了分析,不過基于每個人的分析思路都不盡相同,讀者看起來的收獲也各不相同,所以我還是整理了下思路,寫了點自己的分析感悟。

本文基于okhttp3.11.0版本分析

基本用法

String url = "http://www.baidu.com";
//'1. 生成OkHttpClient實例對象'
OkHttpClient okHttpClient = new OkHttpClient();
//'2. 生成Request對象'
Request request = new Request.Builder()
    .url(url)
    .post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"),"test content"))
    .build();
Call call = okHttpClient.newCall(request);

call.enqueue(new Callback() {
    @Override
    public void onFailure(@NonNull Call call, @NonNull IOException e) {
    }

    @Override
    public void onResponse(@NonNull Call call, @NonNull Response response)  {

    }
});

整體流程

借用別人的一張流程圖來概括一下okHttp的請求走向 原圖出處

image
okHttp的整體流程大致分為以下幾個階段
  1. 創(chuàng)建請求對象 (url, method,body)-->request-->Call

  2. 請求事件隊列,線程池分發(fā) enqueue-->Runnable-->ThreadPoolExecutor

  3. 遞歸Interceptor攔截器,發(fā)送請求。 InterceptorChain

  4. 請求回調(diào),數(shù)據(jù)解析。 Respose-->(code,message,requestBody)

創(chuàng)建請求對象

其中 Request維護請求對象的屬性

public final class Request {
    final HttpUrl url;  
    final String method;
    final Headers headers;
    final @Nullable RequestBody body;
    //請求的標(biāo)記,在okHttp2.x的時候,okHttpClint提供Cancel(tag)的方法來批量取消請求
    //不過在3.x上批量請求的api被刪除了,要取消請求只能在Callback中調(diào)用 call.cancel()
    //因此這個tags參數(shù)只能由開發(fā)者自己編寫函數(shù)來實現(xiàn)批量取消請求的操作
    final Map<Class<?>, Object> tags;  
}

請求響應(yīng)的包裝接口Call

public interface Call extends Cloneable {
    Request request();
    Response execute() throws IOException;
    void enqueue(Callback responseCallback);
    void cancel();
}

請求事件隊列,線程池分發(fā)

Call的實現(xiàn)類RealCallAsyncCall

  @Override 
  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));
  }

//其中AsyncCall是RealCall的一個內(nèi)部類,繼承自Runnable,這樣就能通過線程池來回調(diào)AsyncCall的execute函數(shù)

final class AsyncCall extends NamedRunnable {
    @Override 
    protected void execute() {
        boolean signalledCallback = false;
        try {
            //getResponseWithInterceptorChain 攔截鏈的邏輯,也是發(fā)起請求的真正入口
            Response response = getResponseWithInterceptorChain();
            if (retryAndFollowUpInterceptor.isCanceled()) {
                signalledCallback = true;
                responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
            } else {
                signalledCallback = true;
                responseCallback.onResponse(RealCall.this, response);
            }
        } catch (IOException e) {
            ...
        } 
        ...
    }
}

遞歸Interceptor攔截器,發(fā)送請求

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    //用戶自定義的攔截器(注意addAll 所以可以添加多個自定義的攔截器)
    interceptors.addAll(client.interceptors());
    //重試與重定向攔截器
    interceptors.add(retryAndFollowUpInterceptor);
    //內(nèi)容攔截器
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //緩存攔截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //網(wǎng)絡(luò)連接攔截器
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      //用戶自定義的網(wǎng)絡(luò)攔截器
      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);
  }

okHttp的核心部分就是這個Interceptor攔截鏈,每個Interceptor各自負責(zé)一部分功能,內(nèi)部通過遞歸的方式遍歷每一個Interceptor攔截器。遞歸邏輯在RealInterceptorChain類下

public final class RealInterceptorChain implements Interceptor.Chain {
    
    //攔截器遞歸的入口
    public Response proceed(Request request, StreamAllocation streamAllocation,
               HttpCodec httpCodec, RealConnection connection) throws IOException {
     ...
    //攔截器遞歸的核心代碼,根據(jù)interceptors列表執(zhí)行每一個攔截器的intercept函數(shù)
    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;
  }
}

遞歸結(jié)束后會獲得請求響應(yīng),那么說明我們的request行為就在這個攔截鏈中,接下來我們先看看負責(zé)網(wǎng)絡(luò)請求的那部分攔截器,從類名上就能比較容易的看出 ConnectInterceptorCallServerInterceptor這兩個攔截器的主要工作。

網(wǎng)絡(luò)連接攔截器ConnectInterceptor
public final class ConnectInterceptor implements Interceptor {
  
    @Override 
    public Response intercept(Chain chain) throws IOException {
        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 httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
        RealConnection connection = streamAllocation.connection();

        return realChain.proceed(request, streamAllocation, httpCodec, connection);
    }
}

其中有幾個對象說明一下

  • StreamAllocation:內(nèi)存流的存儲空間,這個對象可以直接從realChain中直接獲取,說明在之前的攔截鏈中就已經(jīng)賦值過

  • HttpCodec(Encodes HTTP requests and decodes HTTP responses): 對請求的編碼以及對響應(yīng)數(shù)據(jù)的解碼

  • realChain.proceed():通知下一個攔截器執(zhí)行

接下來看創(chuàng)建HttpCodec對象的newStream函數(shù)中做了些什么

//HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);

public HttpCodec newStream(
      OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    
    ...
    try {
      //findHealthyConnection內(nèi)部通過一個死循環(huán)查找一個可用的連接,優(yōu)先使用存在的可用連接,否則就通過      //線程池來生成,其中多處使用 synchronized關(guān)鍵字,防止因為多并發(fā)導(dǎo)致問題
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
      HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

      synchronized (connectionPool) {
        codec = resultCodec;
        return resultCodec;
      }
    } catch (IOException e) {
      throw new RouteException(e);
    }
  }

沿著代碼往下走,你會發(fā)現(xiàn)實際上負責(zé)網(wǎng)絡(luò)連接功能的類是一個叫RealConnection的類,該類中有一個connect的函數(shù)

RealConnection#connect

 public void connect(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled, Call call,
      EventListener eventListener) {
    ...
        
    while (true) {
      try {
        if (route.requiresTunnel()) {
          //這個函數(shù)最終還是會走到connectSocket()函數(shù)中
          connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
          if (rawSocket == null) {
            // We were unable to connect the tunnel but properly closed down our resources.
            break;
          }
        } else {
          connectSocket(connectTimeout, readTimeout, call, eventListener);
        }
      }
      ...
  }
  
  //最終調(diào)用的還是Socket對象來創(chuàng)建網(wǎng)絡(luò)連接,包括connectTimeout,readTimeout等參數(shù)也是這個時候真正設(shè)置的。
網(wǎng)絡(luò)請求攔截器 CallServerInterceptor

This is the last interceptor in the chain. It makes a network call to the server.

直接看CallServerInterceptor的intercept函數(shù)

@Override
public Response intercept(Chain chain) throws IOException{
    //下面的各參數(shù)都是之前幾個攔截器所生成的
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    HttpCodec httpCodec = realChain.httpStream();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    RealConnection connection = (RealConnection) realChain.connection();
    Request request = realChain.request();
    
    //發(fā)送請求頭,也是網(wǎng)絡(luò)請求的開始
    httpCodec.writeRequestHeaders(request);
    
    Response.Builder responseBuilder = null;
    //請求不是get,并且有添加了請求體,寫入請求體信息
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      //如果請求頭中有Expect:100-continue這么一個屬性
      //會先發(fā)送一個header部分給服務(wù)器,并詢問服務(wù)器是否支持Expect:100-continue 這么一個擴展域
      //okhttp3提供這么個判斷是為了兼容http2的連接復(fù)用行為的
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        //刷新緩存區(qū),可以理解為向服務(wù)端寫入數(shù)據(jù)
        httpCodec.flushRequest();
        realChain.eventListener().responseHeadersStart(realChain.call());
        responseBuilder = httpCodec.readResponseHeaders(true);
      }
        
      //寫入請求body
      if (responseBuilder == null) {
        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);
      } 
      ...
    httpCodec.finishRequest();
    
    //響應(yīng)相關(guān)的代碼
    ...
}

寫入請求body的核心代碼

//將請求體寫入到BufferedSink中,而BufferedSink是另外一個類庫Okio中的類
CountingSink requestBodyOut =
            new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);


//httpCodec.finishRequest 最終會調(diào)用 sink.flush(),sink是BufferedSink的對象,BufferedSink在底層
//會將其內(nèi)的數(shù)據(jù)推給服務(wù)端,相當(dāng)于是一個刷新緩沖區(qū)的功能
httpCodec.finishRequest();

響應(yīng)相關(guān)的代碼

if (responseBuilder == null) {
    realChain.eventListener().responseHeadersStart(realChain.call());
    //讀取響應(yīng)頭,實際的返回流存放位置在okio庫下的buffer對象中,讀取過程中做了判斷,只有當(dāng)code==100時才會    //有返回,不然拋出異常并攔截,所以下面這段代碼肯定有響應(yīng)頭返回,不然直到超時也不會回調(diào)
    responseBuilder = httpCodec.readResponseHeaders(false);
}

Response response = responseBuilder
            .request(request)
    .handshake(streamAllocation.connection().handshake())
    .sentRequestAtMillis(sentRequestMillis)
    .receivedResponseAtMillis(System.currentTimeMillis())
    .build();

int code = response.code();
if (code == 100) {
    //如果服務(wù)端響應(yīng)碼為100,需要我們再次請求,注意這里的100是響應(yīng)碼和之前的100不同
    //之前的100是headerLine的標(biāo)識碼
    responseBuilder = httpCodec.readResponseHeaders(false);

    response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    code = response.code();
}

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 {
    //讀取響應(yīng)body
    response = response.newBuilder()
        .body(httpCodec.openResponseBody(response))
        .build();
}
return response;

讀取響應(yīng)body HttpCodec#openResponseBody

public ResponseBody openResponseBody(Response response) throws IOException {
    ...
    Source source = newFixedLengthSource(contentLength);
    return new RealResponseBody(contentType, contentLength, Okio.buffer(source));
    ...
}

//openResponseBody將Socket的輸入流InputStream對象交給OkIo的Source對象,然后封裝成RealResponseBody(該類是ResponseBody的子類)作為Response的body.

//具體讀取是在RealResponseBody父類ResponseBody中,其中有個string()函數(shù)

//響應(yīng)主體存放在內(nèi)存中,然后調(diào)用source.readString來讀取服務(wù)器的數(shù)據(jù)。需要注意的是該方法最后調(diào)用closeQuietly來關(guān)閉了當(dāng)前請求的InputStream輸入流,所以string()方法只能調(diào)用一次,再次調(diào)用的話會報錯
public final String string() throws IOException {
    BufferedSource source = source();
    try {
        Charset charset = Util.bomAwareCharset(source, charset());
        return source.readString(charset);
    } finally {
        Util.closeQuietly(source);
    }
}

請求回調(diào),數(shù)據(jù)解析

拿到請求回調(diào)的Response之后,再回到我們最開始調(diào)用的代碼,

String url = "http://www.baidu.com";
//'1. 生成OkHttpClient實例對象'
OkHttpClient okHttpClient = new OkHttpClient();
//'2. 生成Request對象'
Request request = new Request.Builder()
    .url(url)
    .post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"),"test content"))
    .build();
Call call = okHttpClient.newCall(request);

call.enqueue(new Callback() {
    @Override
    public void onFailure(@NonNull Call call, @NonNull IOException e) {
    }

    @Override
    public void onResponse(@NonNull Call call, @NonNull Response response)  {
        Headers responseHeaders = response.headers();
        for (int i = 0, size = responseHeaders.size(); i < size; i++) {
            System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
        }
        System.out.println(response.body().string());
    }
});

我們可以從Response對象中獲取所有我們所需要的數(shù)據(jù),包括header,body.至此,okHttp的網(wǎng)絡(luò)請求的大致流程已經(jīng)分析完成,至于還有部分沒有講到的攔截器就不再本文綴述了.有興趣的可以看下文末的參考連接或者自行谷歌。

參考文章

Okhttp之CallServerInterceptor簡單分析

okHttp各攔截器解析

Android技能樹 — 網(wǎng)絡(luò)小結(jié)之 OkHttp超超超超超超超詳細解析

OkHttp3.0解析 —— 從源碼的角度談?wù)劙l(fā)起網(wǎng)絡(luò)請求時做的操作

擴展閱讀

關(guān)于Http的請求頭 Expect:100-Continue

Expect請求頭部域,用于指出客戶端要求的特殊服務(wù)器行為。若服務(wù)器不能理解或者滿足
Expect域中的任何期望值,則必須返回417(Expectation Failed)狀態(tài),或者如果請求
有其他問題,返回4xx狀態(tài)。

Expect:100-Continue握手的目的,是為了允許客戶端在發(fā)送請求內(nèi)容之前,判斷源服務(wù)器是否愿意接受
請求(基于請求頭部)。
Expect:100-Continue握手需謹慎使用,因為遇到不支持HTTP/1.1協(xié)議的服務(wù)器或者代理時會引起問題。

http2比起http1.x的有點主要體現(xiàn)在以下幾點

  • 新的數(shù)據(jù)格式, http基于文件協(xié)議解析,http2基于二進制協(xié)議解析,
  • 連接共享,多路復(fù)用(MultiPlexing)
  • header壓縮,減小header的體積,使得請求更快
  • 壓縮算法從gzip改成HPACK的算法,防破解
  • 重置連接表現(xiàn)更好,http1.x取消請求的是直接斷開連接,http2則是斷開某個連接的stream流
  • 更安全的SSL

參考資料

http1.x與http2的區(qū)別

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

相關(guān)閱讀更多精彩內(nèi)容

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