完全理解OkHttp - OkHttp 源碼解析及OkHttp的設計思想

OkHttp 如今統治了Android的網絡請求領域,最經常使用的框架是:Retrofit+okhttp。OkHttp的實現原理和設計思想是必需要了解的,讀懂和理解流行的框架也是程序員進階的必經之路,代碼和語言只是工具,重要的是思想。java

在OKhttp 源碼解析以前,咱們必須先要了解http的相關基礎知識,任何的網絡請求都離不開http。android

概述

okhttp的源碼分析,網上有好多博客講解,但講解的都是一些源碼無關緊要的知識,並無將okhttp的核心設計思想講解到位,咱們閱讀一些框架的源碼,學習的其實就是其設計思想,瞭解了總體的框架設計,在深刻了解細節的實現會更加容易。git

OkHttp 源碼解析

一、OkHttp 的總體框架設計

建議將okhttp的源碼下載下來,用AndroidStudio 打開,整篇文章是根據源碼的分析來學習okhttp的設計技巧和思想,若是本篇文章有內容分析不到位的地方,歡迎你們和我一塊兒討論。程序員

下圖爲okhttp請求網絡的完整流程圖(大體看一遍)github

image.png

okhttp的使用方法

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

咱們第一步先看一下okhttp的構造函數OkHttpClient()和一些配置相關,大體瞭解一下。web

public OkHttpClient() {
        this(new Builder());
    }
複製代碼

原來OkHttpClient 內部已經實現了OkHttpClient(Builder builder),若是咱們不須要配置client,okhttp已將幫咱們默認實現了配置。設計模式

public Builder() {
            dispatcher = new Dispatcher();
            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;
        }
複製代碼

若是須要一些配置如添加攔截器等,則須要這樣調用便可:api

mOkHttpClient = new OkHttpClient.Builder()
                            .addInterceptor(loggingInterceptor)
                            .retryOnConnectionFailure(true)
                            .connectTimeout(TIME_OUT, TimeUnit.SECONDS)
                            .readTimeout(TIME_OUT, TimeUnit.SECONDS)
                            .build();
複製代碼

咱們看一下OkHttpClient 都有那些屬性,稍微瞭解一下便可。後面在分析緩存

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;//cookie
    final @Nullable
    Cache cache;//cache 緩存
    final @Nullable
    InternalCache internalCache;//內部緩存
    final SocketFactory socketFactory;//socket 工廠
    final @Nullable
    SSLSocketFactory sslSocketFactory;//安全套層socket工廠 用於https
    final @Nullable
    CertificateChainCleaner certificateChainCleaner;//驗證確認響應書,適用HTTPS 請求鏈接的主機名
    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;//寫入超時
複製代碼
請求網絡
String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  Response response = client.newCall(request).execute();
  return response.body().string();
}
複製代碼

從源碼中能夠看出 okhttp 實現了Call.Factory接口安全

interface Factory {
    Call newCall(Request request);
  }
複製代碼

咱們看一下okhttpClient 如何實現的Call接口,代碼以下:

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

能夠看出 真正的請求交給了 RealCall 類,而且RealCall 實現了Call方法,RealCall是真正的核心代碼。

RealCall 主要方法:同步請求 :client.newCall(request).execute(); 和 異步請求: client.newCall(request).enqueue();

下面咱們着重分析一下異步請求,由於在項目中全部的網絡請求基本都是異步的,同步不多用到,最後咱們在分析一下同步請求便可。

異步請求

跟着源碼 走一遍 RealCall.enqueue() 的實現

