Okhttp 01 核心流程詳解

1.dns解析,域名對應 ip
2.TCP建立連接,三次握手
3.C端向S端發送請求行命令
4.C端發送請求頭信息
5.S端應答,發送響應命令
6.S端發送響應頭信息
7.S端向C端發送數據,以及消息體
8.S端關閉鏈接 tcp 四次揮手

An HTTP client for Android, Kotlin, and Java.

https://square.github.io/okhttp/
源碼
https://github.com/square/okhttp

參考:
hencoder plus
https://juejin.im/post/5d6e7f256fb9a06ae57d0d9c
4.x
OkHttp works on Android 5.0+ (API level 21+) and on Java 8+.

The OkHttp 3.12.x branch supports Android 2.3+ (API level 9+) and Java 7+.
These platforms lack support for TLS 1.2 and should not be used.
But because upgrading is difficult we will backport critical fixes to the 3.12.x branch through December 31, 2020.

一個簡單請求 okhttp 做了什麼

  1. 創建⼀個 OkHttp 的實例
    OkHttpClient client = new OkHttpClient.Builder().build();

  2. 創建 Request
    Request request = new Request.Builder()
    .url(“http://hencoder.com”)
    .build();

  3. 創建 Call 併發起⽹絡請求

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

@Override 
public void onFailure(Call call, IOException e) {}

@Override public void onResponse(Call call, Response response) throws IOException{
Log.d("okhttp response", response.body().string());}
});

可以看到,使用okhttp發起網絡請求要經過4步:

  1. 創建OkHttpClient
  2. 創建請求Request
  3. 通過OkHttpClient和Request創建一個Call,用於發起網絡請求
  4. 調用Call的execute()或enqueue()方法發起同步或異步請求

本次分析的okhttp源碼版本是基本3.14.x

小結

okhttp的異步請求過程分析總結

  • 當我們調用call.enqueue(Callback)時,就會發起一個異步請求,
  • 實際執行的是realCall.enqueue(Callback),它比同步請求只是多了一個Callback參數,
  • 然後realCall.execute()中先把傳進來的Callback包裝成一個AsyncCall,
  • 然後執行Dispatcher的enqueue(AsyncCall)把這個異步請求任務保存進readyAsyncCalls隊列中,
  • 保存後開始執行 promoteAndExecute()進行異步任務的調度,
  • 它會先把符合條件的異步請求任務從readyAsyncCalls轉移到runningAsyncCalls隊列和添加到executableCalls列表中去,
  • 然後遍歷executableCalls列表,逐個執行AsyncCall 的executeOn(ExecutorService),
  • 然後在這個方法中AsyncCall會把自己放進Dispatcher 的線程池,等待線程池的調度
  • 當線程池執行到這個AsyncCall時,它的run方法就會被執行,從而執行重寫的execute()方法,execute()方法中的流程和同步請求流程大致相同。

下面是異步請求過程的調用鏈:

整個流程

  1. okhttp通過Builder模式創建OkHttpClient、Request和Response,
  2. 通過client.newCall(Resquest)創建一個Call,用於發起異步或同步請求
  3. 請求會經過Dispatcher、一系列攔截器,最後通過okio與服務器建立連接、發送數據並解析返回結果
    這個過程如圖:

1、創建OkHttpClient

OkHttpClient是okhttp中的大管家,它將具體的工作分發到各個子系統中去完成,它使用Builder模式配置網絡請求的各種參數如超時、攔截器、分發器等,Builder中可配置的參數如下:
builder 模式是,先賦值正確,在創建對象。set 是先給予默認值,然後修改。

