深入OKHttp原始碼分析(一)----同步和非同步請求流程和原始碼分析

淡述昔日愛發表於2018-04-12

很久沒有寫過部落格了,一直認為自己的技術實力不夠,怕寫了技術部落格產生錯誤,誤導了大家,讓大家在錯誤的方向越走越遠,不能解決自己的問題,前段時間看了某大佬的關於寫作的分享,我決定從今天開始將自己在Android開發路上學習和解決問題的歷程記錄下來,如果部落格中有錯誤的地方,也歡迎大家指正,我們共同進步共同學習。今天從OKhttp的原始碼分析開始! ###OKhttp框架流程 基本的執行流程如下: OKhttpClient->Request->RealCall->Dispatcher->interceptors(RetryAndFollow->Bridge->Cache->Connect->CallServer)

名詞解釋: #####OkHttpClient: 按照類檔案中的說明就是傳送HTTP請求和讀取響應的例項。也是Call的生產工廠 #####Request : HTTP請求 #####RealCall :真正的Call物件。 #####Dispatcher : 任務排程器 #####interceptors : 攔截器,其中包含RetryAndFollow、Bridge、Cache、Connect、CallServer,攔截器也可以自定義

####OkHttp同步請求流程和原始碼分析

		OkHttpClient client=new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();

        Request request=new Request.Builder().url("http://www.baidu.com").build();

        Call call = client.newCall(request);

        try {
            Response response = call.execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
複製程式碼

首先我們來看下OkHttpClient例項的建立,OkHttpClient例項是通過建立者模式來建立的,我們來看下其原始碼

public Builder() {
      dispatcher = new Dispatcher();//這裡建立了OKhttp的任務排程器物件
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      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;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }
複製程式碼

通過上面的程式碼來看,這個方法裡面主要是基本的賦值操作和任務排程器物件和連線池物件的建立,我們繼續往下看build方法

public OkHttpClient build() {
      return new OkHttpClient(this);
    }
OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    this.protocols = builder.protocols;
    this.connectionSpecs = builder.connectionSpecs;
    this.interceptors = Util.immutableList(builder.interceptors);
    this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
    this.eventListenerFactory = builder.eventListenerFactory;
    this.proxySelector = builder.proxySelector;
    this.cookieJar = builder.cookieJar;
    this.cache = builder.cache;
    this.internalCache = builder.internalCache;
    this.socketFactory = builder.socketFactory;
    ···//篇幅原因,這裡就將剩下的程式碼註釋啦。
  }
複製程式碼

在build方法中直接返回了新建立的OkHttpClient物件,將Builder物件傳入OkHttpClient中,並在OkHttpClient的構造方法中進行賦值操作,此時,OkHttpClient的例項物件建立完成。

接著我們看下請求物件Request的建立過程:

Request request=new Request.Builder().url("http://www.baidu.com").build();
複製程式碼

也和OkHttpClient的建立過程一樣,也是使用了建立者模式,我們來看下Builder方法

public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
    }
複製程式碼

在這裡我們可以看到,預設情況下,請求方式為GET方式,然後建立了header物件,用來儲存HTTP的請求頭資料,url方法就是設定url地址的,我們就不跳進去看程式碼了,我們看下build方法

public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
    }
Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tag = builder.tag != null ? builder.tag : this;
  }
複製程式碼

在build方法中,進行了url的判空操作,然後將Builder物件傳入Request類的建構函式,返回新建立的Request物件 在Request的建構函式中,只是進行簡單的賦值操作。此時Request物件也已經建立完成。

接著進行第三步了,建立Call物件

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

我們點選newCall方法進去看看Call物件是具體如何建立的

/**
   * Prepares the {@code request} to be executed at some point in the future.
   */
  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }
複製程式碼

我們可以看到,裡面很簡單,其實Call物件的真正建立是在RealCall的newRealCall方法裡的,我們進去看看

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;
  }
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
  }
複製程式碼

在newRealCall方法中我們看到,RealCall的建立是通過建構函式建立的,我們點進來繼續看,裡面只是一些簡單的賦值操作,但是,請注意,最後一頁是我們在前面簡介過的重試攔截器RetryAndFollowUpInterceptor的建立。這裡我們先不詳細的介紹,後面再詳細講解。 在newRealCall方法中的最後一行是建立Call物件的事件監聽物件。 到此為止,Call物件建立成功。 我們繼續往下看,最後一步,

Response response = call.execute();
複製程式碼

呼叫call的execute()方法,得到我們想要的response物件,我們進入到execute方法中看看裡面是如何得到我們要的response物件的。

@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      client.dispatcher().finished(this);
    }
  }
複製程式碼

我們直接來看關鍵程式碼,client.dispatcher().executed(this);,我們看下dispatcher方法,

public Dispatcher dispatcher() {
    return dispatcher;
  }
複製程式碼

可以看到,這裡直接返回了之前在構造器中建立的dispatcher物件,我們接著看execute方法,

/** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }
複製程式碼

這個runningSynCalls是什麼鬼呢,我們看下定義位置

/** 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<>();
複製程式碼

可以看到,這個是一個存放還沒有執行完畢的同步任務的佇列,我們還看到了另外兩個佇列readyAsyncCalls、runningAsyncCalls,一個是待執行的非同步任務的佇列,一個是正在執行的非同步任務的佇列。這兩個例項我們在後面的非同步請求中會用到,這裡先跳過。 我們回頭看下executed方法,這裡只是將Call物件新增到同步任務的執行佇列中。 回到前面的程式碼中,我們接著往下看,

Response result = getResponseWithInterceptorChain();
複製程式碼

原來真正得到response物件是在這裡,通過getResponseWithInterceptorChain();方法得到的,我們進入到這個方法中看下是如何執行的。

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    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);
  }
複製程式碼

return語句之前的程式碼都是建立攔截器鏈的操作,攔截器鏈建立完成後,通過攔截器鏈的proceed方法得到response物件,我們繼續跟進

@Override public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
  }
複製程式碼

我們可以看到裡面又呼叫了另一個過載的proceed方法,我們跳過去看下,

  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;
    ···//省略部分程式碼
    // 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);
    Response response = interceptor.intercept(next);
    ···//為了簡化篇幅,將後面一些判斷程式碼進行省略
    return response;
  }
複製程式碼

我們可以看到中間的程式碼,這部分程式碼就是得到攔截器鏈中的下一個攔截器,然後進行該攔截器的intercept(next)方法操作,在這個方法中,會繼續呼叫攔截器鏈中的下一個攔截器的intercept(next)方法呼叫,最後執行到CallServerInterceptor攔截器,從而獲取真正的response物件,然後一層層的返回,每返回一層,在當前層的攔截器中進行包裝處理,最後返回到這裡,再進行返回,從而得到我們要的response物件。攔截器鏈中的詳細執行流程我們後面再詳細講解。

通過上面的程式碼分析,我們就已經分析完了同步請求的執行流程。下面我們繼續看下非同步請求流程的原始碼分析。 ####OkHttp非同步請求流程和原始碼分析 我們先來看下如何來執行非同步請求

OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();

        Request request = new Request.Builder().url("http://www.baidu.com").build();

        Call call = client.newCall(request);

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

            }

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

            }
        });
複製程式碼

我們可以看到,前面三行程式碼是和同步請求一致,我們重點來看call的enqueue方法。上面我們講過,call物件的真正實現是RealCall,那我們就看RealCall中enqueue方法的實現

@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));
  }
複製程式碼

最後一行,還是OkHttpClient物件獲取了任務排程器來執行enqueue方法,並將我們傳遞進來的Callback物件封裝成AsyncCall物件,我們看下Dispatcher中的enqueue方法:

synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }
複製程式碼

可以看到,首先進行判斷,如果正在執行的非同步任務數量小於指定的數量(64),就將AsyncCall物件新增到正在執行的非同步任務佇列中,並加入執行緒池中執行任務,否則就加入到等待執行的非同步任務佇列中去。如果執行緒池中執行緒開始執行了,執行緒池中的執行物件肯定是一個執行緒,那就肯定要有run方法的,那麼我們就來看下AsyncCall到底是個什麼東東。

 final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }
	······//省略部分程式碼
  }
複製程式碼

上面程式碼中我們可以看到,AsyncCall繼承了NamedRunnable,我們繼續看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();
}
複製程式碼

可以看到,原來NamedRunnable實現了Runnable介面,肯定要執行run方法,那就看下NamedRunnable的run方法,其中最重要的就是execute();這一句了,我們看下execute()方法,咦,是個抽象方法,這個方法是給其子類來實現的,那麼我們看下剛剛實現了NamedRunnable類的子類AsyncCall中的executed方法,

 @Override protected void execute() {
      boolean signalledCallback = false;
      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) {
        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);
      }
    }
複製程式碼

我們可以看到熟悉的程式碼,熟悉的味道,······,額,我們看下這一行

Response response = getResponseWithInterceptorChain();
複製程式碼

是不是似曾相識呢,對的,在前面的同步執行流程的原始碼分析中我們已經見過這個程式碼了,如果你不記得了,就往前面翻一翻,這裡就不再講解啦。通過這一句,我們得到了我們想要的response物件,那麼我們看下下面的程式碼是做什麼的 try···catch程式碼塊中是判斷和對callback方法的呼叫,我們忽略它,來看下finally中的程式碼,我們知道,finally中的程式碼是一定會執行到的,我們看下finished方法

 void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }
  private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }
複製程式碼

finished方法呼叫了過載的另一個finished方法,我們看下,在finished方法中,首先將剛剛執行完的call物件進行移除佇列,然後我們看這行程式碼

if (promoteCalls) promoteCalls();
複製程式碼

我們看下promoteCalls()方法

private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }
複製程式碼

頭兩行程式碼是判斷正在執行的任務佇列中的任務數量如果大於或等於指定的最大任務數就返回,或者正在等待的任務數為0也返回,我們來看下for迴圈中的程式碼,這裡是對等待的任務佇列進行遍歷,首先取出一個任務,判斷是否符合執行條件,如果符合,就將其從等待佇列中移除,新增到執行佇列中去,並加入執行緒池執行,最後一行,如果正在執行的任務數量大於等於最大指定任務數,就跳出for迴圈,我們可以看到,這個promoteCalls方法的作用就是判斷正在執行的任務數量,如果數量小於指定的任務數,就從等待的任務佇列中取出,新增到正在執行的任務佇列中,並加入執行緒池中執行,那麼這裡就又可以回到前面分析過的程式碼,那我們就繼續回到前面的finished方法中去,繼續往下看,

runningCallsCount = runningCallsCount();
複製程式碼

我們來看下這行程式碼

public synchronized int runningCallsCount() {
    return runningAsyncCalls.size() + runningSyncCalls.size();
  }
複製程式碼

很簡單,只是重新計算正在執行的任務數量,現在我們回過頭來繼續看finished方法中的最後一部分程式碼

  if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
複製程式碼

這裡是判斷如果正在執行的任務數量為0,並且空閒回收執行緒不為null,就執行空閒回收執行緒的run方法進行回收操作。

好了,到此為止,我們就已經分析完了同步和非同步執行流程,下一篇我們來分析okhttp的任務排程核心類Dispatcher

相關文章