RealCall.java

 @Override public void enqueue(Callback responseCallback) {
    //TODO 不能重複執行
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    //TODO 交給 dispatcher調度器 進行調度
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
複製代碼

能夠看到上述代碼作了幾件事:

  1. synchronized (this) 確保每一個call只能被執行一次不能重複執行,若是想要徹底相同的call,能夠調用以下方法:進行克隆
@SuppressWarnings("CloneDoesntCallSuperClone") // We are a final type & this saves clearing state.
  @Override public RealCall clone() {
    return RealCall.newRealCall(client, originalRequest, forWebSocket);
  }
複製代碼
  1. 利用dispatcher調度器,來進行實際的執行client.dispatcher().enqueue(new AsyncCall(responseCallback));,在上面的OkHttpClient.Builder能夠看出 已經初始化了Dispatcher。

下面咱們着重看一下調度器的實現。

Dispatcher 調度器

Dispatcher#enqueue 的方法實現以下:

//TODO 執行異步請求
    synchronized void enqueue(AsyncCall call) {
        //TODO 同時請求不能超過併發數(64,可配置調度器調整)
        //TODO okhttp會使用共享主機即 地址相同的會共享socket
        //TODO 同一個host最多容許5條線程通知執行請求
        if (runningAsyncCalls.size() < maxRequests &&
                runningCallsForHost(call) < maxRequestsPerHost) {
            //TODO 加入運行隊列 並交給線程池執行
            runningAsyncCalls.add(call);
            //TODO AsyncCall 是一個runnable,放到線程池中去執行,查看其execute實現
            executorService().execute(call);
        } else {
            //TODO 加入等候隊列
            readyAsyncCalls.add(call);
        }
    }
複製代碼

從上述代碼能夠看到Dispatcher將call 加入到隊列中,而後經過線程池來執行call。

可能你們看了仍是懵懵的,咱們先了解一下Dispatcher幾個屬性和方法

//TODO 同時能進行的最大請求數
    private int maxRequests = 64;
    //TODO 同時請求的相同HOST的最大個數 SCHEME :// HOST [ ":" PORT ] [ PATH [ "?" QUERY ]]
    //TODO 如 https://restapi.amap.com  restapi.amap.com - host
    private int maxRequestsPerHost = 5;
    /**
     * Ready async calls in the order they'll be run. * TODO 雙端隊列,支持首尾兩端 雙向開口可進可出,方便移除 * 異步等待隊列 * */ private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); /** * Running asynchronous calls. Includes canceled calls that haven't finished yet.
     * TODO 正在進行的異步隊列
     */
    private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
複製代碼

很明顯,okhttp 能夠進行多個併發網絡請求,而且能夠設置最大的請求數

executorService() 這個方法很簡單,只是建立了一個線程池

public synchronized ExecutorService executorService() {
        if (executorService == null) {
            //TODO 線程池的相關概念 須要理解
            //TODO 核心線程 最大線程 非核心線程閒置60秒回收 任務隊列
            executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher",
                    false));
        }
        return executorService;
    }
複製代碼

既然咱們將 call放到了線程池中那麼它是如何執行的呢?注意這裏的call是AsyncCall。

咱們看一下AsyncCall的實現:

final class AsyncCall extends 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();
}
複製代碼

原來如此 AsyncCall其實就是一個 Runnable,線程池實際上就是執行了execute()。

咱們在看一下AsyncCall的execute()

final class AsyncCall extends NamedRunnable {
    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        //TODO 責任鏈模式
        //TODO 攔截器鏈  執行請求
        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 {
        //TODO 移除隊列
        client.dispatcher().finished(this);
      }
    }
  }
複製代碼

從上述代碼能夠看出真正執行請求的是getResponseWithInterceptorChain(); 而後經過回調將Response返回給用戶。

