OkHttp 源碼分析(一)—— 請求流程

這篇文章主要梳理一下 OkHttp 的請求流程,對 OkHttp 的實現原理有個總體的把握,再深刻細節的實現會更加容易。html

建議將 OkHttp 的源碼下載下來,使用 IDEA 編輯器能夠直接打開閱讀。我這邊也將最新版的源碼下載下來,進行了註釋,有須要的能夠直接從 這裏 下載查看。java

基本使用

咱們先看一下 OkHttp 的基本使用。android

// 一、建立 Request
Request request = new Request.Builder()
    .get()
    .url("xxx")
    .build(); 

// 二、建立 OKHttpClient
OkHttpClient client = new OkHttpClient();

// 三、建立 Call
Call call = client.newCall(request);

try {
    // 四、同步請求
    Response response = call.execute();
} catch (IOException e) {
    e.printStackTrace();
}

// 五、異步請求
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {

    }

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

    }
});
複製代碼

上面的代碼中,首先構建一個請求 Request 和一個客戶端 OkHttpClient,而後 OkHttpClient 對象根據 request 調用 newCall 方法建立 Call 對象,再調用 execute 或者 enqueue 方法進行同步或者異步請求。git

接下來咱們看一看關鍵類和關鍵流程的具體實現。github

Request

Request 類封裝了一次請求須要傳遞給服務端的參數:請求 method 如 GET/POST 等、一些 header、RequestBody 等等。web

Request 類未對外提供 public 的構造函數,因此構建一個 Request 實例須要使用構造者模式構建。緩存

Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tags = Util.immutableMap(builder.tags);
}
複製代碼

OkHttpClient

OkHttpClient 支持兩種構造方式。安全

一種是默認的構造方式:bash

OkHttpClient client = new OkHttpClient();
複製代碼

看一下構造函數:服務器

public OkHttpClient() {
     this(new Builder());
 }
複製代碼

這裏 OkHttpClient 內部默認配置了一些參數。

OkHttpClient(Builder builder) {...}
複製代碼

另外一種是經過 Builder 配置參數,最後經過 build 方法構建一個 OkHttpClient 對象。

OkHttpClient client = new OkHttpClient.Builder().build();

public OkHttpClient build() {
    return new OkHttpClient(this); // 這裏的 this 是 Builder 實例
}
複製代碼

咱們看一下 OkHttpClient 可配置哪些參數:

final Dispatcher dispatcher;    // 調度器
final @Nullable
Proxy proxy; // 代理
final List<Protocol> protocols;  // 協議
final List<ConnectionSpec> connectionSpecs;  // 傳輸層版本和鏈接協議
final List<Interceptor> interceptors;  // 攔截器
final List<Interceptor> networkInterceptors;  // 網絡攔截器
final EventListener.Factory eventListenerFactory;
final ProxySelector proxySelector; // 代理選擇器
final CookieJar cookieJar;  // cookie
final @Nullable
Cache cache;  // 緩存
final @Nullable
InternalCache internalCache;  // 內部緩存
final SocketFactory socketFactory;  // socket 工廠
final SSLSocketFactory sslSocketFactory;  // 安全套接層 socket 工廠,用於 https
final CertificateChainCleaner certificateChainCleaner; // 驗證確認響應證書 適用 HTTPS 請求鏈接的主機名
final HostnameVerifier hostnameVerifier; // 主機名字驗證
final CertificatePinner certificatePinner; // 證書鏈
final Authenticator proxyAuthenticator; // 代理身份驗證
final Authenticator authenticator; // 本地身份驗證
final ConnectionPool connectionPool;  // 鏈接池
final Dns dns;  // 域名
final boolean followSslRedirects;  // 安全套接層重定向
final boolean followRedirects;  // 本地重定向
final boolean retryOnConnectionFailure;  // 重試鏈接失敗
final int callTimeout;
final int connectTimeout;
final int readTimeout;
final int writeTimeout;
final int pingInterval;
複製代碼

Call

Call 是一個接口,是請求的抽象描述,具體實現類是 RealCall,經過Call.Factory 建立。

public interface Call extends Cloneable {
  // 返回當前請求
  Request request();

  // 同步請求方法
  Response execute() throws IOException;

  // 異步請求方法
  void enqueue(Callback responseCallback);

  // 取消請求
  void cancel();

  // 請求是否在執行(當execute()或者enqueue(Callback responseCallback)執行後該方法返回true)
  boolean isExecuted();

