1. 程式人生 > >okhttp3 簡單原始碼解析

okhttp3 簡單原始碼解析

前言

對OKhttp的原始碼看了有一段時間了,感覺有點體會,準備寫點東西,深刻深刻,不然過段時間就忘了O(∩_∩)O哈哈~
網上已經有了很多對OKhttp3的原始碼分析文章,說的也都比較好,但是覺得還是看別人寫的再好終究是別人的東西,自己寫的過程中會對OKhttp的理解更加深刻,而且1000個人的眼裡有一千個哈姆雷特,每個人對OKhttp的理解,認識,還是會有不同的。
對OKhttp的原始碼分析,一篇文章也是說不完的,能說完的話,那也太省略,長篇大論的文章,也很難讓人看下去,所以,準備寫一個關於OKhttp原始碼的分析的系列文章,本篇主要寫的是OKhttp的工作流程,並對流程中的要點進行簡要概述,後續的文章中在精要分析。

概述

基本用法

先看基本用法,通過他的用法,來分析他的原理。
OKhttp請求分為同步請求和非同步請求,程式碼如下:
同步請求:

OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  Response response = client.newCall(request).execute();
  return response
.body().string(); }

非同步請求:

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
                .url(url)
                .build();
client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

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

同步請求比較簡單,所以我們來分析,非同步請求,非同步請求的原理明白了,同步的自然而然就知道了。

流程原始碼剖析

從程式碼中我們不難看出來,OKhttpClient,Request,Response這些都是比較重要的類。我們看OKhttpClient的構造方法 new OKhttpClient();

public OkHttpClient() {
    this(new Builder());
  }

他的無參構造方法中呼叫了有Builder引數的構造方法,自己new 了一個Builder物件穿了進去。我們接著看new Builder()方法。

public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      ...
      ...
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }

發現該構造方法中初始化了一些物件,賦值了變數。

OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    this.protocols = builder.protocols;
    ...
    ...
    this.connectTimeout = builder.connectTimeout;
    this.readTimeout = builder.readTimeout;
    this.writeTimeout = builder.writeTimeout;
    this.pingInterval = builder.pingInterval;

    if (interceptors.contains(null)) {
      throw new IllegalStateException("Null interceptor: " + interceptors);
    }
    if (networkInterceptors.contains(null)) {
      throw new IllegalStateException("Null network interceptor: " + networkInterceptors);
    }
  }

上面程式碼我們看到,把Builder的變數的值賦給了OKhttpClient,我省略的部分程式碼,不然程式碼量會比較多。
這種Builder建造者模式的設計在OKhttp專案中有很多,比如Request,Response,CacheControl等類,都採用了Builder模式來設計。比較靈活方便。
然後我們看Request.Builder.url(url)方法。

public Builder url(String url) {
      if (url == null) throw new NullPointerException("url == null");

      // Silently replace web socket URLs with HTTP URLs.
      if (url.regionMatches(true, 0, "ws:", 0, 3)) {
        url = "http:" + url.substring(3);
      } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
        url = "https:" + url.substring(4);
      }

      HttpUrl parsed = HttpUrl.parse(url);
      if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
      return url(parsed);
    }

這個方法的作用,是對url進行的判斷,轉化。如果傳進來的url字串是一個無效的http,https URL,會出現IllegalArgumentException,為了避免這種情況,就出現了這個方法,通過呼叫HttpUrl.parse()方法來進行轉化,如果是無效的http,https URL,會返回null,而不出現IllegalArgumentException。
通過上面的方法,把要請求的url設定進去之後,接下來就呼叫了client.newCall(request).enqueue(Callback)請求程式碼,我們看一下newCall(request)的原始碼:

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

我們發現他是建立了一個RealCall物件,RealCall是實現Call介面,enqueue()方法是Call介面的方法,所以,執行enqueue()方法的是RealCall物件,所以我們來看RealCall物件裡面的enqueue()方法。
okhttp裡面的Call概念是一個將要被執行網路請求,可以被取消,只可以被執行一次。

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

這裡我們看主要的程式碼client.dispatcher().enqueue(new AsyncCall(responseCallback)); 這裡我們發現,繼續呼叫了Dispatcher的enqueue()方法,此刻OKhttp裡面很重要的一個類出現在我們面前了,那就是Dispatcher(排程分配器)。其實之前在OKhttpClient物件建立的時候,就有dispatcher的身影了,不過這次是比較明顯的看到 。。。