值得注意的finally 執行了client.dispatcher().finished(this); 經過調度器移除隊列,而且判斷是否存在等待隊列,若是存在,檢查執行隊列是否達到最大值,若是沒有將等待隊列變爲執行隊列。這樣也就確保了等待隊列被執行。

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
        int runningCallsCount;
        Runnable idleCallback;
        synchronized (this) {
            //TODO calls 移除隊列
            if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
            //TODO 檢查是否爲異步請求,檢查等候的隊列 readyAsyncCalls,若是存在等候隊列,則將等候隊列加入執行隊列
            if (promoteCalls) promoteCalls();
            //TODO 運行隊列的數量
            runningCallsCount = runningCallsCount();
            idleCallback = this.idleCallback;
        }
        //閒置調用
        if (runningCallsCount == 0 && idleCallback != null) {
            idleCallback.run();
        }
    }
    
    private void promoteCalls() {
        //TODO 檢查 運行隊列 與 等待隊列
        if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
        if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

        //TODO 將等待隊列加入到運行隊列中
        for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall call = i.next();
            //TODO  相同host的請求沒有達到最大,加入運行隊列
            if (runningCallsForHost(call) < maxRequestsPerHost) {
                i.remove();
                runningAsyncCalls.add(call);
                executorService().execute(call);
            }

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

真正的執行網絡請求和返回響應結果:getResponseWithInterceptorChain(),下面咱們着重分析一下這個方法:

每段代碼我都加上了註釋。

//TODO 核心代碼 開始真正的執行網絡請求
  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    //TODO 責任鏈
    List<Interceptor> interceptors = new ArrayList<>();
    //TODO 在配置okhttpClient 時設置的intercept 由用戶本身設置
    interceptors.addAll(client.interceptors());
    //TODO 負責處理失敗後的重試與重定向
    interceptors.add(retryAndFollowUpInterceptor);
    //TODO 負責把用戶構造的請求轉換爲發送到服務器的請求 、把服務器返回的響應轉換爲用戶友好的響應 處理 配置請求頭等信息
    //TODO 從應用程序代碼到網絡代碼的橋樑。首先,它根據用戶請求構建網絡請求。而後它繼續呼叫網絡。最後,它根據網絡響應構建用戶響應。
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //TODO 處理 緩存配置 根據條件(存在響應緩存並被設置爲不變的或者響應在有效期內)返回緩存響應
    //TODO 設置請求頭(If-None-Match、If-Modified-Since等) 服務器可能返回304(未修改)
    //TODO 可配置用戶本身設置的緩存攔截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //TODO 鏈接服務器 負責和服務器創建鏈接 這裏纔是真正的請求網絡
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      //TODO 配置okhttpClient 時設置的networkInterceptors
      //TODO 返回觀察單個網絡請求和響應的不可變攔截器列表。
      interceptors.addAll(client.networkInterceptors());
    }
    //TODO 執行流操做(寫出請求體、得到響應數據) 負責向服務器發送請求數據、從服務器讀取響應數據
    //TODO 進行http請求報文的封裝與請求報文的解析
    interceptors.add(new CallServerInterceptor(forWebSocket));

    //TODO 建立責任鏈
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    //TODO 執行責任鏈
    return chain.proceed(originalRequest);
  }
複製代碼

從上述代碼中,能夠看出都實現了Interceptor接口,這是okhttp最核心的部分,採用責任鏈的模式來使每一個功能分開,每一個Interceptor自行完成本身的任務,而且將不屬於本身的任務交給下一個,簡化了各自的責任和邏輯。

責任鏈模式是設計模式中的一種也至關簡單參考連接,這裏不在複述。

咱們着重分析一下,okhttp的設計實現,如何經過責任鏈來進行傳遞返回數據的。

上述代碼中能夠看出interceptors,是傳遞到了RealInterceptorChain該類實現了Interceptor.Chain,而且執行了chain.proceed(originalRequest)。

其實核心代碼就是chain.proceed() 經過該方法進行責任鏈的執行。

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();
    calls++;
    //TODO 建立新的攔截鏈,鏈中的攔截器集合index+1
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    //TODO 執行當前的攔截器-若是在配置okhttpClient,時沒有設置intercept默認是先執行:retryAndFollowUpInterceptor 攔截器
    Interceptor interceptor = interceptors.get(index);
    //TODO 執行攔截器
    Response response = interceptor.intercept(next);
    return response;
  }
複製代碼

從上述代碼,咱們能夠知道,新建了一個RealInterceptorChain 責任鏈 而且 index+1,而後 執行interceptors.get(index); 返回Response。

其實就是按順序執行了攔截器,這裏我畫了一個簡圖:

image.png

攔截器的執行順序即是如上圖這樣執行的。

這樣設計的一個好處就是,責任鏈中每一個攔截器都會執行chain.proceed()方法以前的代碼,等責任鏈最後一個攔截器執行完畢後會返回最終的響應數據,而chain.proceed() 方法會獲得最終的響應數據,這時就會執行每一個攔截器的chain.proceed()方法以後的代碼,其實就是對響應數據的一些操做。

CacheInterceptor 緩存攔截器就是很好的證實,咱們來經過CacheInterceptor 緩存攔截器來進行分析,你們就會明白了。

CacheInterceptor 的實現以下:

代碼比較長,咱們一步一步的來進行分析。

首先咱們先分析上部分代碼當沒有網絡的狀況下是如何處理獲取緩存的。