  // 請求是否被取消
  boolean isCanceled();
  
  Timeout timeout();

  // 建立一個新的如出一轍的請求
  Call clone();

  interface Factory {
    Call newCall(Request request);
  }
}
複製代碼

OkHttpClient 實現了 Call.Factory,負責根據 Request 建立新的 Call:

Call call = client.newCall(request);
複製代碼

看一下 newCall 方法。

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

這裏咱們發現實際上調用了 RealCall 的靜態方法 newRealCall, 不難猜想 這個方法就是建立 Call 對象。

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;
}
複製代碼

同步請求

從上面的分析咱們知道,同步請求調用的實際是 RealCall 的 execute 方法。

@Override public Response execute() throws IOException {
    synchronized (this) {
      // 每一個 call 只能執行一次
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    timeout.enter();
    eventListener.callStart(this);
    try {
      // 請求開始, 將本身加入到runningSyncCalls隊列中
      client.dispatcher().executed(this);
      // 經過一系列攔截器請求處理和響應處理獲得最終的返回結果
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      e = timeoutExit(e);
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      // 請求完成, 將其從runningSyncCalls隊列中移除
      client.dispatcher().finished(this);
    }
  }
複製代碼

這裏主要作了這幾件事:

  • 檢測這個 call 是否已經執行了,保證每一個 call 只能執行一次。
  • 通知 dispatcher 已經進入執行狀態,將 call 加入到 runningSyncCalls 隊列中。
  • 調用 getResponseWithInterceptorChain() 函數獲取 HTTP 返回結果。
  • 最後還要通知 dispatcher 本身已經執行完畢,將 call 從 runningSyncCalls 隊列中移除。

這裏涉及到了 Dispatcher 這個類,咱們在異步請求這一節中再介紹。

真正發出網絡請求以及解析返回結果的是在 getResponseWithInterceptorChain 方法中進行的。

Response getResponseWithInterceptorChain() throws IOException {
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }

    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);
複製代碼

getResponseWithInterceptorChain 方法的代碼量並很少,可是卻完成了全部的請求處理過程。

這裏先是建立了一個 Interceptor 的集合,而後將各種 interceptor 所有加入到集合中,包含如下 interceptor:

  • interceptors:配置 OkHttpClient 時設置的 inteceptors
  • RetryAndFollowUpInterceptor:負責失敗重試以及重定向
  • BridgeInterceptor:負責把用戶構造的請求轉換爲發送到服務器的請求、把服務器返回的響應轉換爲用戶友好的響應
  • CacheInterceptor:負責讀取緩存直接返回、更新緩存
  • ConnectInterceptor:負責和服務器創建鏈接
  • networkInterceptors:配置 OkHttpClient 時設置的 networkInterceptors
  • CallServerInterceptor:負責向服務器發送請求數據、從服務器讀取響應數據

添加完攔截器後,建立了一個 RealInterceptorChain 對象,將集合 interceptors 和 index(數值0)傳入。接着調用其 proceed 方法進行請求的處理,咱們來看 proceed方法。

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();
    ...
    // 建立下一個RealInterceptorChain,將index+1(下一個攔截器索引)傳入
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    // 獲取當前的攔截器
    Interceptor interceptor = interceptors.get(index);
    // 經過Interceptor的intercept方法進行處理
    Response response = interceptor.intercept(next);
    ...
    return response;
  }
複製代碼

咱們來看一些關鍵代碼:

RealInterceptorChain 的 proceed 方法先建立 RealInterceptorChain 的對象,將集合 interceptors 和 index + 1 傳入。從前面的分析知道,初始 index 爲 0。

而後獲取當前 index 位置上的 Interceptor,將建立的 RealInterceptorChain 對象 next 傳入到當前攔截器的 intercept 方法中,intercept 方法內部會調用 next 的 proceed 方法,一直遞歸下去,最終完成一次網絡請求。

因此每一個 Interceptor 主要作兩件事情:

  • 攔截上一層攔截器封裝好的 Request,而後自身對這個 Request 進行處理,處理後向下傳遞。
  • 接收下一層攔截器傳遞回來的 Response,而後自身對 Response 進行處理,返回給上一層。

異步請求

異步請求調用的是 RealCall 的 enqueue 方法。

public void enqueue(Callback responseCallback) {
     synchronized(this) {
         if (this.executed) {
             throw new IllegalStateException("Already Executed");
         }

         this.executed = true;
     }

     this.captureCallStackTrace();
     this.eventListener.callStart(this);
     this.client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback));
 }
