Android:okhttp源碼淺析

1.前言

Android網絡平臺的三板斧基本被square公司承包了,Okhttp,Retrofit,Okio真但是三巨頭。平時用的okhttp比較多,因此咱們頗有必要來看看它的實現原理。java

2.使用

相信你們對okhttp的使用一點都不陌生了,我這裏不會詳細講解它的使用,僅僅把官網上的搬下來看看,做爲研究的入口:web

OkHttpClient client = new OkHttpClient();

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

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

複製代碼

這個是比較簡單的Get請求json

public static final MediaType JSON
    = MediaType.get("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();
  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}
複製代碼

這個是Post請求設計模式

3.詳細分析

3.1 OkHttpClient實例化

從上面的使用來看,不論是get仍是post請求,都會先建立OkHttpClient實例,這個很容易理解,沒有實例哪來的入口呢,觀摩一下:緩存

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

接着看看實例化了哪些東西bash

public OkHttpClient() {
    this(new Builder());
  }
複製代碼
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;
    ... 
  }
複製代碼

這是默認的實例化方式,builder裏面也都是默認值,主要實例了代理,緩存,超時等等一些參數,這些系統都給咱們設置了一些默認的值,好比超時是10s,讀寫也是10s等等。可是在實際狀況中,咱們每每去要自定義一些方式,好比,讀寫時間設置30s,增長一些攔截器等,這就須要另一種實例化的方式了。服務器

OkHttpClient okHttpClient = new OkHttpClient.Builder()
                    .addInterceptor(loggingInterceptor)
                    .connectTimeout(20 * 1000, TimeUnit.MILLISECONDS)
                    .readTimeout(30 * 1000, TimeUnit.MILLISECONDS)
                    /* 關閉OkHttp失敗重試鏈接的機制,該問題致使發帖重複的問題 */
                    .retryOnConnectionFailure(false)
                    .addInterceptor(new EnhancedCacheInterceptor())
                    .addInterceptor(new SecurityInterceptor(mApplicationContext)) // 加密解密
                    .addNetworkInterceptor(new StethoInterceptor())
                    .dns(new dsn().build();
複製代碼

看上去很清晰,採用的是建造者模式,咱們設置自定義參數,而後經過build去實例化。這兩種方式具體怎麼用,選擇哪種那就看咱們具體業務了。cookie

3.2構建Request

接下來看看Request的封裝網絡

Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
複製代碼

一樣的,無論什麼請求方式,都要構建Requestapp

final HttpUrl url;
  final String method;
  final Headers headers;
  final @Nullable RequestBody body;
  final Object tag;

  private volatile CacheControl cacheControl; // Lazily initialized.

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

能夠看到request包含url,請求方法,header,以及body。這也就對應了http裏面的請求頭部,包含一些具體信息。它也是採用的建造者模式去構造的。平時咱們用得比較多的是post請求,這裏咱們作個簡要的分析:

RequestBody body = RequestBody.create(JSON, json);
...

  public Builder post(RequestBody body) {
      return method("POST", body);
    }
複製代碼

首先咱們構造RequestBody,body裏面封裝表單或者json,而後調用post方法:

if (method == null) throw new NullPointerException("method == null");
      if (method.length() == 0) throw new IllegalArgumentException("method.length() == 0");
      if (body != null && !HttpMethod.permitsRequestBody(method)) {
        throw new IllegalArgumentException("method " + method + " must not have a request body.");
      }
      if (body == null && HttpMethod.requiresRequestBody(method)) {
        throw new IllegalArgumentException("method " + method + " must have a request body.");
      }
      this.method = method;
      this.body = body;
      return this;
    }
複製代碼

這裏面很簡單,分別對method,body作個校驗,這二者都不能爲空,也就是說,當採用post去發請求的時候body是不能爲空的,這個錯誤有時候咱們常常會遇到。而後賦值給request的變量存着備用。

3.3請求發送

ok,若是上面的都是準備工做,那麼接下來就是正式發送請求了。仍是直接看代碼:

Response response = client.newCall(request).execute()
複製代碼

這個是發送同步請求的,哇塞,一句話?是的,就是一句話解決了,看上去簡直難以想象,咱們看看源碼newCall方法,很顯然call是發送請求的核心:

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

接着去RealCall類中去調用newRealCall方法:

private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
  }
複製代碼

構造函數把參數賦值,而後new了一個實例RetryAndFollowUpInterceptor,這個咱們後面再來看。接着就是調用execut函數了:

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

這裏作了幾件事:

  • 1.檢查當前call是否已經執行,若是已經執行過了就拋異常,每一個call只執行一次。
  • 2.調用dispatcher執行executed執行請求,dispatcher是異步請求的概念,同步也有涉及,這是作得事不多。
  • 3.getResponseWithInterceptorChain獲取請求,攔截器去攔截請求,作對應的處理以後返回結果。
  • 4.關閉請求。 有些細節咱們沒必要過多的在意,真正發請求後回去結果的是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);
  }
複製代碼

這看上去有點兒陌生,裏面增長了各類攔截器,而後調用了chain.proceed(originalRequest),感受這纔是大boss啊,官網裏面有句話

the whole thing is just a stack of built-in interceptors.
複製代碼

這句話頗有含金量,也就是說okhttp所作的是就是攔截各類請求去解析,對攔截器一一作處理,納悶了,爲啥要攔截這麼多?由於一個請求的發送到接收到返回數據,中間要作不少是事,好比失敗重傳,緩存,還有自定義的攔截器,好比對數據加密等等。