@Override public Response intercept(Chain chain) throws IOException
  {
//TODO 獲取request對應緩存的Response 若是用戶沒有配置緩存攔截器 cacheCandidate == null
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    //TODO 執行響應緩存策略
    long now = System.currentTimeMillis();
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    //TODO 若是networkRequest == null 則說明不使用網絡請求
    Request networkRequest = strategy.networkRequest;
    //TODO 獲取緩存中(CacheStrategy)的Response
    Response cacheResponse = strategy.cacheResponse;

    if (cache != null) {
      cache.trackResponse(strategy);
    }
    //TODO 緩存無效 關閉資源
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it. } // If we're forbidden from using the network and the cache is insufficient, fail.
    //TODO networkRequest == null 不實用網路請求 且沒有緩存 cacheResponse == null  返回失敗
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    //TODO 不使用網絡請求 且存在緩存 直接返回響應
    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
    }
複製代碼

上述的代碼,主要作了幾件事:

  1. 若是用戶本身配置了緩存攔截器,cacheCandidate = cache.Response 獲取用戶本身存儲的Response,不然 cacheCandidate = null;同時從CacheStrategy 獲取cacheResponse 和 networkRequest
  2. 若是cacheCandidate != null 而 cacheResponse == null 說明緩存無效清楚cacheCandidate緩存。
  3. 若是networkRequest == null 說明沒有網絡,cacheResponse == null 沒有緩存,返回失敗的信息,責任鏈此時也就終止,不會在往下繼續執行。
  4. 若是networkRequest == null 說明沒有網絡,cacheResponse != null 有緩存,返回緩存的信息,責任鏈此時也就終止,不會在往下繼續執行。

上部分代碼,其實就是沒有網絡的時候的處理。

那麼下部分代碼確定是,有網絡的時候處理

//TODO 執行下一個攔截器
    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    //TODO 網絡請求 回來 更新緩存
    // If we have a cache response too, then we're doing a conditional get. //TODO 若是存在緩存 更新 if (cacheResponse != null) { //TODO 304響應碼 自從上次請求後,請求須要響應的內容未發生改變 if (networkResponse.code() == HTTP_NOT_MODIFIED) { Response response = cacheResponse.newBuilder() .headers(combine(cacheResponse.headers(), networkResponse.headers())) .sentRequestAtMillis(networkResponse.sentRequestAtMillis()) .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis()) .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); networkResponse.body().close(); // Update the cache after combining headers but before stripping the // Content-Encoding header (as performed by initContentStream()). cache.trackConditionalCacheHit(); cache.update(cacheResponse, response); return response; } else { closeQuietly(cacheResponse.body()); } } //TODO 緩存Response Response response = networkResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); if (cache != null) { if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) { // Offer this request to the cache. CacheRequest cacheRequest = cache.put(response); return cacheWritingResponse(cacheRequest, response); } if (HttpMethod.invalidatesCache(networkRequest.method())) { try { cache.remove(networkRequest); } catch (IOException ignored) { // The cache cannot be written. } } } return response; } 複製代碼

下部分代碼主要作了這幾件事:

  1. 執行下一個攔截器,也就是請求網絡
  2. 責任鏈執行完畢後,會返回最終響應數據,若是緩存存在更新緩存,若是緩存不存在加入到緩存中去。

這樣就體現出了,責任鏈這樣實現的好處了,當責任鏈執行完畢,若是攔截器想要拿到最終的數據作其餘的邏輯處理等,這樣就不用在作其餘的調用方法邏輯了,直接在當前的攔截器就能夠拿到最終的數據。

這也是okhttp設計的最優雅最核心的功能。

固然咱們能夠經過一個小例子來進行驗證,實踐才最重要。

首先咱們模擬一個 攔截器的接口

/**
 * @author prim
 * @version 1.0.0
 * @desc 模擬okhttp攔截器
 * @time 2018/8/3 - 下午4:29
 */
public interface Interceptor {
    String interceptor(Chain chain);

    interface Chain {
        String request();

        String proceed(String request);
    }
}
複製代碼

而後在實現幾個攔截器

public class BridgeInterceptor implements Interceptor {
    @Override
    public String interceptor(Chain chain) {
        System.out.println("執行 BridgeInterceptor 攔截器以前代碼");
        String proceed = chain.proceed(chain.request());
        System.out.println("執行 BridgeInterceptor 攔截器以後代碼 獲得最終數據:"+proceed);
        return proceed;
    }
}

public class RetryAndFollowInterceptor implements Interceptor {
    @Override
    public String interceptor(Chain chain) {
        System.out.println("執行 RetryAndFollowInterceptor 攔截器以前代碼");
        String proceed = chain.proceed(chain.request());
        System.out.println("執行 RetryAndFollowInterceptor 攔截器以後代碼 獲得最終數據:" + proceed);
        return proceed;
    }
}