Dispatcher (排程分配器)

Dispatcher是一個主要在非同步請求時會用到的一個策略,我看有的文章說的比較絕對,說只有在非同步請求才會用到,其實同步也有用到,只不過沒非同步所佔的份額大罷了。
Dispatcher內部維護了一個執行緒池,用來對Call網路請求,進行處理。每一個dispatcher可以最大併發64個請求,每一臺主機可以最大併發5個。我們從下面程式碼可以看到:

public final class Dispatcher {
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private @Nullable Runnable idleCallback;

  /** Executes calls. Created lazily. */
  private @Nullable ExecutorService executorService;

  /** 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<>();

  public Dispatcher(ExecutorService executorService) {
    this.executorService = executorService;
  }

  public Dispatcher() {
  }

  public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

上面程式碼中還有一些通過名字就知道的變數,就不介紹了。我們主要看一下執行緒池的創鍵,這邊在建立執行緒池的時候,沒有用常用的四大執行緒池,他建立了一個核心執行緒數為0,最大執行緒數為Integer.MAX_VALUE,執行緒存活時間為1分鐘,容器為SynchronizeQueue的這樣一個執行緒池。
關於SynchronizeQueue的介紹可以看這裡SynchronizeQueue內容
他這個執行緒池很像四大執行緒池中的newCacheThreadPool。適合處理請求頻繁,高併發的任務。
這裡的執行緒池,預設是這樣的,如果有需求可以自己設定的。
Dispatcher如他名字的含義一樣,排程。進他的原始碼可以看到,它裡面的方法就是,執行請求,取消請求,結束請求等之類的呼叫,他的類裡面的程式碼不多,大家可以自己進去看看,很容易理解。這裡提一嘴,建議在看這篇文章的同學,採取邊看文章,邊看原始碼的方式來理解掌握。

回過頭來繼續看這句程式碼:

client.dispatcher().enqueue(new AsyncCall(responseCallback));

我們知道他就是呼叫Dispatcher裡面的enqueue方法,我們來看,Dispatcher裡面的enqueue方法。

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

runningAsyncCalls是一個儲存正在進行非同步請求的容器,這邊對它進行了判斷,如果runningAsyncCalls的size小於最大併發量64,並且每臺主機的正在請求量小於5(maxRequestsPerHost),runningAsyncCalls就把這個非同步請求新增進去,接著用通過呼叫執行緒池中的執行緒去執行這個AsyncCall,否者就用readyAsyncCalls容器去儲存該請求。這段邏輯很好理解。我們現在看一下AsyncCall。看看這個AsyncCall是什麼。

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

這裡我們發現了之前傳過來的Callback回撥,AsyncCall是RealCall的內部類。AsyncCall 繼承NamedRunnable ,咦。。有Runnable,難到。。。我們接著看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();
}

果然~~,NameRunnable是實現了Runnable介面。是一個抽象類,在run方法中執行了execute()方法,execute方法是一個抽象方法。他的實現在NameRunnable子類中,AsyncCall是NameRunnable的子類。所以,我們就定位到了AsyncCall類中的關鍵方法execute()。

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

看到這裡,我們要知道,之前Dispatcher裡面的執行緒池執行的任務就是上面的execute()方法。然後根據上面的原始碼我們發現,在這裡獲取到了Response,網路響應結果。並且對根據條件,處理了對Callback回撥的響應。

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

            }

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

            }
        });

之前我們在使用OKhttp進行一步請求的時候,傳進去的Callback,結果回撥,就是AsyncCall 裡面execute()方法裡面的Callback。
獲取到Response的這行程式碼

Response response = getResponseWithInterceptorChain();

我們待會再看。我們接著看,發現finally語句塊中執行的下面的語句:

client.dispatcher().finished(this);

呼叫了Dispatcher的finished方法。

/** Used by {@code AsyncCall#run} to signal completion. */
  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();
    }
  }

這裡的promoteCalls引數,在非同步請求的情況下,Boolean值為true,同步的話,值為false。我們看到finished方法裡面有一個同步程式碼塊,首先他移除了當前正在執行的call(請求都完成了,到這該結束),邏輯沒毛病。然後獲取正在執行的請求數量,如果數量==0,idleCallback不為零,就執行idleCallback的run方法。idleCallback預設為null的,使用者有需要得設定了才有。咦,我們剛剛是不是錯過了什麼邏輯。對了,這行程式碼還沒看呢。沒事咱現在看。
if (promoteCalls) promoteCalls();
我們分析的是非同步請求,所以promoteCalls是true。我們看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.
    }
  }