//OkHttpClient.Builder
public static final class Builder {
    Dispatcher dispatcher;//分發器
    @Nullable Proxy proxy;//代理
    List<Protocol> protocols;//應用層協議
    List<ConnectionSpec> connectionSpecs;//傳輸層協議
    final List<Interceptor> interceptors = new ArrayList<>();//應用攔截器
    final List<Interceptor> networkInterceptors = new ArrayList<>();//網絡攔截器
    EventListener.Factory eventListenerFactory;//http請求回調監聽
    ProxySelector proxySelector;//代理選擇
    CookieJar cookieJar;//cookie
    @Nullable Cache cache;//網絡緩存
    @Nullable InternalCache internalCache;//內部緩存
    SocketFactory socketFactory;//socket 工廠
    @Nullable SSLSocketFactory sslSocketFactory;//安全套接層socket 工廠,用於HTTPS
    @Nullable CertificateChainCleaner certificateChainCleaner;//驗證確認響應證書,適用 HTTPS 請求連接的主機名
    HostnameVerifier hostnameVerifier;//主機名字確認
    CertificatePinner certificatePinner;//證書鏈
    Authenticator proxyAuthenticator;//代理身份驗證
    Authenticator authenticator;//本地身份驗證
    ConnectionPool connectionPool;//連接池,複用連接
    Dns dns;//域名
    boolean followSslRedirects;//安全套接層重定向
    boolean followRedirects;//本地重定向
    boolean retryOnConnectionFailure;//錯誤重連
    int callTimeout;//請求超時,它包括dns解析、connect、read、write和服務器處理的時間
    int connectTimeout;//connect超時
    int readTimeout;//read超時
    int writeTimeout;//write超時
    int pingInterval;//ping超時

    //這裏是配置默認的參數
    public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;//Protocol.HTTP_2和Protocol.HTTP_1_1
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      if (proxySelector == null) {
        proxySelector = new NullProxySelector();
      }
      cookieJar = CookieJar.NO_COOKIES;
      socketFactory = SocketFactory.getDefault();
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      certificatePinner = CertificatePinner.DEFAULT;
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;
      connectionPool = new ConnectionPool();
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      retryOnConnectionFailure = true;
      callTimeout = 0;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }

    //這裏通過另外一個OkHttpClient配置參數
    Builder(OkHttpClient okHttpClient) {
      this.dispatcher = okHttpClient.dispatcher;
      this.proxy = okHttpClient.proxy;
      this.protocols = okHttpClient.protocols;
      //...
    }
    
    //...
    
    //配置完參數後,通過Builder的參數創建一個OkHttpClient
    public OkHttpClient build() {
        return new OkHttpClient(this);
    }
}

2、創建請求Request

在 okhttp 中 Request 代表着一個HTTP請求,它封裝了請求的具體消息,如url、header、body等,它和OkHttpClient一樣都是使用Budiler模式來配置自己的參數,如下:

//Request.Budiler
public static class Builder {
    HttpUrl url;
    String method;
    Headers.Builder headers;
    RequestBody body;

    //這裏配置默認的參數
    public Builder() {
      this.method = "GET";//默認是GET請求
      this.headers = new Headers.Builder();
    }

    //這裏通過另外一個Request配置參數
    Builder(Request request) {
      this.url = request.url;
      this.method = request.method;
      //...
    }
    
    //...
    
    //配置完參數後,通過Builder的參數創建一個Request
    public Request build() {
        if (url == null) throw new IllegalStateException("url == null");
        return new Request(this);
    }
}

3、創建用於發起網絡請求的Call

Call是一個接口,它的具體實現類是RealCall,Call中定義了一些enqueue(Callback)、execute()等關鍵方法,如下:
調用**client.newCall(request)**其實返回的是RealCall對象,而RealCall封裝了請求的調用邏輯。

public interface Call extends Cloneable {
    //返回當前請求
    Request request();
    //同步請求方法,此方法會阻塞當前線程直到請求結果放回
    Response execute() throws IOException;
    //異步請求方法,此方法會將請求添加到隊列中,然後等待請求返回
    void enqueue(Callback responseCallback);
    //取消請求
    void cancel();
	//判斷請求是否在執行
    boolean isExecuted();
    //判斷請求是否取消
    boolean isCanceled();
    //返回請求的超時時間
    Timeout timeout();
    //克隆一個新的請求
    Call clone();
    interface Factory {
        Call newCall(Request request);
    }
}