複製代碼

與同步請求同樣,異步請求也涉及了一個重要的參與者 Dispatcher,它的做用是:控制每個 Call 的執行順序和生命週期。它內部維護了三個隊列:

  • readyAsyncCalls:等待的異步請求隊列
  • runningAsyncCalls:正在運行的異步請求隊列
  • runningSyncCalls:正在運行的同步請求隊列

對於同步請求,因爲它是即時運行的, Dispatcher 只須要運行前請求前存儲到 runningSyncCalls,請求結束後從 runningSyncCalls 中移除便可。

對於異步請求,Dispatcher 是經過啓動 ExcuteService 執行,線程池的最大併發量 64,異步請求先放置在 readyAsyncCalls,能夠執行時放到 runningAsyncCalls 中,執行結束從runningAsyncCalls 中移除。

咱們看一下具體實現細節,下面是 Dispatcher 的 enqueue 方法,先將 AsyncCall 添加到 readyAsyncCalls。

void enqueue(AsyncCall call) {
  // 將AsyncCall加入到準備異步調用的隊列中
  synchronized (this) {
    readyAsyncCalls.add(call);
  }
  promoteAndExecute();
}
複製代碼

再看 promoteAndExecute 方法:

private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
        for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall asyncCall = i.next();

            if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
            if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.

            i.remove();
            executableCalls.add(asyncCall);
            runningAsyncCalls.add(asyncCall);
        }
        isRunning = runningCallsCount() > 0;
    }

    for (int i = 0, size = executableCalls.size(); i < size; i++) {
        AsyncCall asyncCall = executableCalls.get(i);
        asyncCall.executeOn(executorService());
    }

    return isRunning;
}
複製代碼

這裏主要的工做有:

  • 從準備異步請求的隊列中取出能夠執行的請求(正在運行的異步請求不得超過64,同一個host下的異步請求不得超過5個),加入到 executableCalls 列表中。
  • 循環 executableCalls 取出請求 AsyncCall 對象,調用其 executeOn 方法。
void executeOn(ExecutorService executorService) {
    assert (!Thread.holdsLock(client.dispatcher()));
    boolean success = false;
    try {
        executorService.execute(this);
        success = true;
    } catch (RejectedExecutionException e) {
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        eventListener.callFailed(RealCall.this, ioException);
        responseCallback.onFailure(RealCall.this, ioException);
    } finally {
        if (!success) {
            client.dispatcher().finished(this); // This call is no longer running!
        }
    }
}
複製代碼

能夠看到 executeOn 方法的參數傳遞的是 ExecutorService 線程池對象,方法中調用了線程池的 execute方法,因此 AsyncCall 應該是實現了 Runnable 接口,咱們看看它的 run 方法是怎樣的。

AsyncCall 繼承自 NamedRunnable 抽象類。

public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}
複製代碼

因此當線程池執行 execute 方法會走到 NamedRunnable 的 run 方法,run 方法中又調用了 抽象方法 execute,咱們直接看 AsyncCall 的 execute 方法。

@Override
protected void execute() {
    boolean signalledCallback = false;
    timeout.enter();
    try {
        // 請求網絡獲取結果
        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) {
        e = timeoutExit(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 {
        // 調度完成,移出隊列
        client.dispatcher().finished(this);
    }
}
}
複製代碼

這裏咱們又看到了熟悉的 getResponseWithInterceptorChain 方法。

這樣看來,同步請求和異步請求的原理是同樣的,都是在 getResponseWithInterceptorChain() 函數中經過 Interceptor 鏈條來實現的網絡請求邏輯。

總結

以上即是 Okhttp 整個請求與響應的具體流程,OkHttp 流程圖以下。

簡述 OkHttp 的請求流程:

  • OkHttpClient 實現了 Call.Fctory,負責爲 Request 建立 Call。
  • RealCall 是 Call 的具體實現,它的異步請求是經過 Dispatcher 調度器利用 ExcutorService 實現,而最終進行網絡請求時和同步請求同樣,都是經過 getResponseWithInterceptorChain 方法實現。
  • getResponseWithInterceptorChain 方法中採用了責任鏈模式,每個攔截器各司其職,主要作兩件事。
    • 攔截上一層攔截器封裝好的 Request,而後自身對這個 Request 進行處理,處理後向下傳遞。
    • 接收下一層攔截器傳遞回來的 Response,而後自身對 Response 進行處理,返回給上一層。

參考

相關文章
相關標籤/搜索