邏輯很簡單,我就不細說了,就是把readyAsyncCalls準備執行裡的請求放到runningAsyncCalls正在執行的容器裡,並通過executorService().execute(call);來執行。

接下來我們來看之前被我們放在一邊的獲取Response的方法,程式碼如下:

Response response = getResponseWithInterceptorChain();
Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());//注意1
    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());//注意2
    }
    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(攔截器)

interceptor是什麼?他是用來對發出去的網路請求和對應響應的responses 進行(Observes)觀察,(modifies)修改和potentially short-circuits(可能的短路?掉線?不好翻譯,理解吧),也就是對Request和Response進行修改,處理的一個藉口。通常,interceptor(攔截器)會對Request和Response的Header進行add,remove,transform等操作。

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;
    ...
    ...
    省略...

我們現在來看上面的getResponseWithInterceptorChain()方法。他首先建立了一個ArrayList集合把一些xxxInterceptor新增進去,然後建立了RealInterceptorChain,並執行chain.proceed(originalRequest);方法。這裡先說一個結論就是interceptors容器裡面的各式各樣的interceptor攔截器會,按新增的順序,逐個執行intercept()方法。這個結論後續會被證明。然後我們上面程式碼,有兩個注意點,那兩個地方的interceptor是開發者,根據需要選擇是否設定的。設定後執行順序就是add的順序。
接著我們看RealInterceptorChain.proceed(originalRequest);方法。

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);

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    if (response.body() == null) {
      throw new IllegalStateException(
          "interceptor " + interceptor + " returned a response with no body");
    }

    return response;
  }

這裡,我刪除了一些非關鍵程式碼,方便我們瀏覽。剛開始,index和calls的值都是0,我們看到他建立了一個RealInterceptorChain型別的next物件,他的構造方法這裡注意一下,他的構造方法傳進去的引數,有一個index+1,也就是說這個RealInterceptorChain的index值+1了。變成了1 注意這裡是新建立的next物件裡面的index值加一了,並不是說現在在所的物件裡面index加一,Interceptor interceptor = interceptors.get(index);在執行該語句時,index還是0,這裡如果開發者通過呼叫OKhttpClient的addInterceptor(Interceptor interceptor)方法,設定了攔截器,則會先執行使用者通過該方法設定的攔截器,否則就是執行RetryAndFollowUpInterceptor 攔截器。這個攔截器看名字我們很容易知道,他是失敗重試和重定向的時候發揮作用的,這裡我們先不細說了。本篇文章的主要目的是捋清流程。我們接著看,這裡我們沒有add 自定義的攔截器,所以就執行了RetryAndFollowUpInterceptor 攔截器的interceptor.intercept(next);方法,注意這裡把next物件,穿進去了,還有要記得我們之前強調的,next物件裡面的index的值加一了已經,變成了1了。
然後我們看RetryAndFollowUpInterceptor 攔截器的interceptor.intercept(next);方法。

@Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;//注意1
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();

    streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
        call, eventListener, callStackTrace);

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;
      try {
        response = realChain.proceed(request, streamAllocation, null, null);//注意2
        releaseConnection = false;

上面的程式碼,別的我們先不看,我們主要看我上面標註的注意1,和注意2處的程式碼。看注意1,這裡傳進來的chain就是next物件,然後注意2,那邊realChain.proceed()方法,(⊙o⊙)哦~有點熟悉哦,我們進去看一下。

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);

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    if (response.body() == null) {
      throw new IllegalStateException(
          "interceptor " + interceptor + " returned a response with no body");
    }

    return response;
  }

發現又走了回來,注意,此時index的值不再是0了,此時變成了1,接著就是上面一樣的邏輯,建立新的next物件,index加1,從interceptors容器中取出下一個interceptor攔截器,執行intercept(next)方法。正是這種遞迴式的呼叫,來吧interceptors集合裡面的所有攔截器都跑一邊,最後獲取到Response物件。

總結

不自覺中說了這麼多,之後的文章會對Interceptor的各個實現類,CacheControl等重要類進行剖析。最後上一張總結圖:

這裡寫圖片描述