天天看點

okhttp源碼解析okhttp介紹okhttp優點okhttp簡單用法okhttp的源碼深入請求過程源碼解析排程器異步請求攔截器

okhttp介紹

OkHttp是一個非常優秀的網絡請求架構,已被谷歌加入到Android的源碼中。目前比較流行的Retrofit也是預設使用OkHttp的。是以OkHttp的源碼是一個不容錯過的學習資源,學習源碼之前,務必熟練使用這個架構,否則就是跟自己過不去。

okhttp優點

  • 支援HTTP2/SPDY黑科技
  • socket自動選擇最好路線,并支援自動重連
  • 擁有自動維護的socket連接配接池,減少握手次數
  • 擁有隊列線程池,輕松寫并發
  • 擁有Interceptors輕松處理請求與響應(比如透明GZIP壓-

    縮,LOGGING)

  • 實作基于Headers的緩存政策

至于為什麼有這麼多優點,各位看官老爺在下面的源碼解析中慢慢體會吧!

okhttp簡單用法

既然是網絡架構,那麼先來看看它的post和get請求吧。總的來說,分為三步:

  1. 執行個體化一個OkHttpClient 對象;
  2. 構造Request請求體;
  3. 發請求,同步調用okHttpClient.newCall(request).execute();異步調用 okHttpClient.newCall(request).enqueue(new Callback())。
  • get(異步)請求
OkHttpClient okHttpClient = new OkHttpClient();
                Request request = new Request.Builder().url(URL).build();

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

                        }
                        @Override
                        public void onResponse(Call call, Response response) throws IOException {
                            String dataStr=response.body().string();
                            Log.e("info",dataStr);
                        }
                    });
           
  • get(同步)請求
OkHttpClient okHttpClient = new OkHttpClient();
Response  response = okHttpClient.newCall(request).execute();
String dataStr=response.body().string();
           
  • post(異步)請求
OkHttpClient okHttpClient = new OkHttpClient();
                RequestBody requestBody = new FormBody
                        .Builder()
                        .add("name", "張士超")
                        .add("password", "123456")
                        .build();
                Request request = new Request
                        .Builder()
                        .url(URL)
                        .post(requestBody)
                        .build();
                    okHttpClient.newCall(request).enqueue(new Callback() {
                        @Override
                        public void onFailure(Call call, IOException e) {

                        }

                        @Override
                        public void onResponse(Call call, Response response) throws IOException {
                            String dataStr=response.body().string();
                            Log.e("info",dataStr);
                        }
                    });
           
  • post(同步)請求
OkHttpClient okHttpClient = new OkHttpClient();
                RequestBody requestBody = new FormBody
                        .Builder()
                        .add("name", "張士超")
                        .add("password", "123456")
                        .build();
                Request request = new Request
                        .Builder()
                        .url(URL)
                        .post(requestBody)
                        .build();
                try {
                    Response  response =   okHttpClient.newCall(request).execute();
                    String dataStr=response.body().string();
                } catch (IOException e) {
                    e.printStackTrace();
                }
           
  • 總結:post與get請求差別在于需要構造一個RequestBody對象,同步和異步差別在于執行的是execute()還是enqueue(new Callback()),這也很好了解,異步需要回調接口回報的請求資料對吧。

okhttp的源碼深入

  • 好啦,步入正題,看看我們的okhttp同學是怎麼樣完成一個個請求的!

首先我們看看okhttp源碼涉及到的幾個類的源碼:

  • Request請求類:
public final class Request {
  final HttpUrl url;
  final String method;
  final Headers headers;
  final @Nullable RequestBody body;
  final Object tag;
  private volatile CacheControl cacheControl; // Lazily initialized.
           

這裡面比較重要的是url、method、header和RequestBody,分别代表request請求的請求URL、請求方法、請求頭和請求體。

  • Response響應類
public final class Response implements Closeable {
  final Request request;
  final Protocol protocol;
  final int code;
  final String message;
  final @Nullable Handshake handshake;
  final Headers headers;
  final @Nullable ResponseBody body;
  final @Nullable Response networkResponse;
  final @Nullable Response cacheResponse;
  final @Nullable Response priorResponse;
  final long sentRequestAtMillis;
  final long receivedResponseAtMillis;
           

這裡面比較重要的是code響應碼、message響應資訊、headers響應頭和body響應體。

  • Okhttp類
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;
  final @Nullable Cache cache;
  final @Nullable InternalCache internalCache;
  final SocketFactory socketFactory;
  final @Nullable SSLSocketFactory sslSocketFactory;
  final @Nullable CertificateChainCleaner certificateChainCleaner;
  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 connectTimeout;
  final int readTimeout;
  final int writeTimeout;
  final int pingInterval;
           

Okhttp包含的東西很多,這裡我們需要重點關注的是dispatcher排程器,interceptors自定義應用攔截器和networkInterceptors自定義網絡攔截器。