public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
    //...
    @Override 
    public Call newCall(Request request) {
        //調用了RealCall的newRealCall()
        return RealCall.newRealCall(this, request, false /* for web socket */);
    }
}

final class RealCall implements Call {
    //...
    static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) 	{
        //返回RealCall對象
        RealCall call = new RealCall(client, originalRequest, forWebSocket);
        call.transmitter = new Transmitter(client, call);
        return call;
    }
}



同步請求 - RealCall :: execute()
//RealCall.java
@Override
public Response execute() throws IOException {
    //...
    try {
        //1、調用Dispatcher的executed(RealCall)方法
        client.dispatcher().executed(this);
        //2、調用getResponseWithInterceptorChain()方法
        return getResponseWithInterceptorChain();
    } finally {
        //3、同步請求任務執行完畢,調用Dispatcher的finished(RealCall)方法
        client.dispatcher().finished(this);
    }
}
client就是我們上面所講的OkHttpClient的實例,它在創建RealCall時作爲構造參數傳了進去,而OkHttpClient的dispatcher()方法返回的是Dispatcher實例,
它在OkHttpClient構造時被創建。
Dispatcher是一個任務調度器
它負責進行請求任務的調度,它的內部維護着3個任務隊列(readyAsyncCalls、runningAsyncCalls、runningSyncCalls)1個線程池(executorService),Dispatcher主要內容如下:
public final class Dispatcher {
    private int maxRequests = 64;//最大請求數64個
    private int maxRequestsPerHost = 5;//每個主機最大請求數5個
    private @Nullable Runnable idleCallback;//idle任務回調,類似於Android的idlehandler, 可以在Dispatcher沒有任務調度(空閒時)時執行idleCallback中的任務

    //線程池,執行runningAsyncCalls隊列裏面的請求任務
    private @Nullable ExecutorService executorService;

    //等待執行的異步請求任務隊列
    private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

    //正在執行的異步請求任務隊列
    private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

    //正在執行的同步請求任務隊列
    private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

    synchronized void executed(RealCall call) {
        //...
    }

    void enqueue(AsyncCall call) {
        //...
    }  
    
    void finished(RealCall call) {
        //...
    }

    void finished(AsyncCall call) {
        //...
    }
    
    private boolean promoteAndExecute() {
        //...
    }

  //...  
}
  • Dispatcher提供了
    executed(RealCall)和enqueue(AsyncCall)方法來進行同步和異步請求任務的入隊,
  • 還提供了finished(RealCall)和finished(AsyncCalll)方法來進行同步和異步請求任務的出隊
  //Dispatcher
  /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

可以看到okhttp把ReadCall當作同步請求任務的代表,把AsyncCall當作異步請求任務的代表,

  1. RealCall前面已經講過了,而AsyncCal是RealCall的一個內部類,它本質上就是一個Runnable,
  2. Dispatcher的線程池執行任務主要執行的是 runningAsyncCalls 隊列裏面的異步請求任務,
    也就是AsyncCall異步任務
  3. 而Dispatcher的promoteAndExecute()方法就是用來進行異步任務的調度,它的邏輯主要是按順序把readyAsyncCalls隊列中準備執行的異步任務轉移到runningAsyncCalls後,
    再由線程池執行,對於同步任務 Dispatcher只是暫時保存在runningSyncCalls隊列中,並不會由線程池執行。

同步執行:

1. Dispatcher :: executed(RealCall)

看RealCall的execute()方法,
它首先調用了Dispatcher的executed(RealCall)方法
,Dispatcher的executed(RealCall)方法實現如下:

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

可以看到沒有做什麼處理,只是簡單的把同步請求任務放入runningSyncCalls隊列。

2. RealCall :: getResponseWithInterceptorChain()

看RealCall的execute(),調用getResponseWithInterceptorChain()方法,這裏纔是同步請求處理的地方,我們點進去,如下:


//RealCall.java 
Response getResponseWithInterceptorChain() throws IOException {
    //攔截器的添加
    List<Interceptor> interceptors = new ArrayList<>();
    //添加用戶自定義攔截器
    interceptors.addAll(client.interceptors());
    //添加默認攔截器
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    //添加的最後一個攔截器是CallServerInterceptor
    interceptors.add(new CallServerInterceptor(forWebSocket));

    //創建一個RealInterceptorChain,傳入了interceptors和Request
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    try {
      //調用RealInterceptorChain的proceed(Request)方法處理請求
      Response response = chain.proceed(originalRequest);
      //...
      return response;
    } catch (IOException e) {
     //...
    } finally {
     //...
    }
  }

3. Dispatcher :: finished(RealCall)

我們繼續看RealCall的execute()方法,調用Dispatcher的finished(AsyncCall)方法,如下:

//Dispatcher.java
void finished(RealCall call) {
    //傳進了runningSyncCalls隊列
    finished(runningSyncCalls, call);
}

private <T> void finished(Deque<T> calls, T call) {
    Runnable idleCallback;
    synchronized (this) {
        //嘗試移除隊列中的同步請求任務
        if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
        idleCallback = this.idleCallback;
    }

    //緊接着調用promoteAndExecute()方法進行異步任務的調度,如果沒有異步任務要進行,promoteAndExecute()返回false
    boolean isRunning = promoteAndExecute();

    //isRunning等於false且設置了idleCallback,會執行一遍idle任務
    if (!isRunning && idleCallback != null) {
        idleCallback.run();
    }
}
  • finished()方法中首先嚐試從runningSyncCalls隊列把剛纔通過 executed()入隊的同步任務RealCall移除
  • 如果移除失敗,就拋出異常
  • 如果移除成功,就緊接着調用promoteAndExecute()方法進行異步任務的調度並嘗試執行一遍idle任務,promoteAndExecute()方法在異步請求中再詳細介紹。

小結

至此okhttp的同步請求過程分析完畢,這裏總結一下:

  1. 當我們調用call.execute()時,就會發起一個同步請求,
  2. 而call的實現類是RealCall,所以實際執行的是realCall.execute(),
  3. realCall.execute()中執行Dispatcher的executed(RealCall)把這個同步請求任務保存進runningSyncCalls隊列中,(沒有做事情,只是入隊出隊。)
  4. 然後RealCall執行getResponseWithInterceptorChain()處理同步請求,
  5. 請求經過層層攔截器後到達最後一個攔截器CallServerInterceptor,在這個攔截器中通過Exchange把請求發送到服務器,
  6. 然後同樣的通過Exchange獲得服務器的響應,根據響應構造Response,然後返回,
  7. 最後RealCall執行Dispatcher的finished(RealCall)把之前暫時保存的同步請求任務從runningSyncCalls隊列中移除。

下面是同步請求過程的調用鏈:

異步請求 - RealCall.enqueue(Callback)

//RealCall.java
@Override
public void enqueue(Callback responseCallback) {
    //...
    //1、調用Dispatcher的enqueue(AsyncCall)方法
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

它比同步請求只是多了一個Callback,

在Callback的 onResponse(Call, Response)回調中我們可以拿到網絡響應返回的Response,
RealCall的enqueue(Callback)方法中首先把Callback用AsyncCall包裝起來,
然後調用調用Dispatcher的enqueue(AsyncCall)方法。

1. Dispatcher :: enqueue(AsyncCall)

我們看Dispatcher的enqueue(AsyncCall)方法,如下:

//Dispatcher.java
void enqueue(AsyncCall call) {
    synchronized (this) {
       readyAsyncCalls.add(call);
       //...
    }
    promoteAndExecute();
}

2. Dispatcher :: promoteAndExecute()

//Dispatcher.java
private boolean promoteAndExecute() {
    //準備一個正在執行任務列表executableCalls
    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
        //1、這個for循環主要把readyAsyncCalls中等待執行的異步任務轉移到runningAsyncCalls隊列和executableCalls列表中去
        for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {

            //取出readyAsyncCalls中等待執行的異步任務
            AsyncCall asyncCall = i.next();

            //判斷條件:1、正在運行的異步請求任務不能大於maxRequests;2、等待執行的異步任務的主機請求數不能大於maxRequestsPerHost
            if (runningAsyncCalls.size() >= maxRequests) break; 
            if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue;
            //滿足條件,進入下面邏輯

            //把這個等待執行的異步任務從readyAsyncCalls中移除
            i.remove();
            asyncCall.callsPerHost().incrementAndGet();
            
            //把這個等待執行的異步任務添加進executableCalls列表
            executableCalls.add(asyncCall);
            //把這個等待執行的異步任務添加進runningAsyncCalls隊列
            runningAsyncCalls.add(asyncCall);
        }
        
        //runningCallsCount()裏面的邏輯: return runningAsyncCalls.size() + runningSyncCalls.size();
        isRunning = runningCallsCount() > 0;
    }
    //2、這個for循環主要是執行executableCalls列表中的異步任務
    for (int i = 0, size = executableCalls.size(); i < size; i++) {
        AsyncCall asyncCall = executableCalls.get(i);
        //傳進executorService,調用AsyncCall的executeOn()方法,由線程池執行這個異步任務
        asyncCall.executeOn(executorService());
    }

    return isRunning;
}


