天天看點

OkHttp是怎麼工作的 - 從介紹到分析OkHttp官網說了什麼使用分析

OkHttp

Retrofit使用OkHttp,Volley支援替換底層http棧為OkHttp,甚至Google的最新源碼裡,都用起了OkHttp,替換了原來用的HttpClient。

筆者雖然一直聽說OkHttp怎麼怎麼好,但始終雲裡霧裡究竟它如何優越,是以抽空瞄了瞄源碼,小小分析一下。

官網說了什麼

okhttp官網

  • HTTP/2 和 SPDY 支援允許所有到同一個host的請求共享一個socket。
  • 連接配接池減少請求延時 (如果 SPDY 不可用).
  • 透明的GZIP支援,減少下載下傳大小。
  • 緩存Response以減少網絡去做完全重複的請求。

OkHttp在網絡有問題的時候表現很好:

- 它會靜默從常見的連接配接問題中恢複。

- 如果你的服務有多個IP位址對應,OkHttp會在首次連接配接失敗的時候嘗試其他位址。

- OkHttp使用現代的TLS features (SNI, ALPN) 來初始化連接配接, 并在握手失敗的時候倒回到TLS 1.0。

OkHttp 2.0的API設計為流式builders和immutability,同時支援同步blocking call和異步帶callback的call。

你可以不用重寫網絡層代碼來試試OkHttp。okhttp-urlconnection子產品實作了大家熟悉的java.net.HttpURLConnection API,而okhttp-apache子產品實作了Apache HttpClient API.

OkHttp支援Android 2.3和以上,對Java要求至少1.7。

對了,OkHttp還用了Okio來做快速I/O和可調整大小的buffer。

使用

分别來看看Get和Post請求吧

Get請求并獲得response body:

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

Post請求,看上去和上面的差别隻在于Request的Builder多了.post(body):

public static final MediaType JSON
    = MediaType.parse("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
  RequestBody body = RequestBody.create(JSON, json);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
  Response response = client.newCall(request).execute();
  return response.body().string();
}
           

分析

這麼多module,裡面的類也不算少,怎麼看起呢,不如就以使用裡面的代碼作為切入點吧。

OkHttpClient

作為入口,OkHttpClient裡面包含了各種東西,

OkHttpClient

:

// 之前提到的協定
  private static final List<Protocol> DEFAULT_PROTOCOLS = Util.immutableList(
      Protocol.HTTP_2, Protocol.SPDY_3, Protocol.HTTP_1_1);
  // TLS那些事
  private static final List<ConnectionSpec> DEFAULT_CONNECTION_SPECS = Util.immutableList(
      ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS, ConnectionSpec.CLEARTEXT);

  // 這裡實作了各種抽象方法,初始化了Intelnal單例,提供給各處使用
  static {
    Internal.instance = new Internal() {
  }

  /** 懶加載(HTTPS那些事) */
  private static SSLSocketFactory defaultSslSocketFactory;
  // 也是之前提過的,多個IP時候的選擇政策
  private final RouteDatabase routeDatabase;
  // 異步請求執行時候的政策,用到了ExecutorService作線程池,預設建立的是core 0,max Integer.MAX_VALUE,keep alive 60秒的線程池
  private Dispatcher dispatcher;
  // 代理嘛,大家都懂得,分為DIRECT(直連無代理)、HTTP和SOCKS
  private Proxy proxy;
  private List<Protocol> protocols;
  private List<ConnectionSpec> connectionSpecs;
  // 攔截器 - 在Response正式傳回前處理資料,比如Gzip解壓
  private final List<Interceptor> interceptors = new ArrayList<>();
  // 這個網絡攔截器可就牛逼了。。。相較前面那個發生地更早,在HttpEngine的readResponse中被調用
  // 可以做諸如CheckHandshake初始化的時候加進去了一個握手的攔截器去檢查是否在黑名單;LoggingInterceptors則加了一個log的攔截器,輸出請求和response的資訊;Progress加了個包裝response的攔截器,來增加progress功能;RewriteResponseCacheControl直接重寫server的cache控制頭
  private final List<Interceptor> networkInterceptors = new ArrayList<>();
  // 代理伺服器選擇器
  private ProxySelector proxySelector;
  // Cookie處理器,可以接受get和put cookie的事件
  private CookieHandler cookieHandler;

  // 就是個接口
  /** Non-null if this client is caching; possibly by {@code cache}. */
  private InternalCache internalCache;
  // 可以自定義,内部包含了上面的InternalCache實作
  private Cache cache;

  // 都是java的東西
  private SocketFactory socketFactory;
  private SSLSocketFactory sslSocketFactory;
  private HostnameVerifier hostnameVerifier;

  // 證書驗證
  private CertificatePinner certificatePinner;
  // 回複伺服器認證要求
  private Authenticator authenticator;
  // 連接配接池,同一個host的請求可能共享同一個connection,該類還實作了connection為以後的使用保持打開的政策
  private ConnectionPool connectionPool;
  // 接口,實際使用了InetAddress.getAllByName
  private Network network;

  private boolean followSslRedirects = true;
  private boolean followRedirects = true;
  private boolean retryOnConnectionFailure = true;

  // 其實10_000就是10000,這裡是10000毫秒即10秒
  private int connectTimeout = _000;
  private int readTimeout = _000;
  private int writeTimeout = _000;
           

再看看構造函數:

public OkHttpClient() {
    routeDatabase = new RouteDatabase();
    dispatcher = new Dispatcher();
  }

  private OkHttpClient(OkHttpClient okHttpClient) {
    // 反正就是各種參數拷貝過來
  }
           

那麼當我們調用

new OkHttpClient()

的時候,其實就是new了兩個成員變量。

嗯。。。好像很輕,還是看看具體的請求執行吧

Response response = client.newCall(request).execute();
           

看來具體執行是在

Call

裡面

public class Call {
  private final OkHttpClient client;

  // Guarded by this.
  private boolean executed;
  volatile boolean canceled;

  /** The application's original request unadulterated by redirects or auth headers. */
  Request originalRequest;
  HttpEngine engine;
           

原來是

Call

持有了

HttpEngine

,Call是一個待準備執行的請求,可以被取消,由于它代表了單個的請求/回複 對,是以不能被執行兩次。

具體execute的執行:

public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    try {
      // 結果又交給了OkHttpClient的Dispatcher去執行
      client.getDispatcher().executed(this);
      Response result = getResponseWithInterceptorChain(false);
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.getDispatcher().finished(this);
    }
  }
           

嗯…我們再跳

Dispatcher

:

/** In-flight synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<Call> executedCalls = new ArrayDeque<>();

  /** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(Call call) {
    executedCalls.add(call);
  }
           

直接就丢進了隊列裡。坑爹呢?不執行了?!不是叫

executed

嘛?!收拾好日了狗的心情,我們繼續看下去…