public class CacheInterceptor implements Interceptor {
    @Override
    public String interceptor(Chain chain) {
        System.out.println("執行 CacheInterceptor 最後一個攔截器 返回最終數據");
        return "success";
    }
}
複製代碼

而後實現Chain 接口

public class RealInterceptorChain implements Interceptor.Chain {

    private List<Interceptor> interceptors;

    private int index;

    private String request;

    public RealInterceptorChain(List<Interceptor> interceptors, int index, String request) {
        this.interceptors = interceptors;
        this.index = index;
        this.request = request;
    }

    @Override
    public String request() {
        return request;
    }

    @Override
    public String proceed(String request) {
        if (index >= interceptors.size()) return null;

        //獲取下一個責任鏈
        RealInterceptorChain next = new RealInterceptorChain(interceptors, index+1, request);
        // 執行當前的攔截器
        Interceptor interceptor = interceptors.get(index);

        return interceptor.interceptor(next);
    }
}
複製代碼

而後進行測試,看看咱們是否分析的正確

List<Interceptor> interceptors = new ArrayList<>();
        interceptors.add(new BridgeInterceptor());
        interceptors.add(new RetryAndFollowInterceptor());
        interceptors.add(new CacheInterceptor());

        RealInterceptorChain request = new RealInterceptorChain(interceptors, 0, "request");

        request.proceed("request");
複製代碼

打印的log日誌以下:

執行 BridgeInterceptor 攔截器以前代碼
執行 RetryAndFollowInterceptor 攔截器以前代碼
執行 CacheInterceptor 最後一個攔截器 返回最終數據
執行 RetryAndFollowInterceptor 攔截器以後代碼 獲得最終數據:success
執行 BridgeInterceptor 攔截器以後代碼 獲得最終數據:success
複製代碼

OK 完美,驗證沒有問題,我想至此你們都應該懂了 okhttp的核心設計思想了。

okhttp的其餘攔截器的具體實現你們能夠本身研究一下便可,okhttp的這種設計思想咱們徹底能夠應用到項目中去,解決一些問題。

同步請求

這裏在稍微講一下,okhttp的同步請求,代碼很簡單 一樣是在RealCall 類中實現的

//TODO 同步執行請求 直接返回一個請求的結果
  @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    //TODO 調用監聽的開始方法
    eventListener.callStart(this);
    try {
      //TODO 交給調度器去執行
      client.dispatcher().executed(this);
      //TODO 獲取請求的返回數據
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      //TODO 執行調度器的完成方法 移除隊列
      client.dispatcher().finished(this);
    }
  }
複製代碼

主要作了幾件事:

  1. synchronized (this) 避免重複執行,上面的文章部分有講。
  2. client.dispatcher().executed(this); 實際上調度器只是將call 加入到了同步執行隊列中。代碼以下:
//TODO 調度器執行同步請求
    synchronized void executed(RealCall call) {
        runningSyncCalls.add(call);
    }
複製代碼
  1. getResponseWithInterceptorChain()最核心的代碼,上面已經分析過了,請求網絡獲得響應數據,返回給用戶。
  2. client.dispatcher().finished(this); 執行調度器的完成方法 移除隊列

能夠看出,在同步請求的方法中,涉及到dispatcher 只是告知了執行狀態,開始執行了(調用 executed),執行完畢了(調用 finished)其餘的並無涉及到。dispatcher 更多的是服務異步請求。

總結

okhttp還有不少細節在本文中並無涉及到,例如:okhttp是如何利用DiskLruCache實現緩存的、HTTP2/HTTPS 的支持等,本文主要講解okhttp的核心設計思想,對總體有了清晰的認識以後,在深刻細節,更容易理解。

簡述okhttp的執行流程:

  1. OkhttpClient 實現了Call.Fctory,負責爲Request 建立 Call;
  2. RealCall 爲Call的具體實現,其enqueue() 異步請求接口經過Dispatcher()調度器利用ExcutorService實現,而最終進行網絡請求時和同步的execute()接口一致,都是經過 getResponseWithInterceptorChain() 函數實現
  3. getResponseWithInterceptorChain() 中利用 Interceptor 鏈條,責任鏈模式 分層實現緩存、透明壓縮、網絡 IO 等功能;最終將響應數據返回給用戶。

拆輪子系列:拆 OkHttp

OkHttp

相關文章
相關標籤/搜索