promoteAndExecute()方法中主要是2個for循環,

  • 第 1 個for循環是把符合條件的異步請求任務從readyAsyncCalls轉移(提升)到runningAsyncCalls隊列和添加到executableCalls列表中去,
  • 第 2 個for循環就是遍歷executableCalls列表,從executableCalls列表中獲取AsyncCall對象,並且調用它的executeOn()方法,
  • executeOn()方法傳進了一個Dispatcher的executorService,所以我們看AsyncCall的executeOn()方法,裏面是真正執行異步請求任務的地方。

3. 1、AsyncCall :: executeOn(ExecutorService)

AsyncCall的executeOn()方法如下:

//AsyncCall.java
void executeOn(ExecutorService executorService) {
    boolean success = false;
    try {
        //傳進this,執行AsyncCall異步任務,AsyncCall本質是Runnable
        executorService.execute(this);
        success = true;
    } catch (RejectedExecutionException e) {
       //...
    } finally {
        if (!success) {
            //異步任務執行失敗,調用Dispatcher的finished(AsyncCall)方法
            client.dispatcher().finished(this); 
        }
    }

主要邏輯就是調用
executorService.execute(this)執行當前的 AsyncCall 異步任務,
AsyncCall實現了NamedRunnable,本質是Runnable,如下:

final class AsyncCall extends NamedRunnable {
    //...
}

public abstract class NamedRunnable implements Runnable {
	//...	
  @Override
    public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      //run方法中執行execute()方法
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}

AsyncCall實現了NamedRunnable,所以AsyncCall重寫了execute()實現了執行邏輯,所以我們直接看AsyncCal的execute()方法。

4. 2、AsyncCall :: execute()

AsyncCal的execute()方法如下:
//AsyncCall.java
@Override 
protected void execute() {
    //...
    try {
        //調用RealCall的getResponseWithInterceptorChain()方法處理請求
        Response response = getResponseWithInterceptorChain();
        signalledCallback = true;
        //請求處理完畢,返回響應,回調Callback的onResponse()方法
        responseCallback.onResponse(RealCall.this, response);
    } catch (IOException e) {
        //...
    } finally {
        //異步請求任務執行完畢,調用Dispatcher的finished(AsyncCall)方法
        client.dispatcher().finished(this);
    }
}

AsyncCal的execute()方法的邏輯和前面介紹的同步請求過程殊途同歸,

  1. 首先調用RealCall的getResponseWithInterceptorChain()方法處理請求
  2. 請求處理完畢後,返回響應Response,
  3. 這時回調我們調用Call.enqueue(Callback)時傳進來的Callback的onResponse()方法,
  4. 最後在finally語句中調用Dispatcher的finished(AsyncCall)方法來把異步請求任務從runningAsyncCalls隊列中移除出去,
  5. 這個移除邏輯和上面同步請求任務的移除邏輯一樣,只是這次是從runningAsyncCalls移除而不是runningSyncCalls.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章