  • ok,比較重要的三個類介紹完了,接下來我們看一看同步和異步請求的源碼解析,先放一張圖(盜的别人的圖hhhh),讓看官老爺門大緻上有一個印象,後面比較好了解。

    整個請求過程

    整個過程大緻說一下吧,首先不管啥請求okHttpClient.newCall(request)這玩意實際上傳回一個RealCall類,然後同步請求調用execute(),異步調用enqueue()之後給排程器,其實同步和異步都給了排程器,隻是異步調用了排程器的execute()進行請求排程處理,下面給一張圖就了解啦:

    排程器

    處理完之後通過一系列花裡胡哨的攔截器之後傳回一個response響應,就獲得資料啦!

請求過程源碼解析

以下面代碼為例,看看源碼都幹了啥:

OkHttpClient okHttpClient = new OkHttpClient();
                Request request = new Request.Builder().url(URL).build();
                    okHttpClient.newCall(request).enqueue(new Callback()/execute()
           

前面兩句話就不說了,就是OkHttpClient和Request 對象的執行個體化,我們看看okHttpClient.newCall(request)這裡都幹了些啥:

@Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false );
  }
           

進去newRealCall,看看:

static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
  }
           

總之就是工廠模式将request封裝成一個RealCall對象,接着看okHttpClient.newCall(request).execute(),同步請求;

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

比較重要的是上面三處注釋:

  1. 首先看這個請求有沒有被執行,每個請求隻能被執行一次;
  2. 若沒有被執行,則将這個請求任務給排程器dispatcher,簡單看看這個排程器做了些什麼:
synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }
           

将這個請求加入到runningSyncCalls隊列中,後面詳細說說這個排程器。

  1. 經過一系列花裡胡哨的攔截器得到響應,這具體後再說怎麼做的。
  2. 執行完之後排程器dispatcher結束這個請求任務,看看具體怎麼做的:
void finished(RealCall call) {
    finished(runningSyncCalls, call, false);
  }
           

将請求任務從runningSyncCalls隊列中移除。

  • 這樣整個同步請求流程就實作了,看下面這個圖會清晰很多:

    同步請求任務.png

  • 上面可以知道,請求處理都交給排程器了,那我們來看看這個排程器是個什麼鬼:
public final class Dispatcher {
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private @Nullable Runnable idleCallback;
  private @Nullable ExecutorService executorService;
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
           
  • 解釋一下這幾個參數啥子意思,第一個maxRequests最大請求數,表示最大并發數 ;maxRequestsPerHost 單個域名最大請求數 預設為 5 個。是以極端情況下,才會開啟 64 個線程。這個場景非常罕見;readyAsyncCalls 表示異步請求的等待隊列,runningAsyncCalls 正在執行的異步請求隊列,AsyncCall隊列;runningSyncCalls 正在執行的同步請求隊列,因為是同步的,這也好了解,是以不需要等待隊列。executorService這個就是排程器的線程池,java線程池我們都知道,我們看看這裡的線程池怎麼定義的:
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,非核心線程無數個,每個空線程的回收時間為60s。

異步請求

異步請求以下面這個為例,看看源碼都怎麼做的:

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

                        }
                        @Override
                        public void onResponse(Call call, Response response) throws IOException {
                            String dataStr=response.body().string();
                            Log.e("info",dataStr);
                        }
                    });
           
  • 與同步請求的差距主要在于調用的是enqueue()函數,點進去看看與execute的差別
@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));
  }
           
  • 先判斷有沒有執行過,如果沒有,這執行排程器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小于最大并發數,而且單域名的正在執行異步隊列小于最大單域名最大請求數,則将這個請求放到正在執行的異步請求隊列,并調用排程器的execute()方法,否則就放入等待異步隊列。
  • 排程器線程池總結
  1. 排程線程池Disptcher實作了高并發,低阻塞的實作
  2. 采用Deque作為緩存,先進先出的順序執行
  3. 任務在try/finally中調用了finished函數,控制任務隊列的執行順序,而不是采用鎖,減少了編碼複雜性提高性能

攔截器

  • 主要是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);
    return chain.proceed(originalRequest);
  }
           

1)在配置 OkHttpClient 時設定的 interceptors;

2)負責失敗重試以及重定向的 RetryAndFollowUpInterceptor;

3)負責把使用者構造的請求轉換為發送到伺服器的請求、把伺服器傳回的響應轉換為使用者友好的響應的 BridgeInterceptor;

4)負責讀取緩存直接傳回、更新緩存的 CacheInterceptor;

5)負責和伺服器建立連接配接的 ConnectInterceptor;

6)配置 OkHttpClient 時設定的 networkInterceptors;

7)負責向伺服器發送請求資料、從伺服器讀取響應資料的 CallServerInterceptor。

  • OkHttp的這種攔截器鍊采用的是責任鍊模式,這樣的好處是将請求的發送和處理分開,并且可以動态添加中間的處理方實作對請求的處理、短路等操作。給個圖加強了解:

    攔截器.png

  • 不多說了,結合圖和源碼了解吧,創作不易,給個小心心吧!

繼續閱讀