3.4分析攔截器

首先給出一張總體圖:

上一節中,實例化RealInterceptorChain 後調用了chain.proceed();最終會執行下面的代碼

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

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // 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 next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

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

這是核心中的核心,它採用了責任鏈模式,這種模式,是java設計模式中比較重要的一種,你們不懂的能夠單獨去查查,它的整體思想就是若是本身能夠消費事件就消費,不消費就傳遞給下一個攔截器。

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

能夠看到這裏的index進行了+1處理,而後開始啓動下一個攔截器。很顯然,這這些攔截器的執行是有順序的。咱們先看看這種攔截器的做用:

  • RetryAndFollowUpInterceptor:負責失敗重傳和重定向。
  • BridgeInterceptor
/**
 * Bridges from application code to network code. First it builds a network request from a user
 * request. Then it proceeds to call the network. Finally it builds a user response from the network
 * response.
 */
複製代碼

它的意思是把用戶的代碼,也就是咱們寫的代碼,轉換爲服務器標準的代碼。解釋:它會提取http請求所須要的一些參數,好比agent,heades,contenttype等,而後發送出去。響應的過程也是同樣,把服務器的響應結果轉換爲咱們須要的,好比把respones轉換爲咱們須要的對象。實質就是按照http協議標準化格式。

  • CacheInterceptor
/** Serves requests from the cache and writes responses to the cache. */
複製代碼

這個主要是處理緩存的,咱們發送請求和獲取響應結果的時候,不能每次都去從新建立吧,那樣效率過低,首先去從緩存中去取沒,若是緩存中有現有的鏈接,咱們直接發請求,不用重複建立。響應結果也是同樣的。

  • ConnectInterceptor
/** Opens a connection to the target server and proceeds to the next interceptor. */
複製代碼

若是緩存沒有命中,那麼就去新建鏈接啦,這個用來新建鏈接的。

  • CallServerInterceptor
/** This is the last interceptor in the chain. It makes a network call to the server. */
複製代碼

這個是責任鏈模式的尾端,它最終會去經過網絡請求服務器資源。

這裏我來着重分析一下ConnectInterceptor,CallServerInterceptor這兩種,由於這是最終創建鏈接和發送請求的過程,能夠說是最重要的。

3.4.1 ConnectInterceptor
@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
複製代碼

這個過程建立了HttpCodec,可是它並無在這個攔截器中去使用,而是傳遞到後面發動請求的攔截器中去了也便是CallServerInterceptor。HttpCodec究竟是什麼,它是對HTTP協議作了抽象處理,在 Http1Codec 中,它利用 Okio 對 Socket 的讀寫操做進行封裝,Okio 之後有機會再進行分析,如今讓咱們對它們保持一個簡單地認識:它對 java.io 和 java.nio 進行了封裝,讓咱們更便捷高效的進行 IO 操做。

3.4.2 CallServerInterceptor
@Override public Response intercept(Chain chain) throws IOException {
  HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
  StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
  Request request = chain.request();

  long sentRequestMillis = System.currentTimeMillis();
  httpCodec.writeRequestHeaders(request);

  if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
    Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
    BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
    request.body().writeTo(bufferedRequestBody);
    bufferedRequestBody.close();
  }

  httpCodec.finishRequest();

  Response response = httpCodec.readResponseHeaders()
      .request(request)
      .handshake(streamAllocation.connection().handshake())
      .sentRequestAtMillis(sentRequestMillis)
      .receivedResponseAtMillis(System.currentTimeMillis())
      .build();

  if (!forWebSocket || response.code() != 101) {
    response = response.newBuilder()
        .body(httpCodec.openResponseBody(response))
        .build();
  }

  if ("close".equalsIgnoreCase(response.request().header("Connection"))
      || "close".equalsIgnoreCase(response.header("Connection"))) {
    streamAllocation.noNewStreams();
  }

  // 省略部分檢查代碼

  return response;
}
複製代碼

這個裏面代碼比較長,咱們只保留核心分心,上面不是傳遞了HttpCodec了嘛,這裏就利用起來了,它是利用okio進行發送請求,這裏okio不是咱們分析的重點,讀者能夠單獨找文章去看看。 總之:Okhttp發送請求是經過okio去作的,okio實質上就是對socket進行了封裝,一層套一層而已,這就是神祕的面紗後面的簡單。 這裏作了幾件事:

  • 1.發送header
  • 2.發送body,若是有
  • 3.獲取響應數據
  • 4.close鏈接 這裏給出一張總體總結圖:

4.總結

咱們對okhttp作了個總體介紹,固然裏面有不少細節沒有解釋,也不想去解釋,讀者有須要本身去研究。這裏我總結一下:

4.1 設計模式

OKhttp中使用的設計模式:建造者模式和責任鏈模式;

4.2 整體執行流程

  • OkHttpClient 實現 Call.Factory,負責爲 Request 建立 Call;
  • RealCall 爲具體的 Call 實現,其 enqueue() 異步接口經過 Dispatcher 利用 ExecutorService 實現,而最終進行網絡請求時和同步 execute() 接口一致,都是經過 getResponseWithInterceptorChain() 函數實現;
  • getResponseWithInterceptorChain() 中利用 Interceptor 鏈條,分層實現緩存、透明壓縮、網絡 IO 等功能;

最後上一張圖片,這個是從其餘讀者文章弄過來的,很用心,我就直接使用了;

相關文章
相關標籤/搜索