okhttp3源碼分析

前言

相信大家想仔細認真的看下源碼憔儿,然后去網(wǎng)上找資料都會有這樣的兩個感覺

  • 版本不一致,導致和博主分析的有偏差
  • 突然就跳轉到某個關鍵類镰禾,根本不知道是從哪里進去的

反正阿簡在看源碼找資料的時候就是這樣皿曲,說著說著快流了淚...
那阿簡今天來和大家一起慢慢分析一下okhttp攔截器源碼

先上總的框架圖

okhtt.png

源碼分析準備

  • app的build.gradle引入okhttp相關包
    implementation 'com.squareup.okhttp3:okhttp:3.14.4'
  • 創(chuàng)建一個簡單的activity,寫一個簡單的GET請求的okhttp異步方法
    private void okhttpTest() {
        String url = "http://wwww.baidu.com";
        OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(TestInterceptor);
        final Request request = new Request.Builder()
                .url(url)
                .get()//默認就是GET請求吴侦,可以不寫
                .build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.d(TAG, "onFailure: ");
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.d(TAG, "onResponse: " + response.body().string());
            }
        });
    }

然后開始我們的源碼分析~

首先屋休,我們創(chuàng)建了一個OkHttpClient,點進去看它的構造方法备韧,初始化了一個叫做Builder的靜態(tài)內部類,我們看看初始化了哪些參數(shù)(部分參數(shù)介紹請看注釋)

 public Builder() {
     //任務調度器
      dispatcher = new Dispatcher();
     //支持的協(xié)議
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      if (proxySelector == null) {
        proxySelector = new NullProxySelector();
      }
      cookieJar = CookieJar.NO_COOKIES;
      socketFactory = SocketFactory.getDefault();
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      certificatePinner = CertificatePinner.DEFAULT;
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;
      //ConnectionPool是一個連接池對象劫樟,它可以用來管理連接對象,從它的構造方法中可以看到連接池的默認空閑連接數(shù)為5個织堂,keepAlive時間為5分鐘叠艳。
      connectionPool = new ConnectionPool();
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      retryOnConnectionFailure = true;
      callTimeout = 0;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }

阿簡認為,初始化的這些這些成員變量中最重要的是Dispatcher易阳,其他都是一些一看命名就大致知道的成員變量附较,那我們來看下這個任務調度器包含什么

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

可以看到Dispatcher這個類中包含了兩個異步隊列,一個同步隊列用來保存請求潦俺,該文章主要從異步請求分析拒课,所以大家可以記一下這兩個異步請求的成員變量名稱徐勃,后面會用到readyAsyncCallsrunningAsyncCalls

接著我們通過建造者模式創(chuàng)建了一個Request請求:

      final Request request = new Request.Builder()
                .url(url)
                .get()//默認就是GET請求早像,可以不寫
                .build();

可以看到Request里面就是包含一個完整的請求報文的幾個成員變量僻肖,對這部門計算機網(wǎng)絡有疑惑的小伙伴可以參考阿簡的這篇文章喲:網(wǎng)絡基本知識-http

public final class Request {
  final HttpUrl url;
  final String method;
  final Headers headers;
  final @Nullable RequestBody body;
  final Map<Class<?>, Object> tags;

緊接著,我們通過okhttpClient去new了一個請求(call)卢鹦,newCall點進去

   Call call = okHttpClient.newCall(request);

可以看到最后是調用到了一個叫RealCall類的一個靜態(tài)方法newRealCall臀脏,該方法創(chuàng)建了一個realCall的成員變量,傳入了OkHttpClient 對象冀自,同時創(chuàng)建了一個叫Transmitter的對象揉稚,這個類是干嘛的后面會提到(官方注釋是:OkHttp的應用層和網(wǎng)絡層之間的橋梁)

  static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.transmitter = new Transmitter(client, call);
    return call;
  }

然后開始我們的異步請求

        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.d(TAG, "onFailure: ");
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.d(TAG, "onResponse: " + response.body().string());
            }
        });

點進去,可以看到Call只有一個實現(xiàn)類凡纳,就是我們剛才的那個RealCall窃植,

  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.callStart();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

可以看到這里transmitter干了一件事callStart()
然后通過Okhttp的那個成員變量dispatcher(任務調度器)調用了異步方法enqueue(AsyncCall call),傳入了我們的CallBack回調監(jiān)聽(觀察者模式喔)
然后最最最重要的來啦
我們看RealCall里面的這個內部類AsyncCall荐糜,繼承了NamedRunnable巷怜,NamedRunnable又實現(xiàn)了Runnable 接口,那必然要實現(xiàn)run方法暴氏,run方法里面又調用了execute

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延塑,后面會調用到這里的關鍵代碼
回到剛才 client.dispatcher().enqueue這里,可以看到Dispatcher中的enqueue方法答渔,我們的請求放入了readyAsyncCalls這個異步隊列

  void enqueue(AsyncCall call) {
    synchronized (this) {
     //請求放入了該異步隊列
      readyAsyncCalls.add(call);

      // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
      // the same host.
      if (!call.get().forWebSocket) {
        AsyncCall existingCall = findExistingCallWithHost(call.host());
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
      }
    }
    promoteAndExecute();
  }

進入promoteAndExecute()方法关带,這個方法很重要哦,可以看到兩個判斷條件
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
如果正在執(zhí)行的異步請求數(shù)量大于最大的請求數(shù)量(maxRequests 默認等于64)的話
if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity
每個主機最大請求數(shù)沼撕,默認為5

  private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));
    //創(chuàng)建一個臨時變量List用作儲存異步請求
    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        AsyncCall asyncCall = i.next();

        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
        if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.

        i.remove();
       //循環(huán)中把請求分別放入臨時變量和readyAsyncCalls中
        asyncCall.callsPerHost().incrementAndGet();
        executableCalls.add(asyncCall);
        runningAsyncCalls.add(asyncCall);
      }
      isRunning = runningCallsCount() > 0;
    }

  //遍歷該臨時變量宋雏,執(zhí)行executeOn
    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      asyncCall.executeOn(executorService());
    }

    return isRunning;
  }

我們看到遍歷的 asyncCall.executeOn(executorService())這行代碼,然后回到了RealCall的內部類AsyncCall的executeOn方法务豺,然后傳入了ThreadPoolExecutor線程池對象

    void executeOn(ExecutorService executorService) {
      assert (!Thread.holdsLock(client.dispatcher()));
      boolean success = false;
      try {
        executorService.execute(this);
        success = true;
      } catch (RejectedExecutionException e) {
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        transmitter.noMoreExchanges(ioException);
        responseCallback.onFailure(RealCall.this, ioException);
      } finally {
        if (!success) {
          client.dispatcher().finished(this); // This call is no longer running!
        }
      }
    }
  public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

執(zhí)行了 executorService.execute(this)磨总,傳入了this,this是什么,是不是就是我們剛才的那個實現(xiàn)Runable的實現(xiàn)類AsyncCall本身笼沥,其實這里已經(jīng)很清楚了蚪燕,傳入Runable到線程池,線程池肯定會多態(tài)重寫執(zhí)行run方法奔浅,run方法又調用了execute

    @Override protected void execute() {
      boolean signalledCallback = false;
      transmitter.timeoutEnter();
      try {
        Response response = getResponseWithInterceptorChain();
        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 {
          responseCallback.onFailure(RealCall.this, e);
        }
      } catch (Throwable t) {
        cancel();
        if (!signalledCallback) {
          IOException canceledException = new IOException("canceled due to " + t);
          canceledException.addSuppressed(t);
          responseCallback.onFailure(RealCall.this, canceledException);
        }
        throw t;
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

然后攔截器來了馆纳,大家進入RealCall的

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    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, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
  }

可以看到interceptors 這個list中放入了很多攔截器,最新放入的是我們的應用攔截器OkHttpClient.Builder().addInterceptor(TestInterceptor)
這些攔截器的作用我后面會說明
然后就開始了我們的攔截器責任鏈的代碼了(原理也是非常的簡單)
這里創(chuàng)建了一個RealInterceptorChain對象汹桦,然后開始執(zhí)行proceed傳入原始的請求originalRequest

  public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
      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.exchange != null && !this.exchange.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.exchange != 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, transmitter, exchange,
        index + 1, request, call, 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 (exchange != 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;
  }

每次傳入的index+1取集合中的下一個攔截器執(zhí)行intercept鲁驶,然后intercept中又執(zhí)行proceed進行下一次的攔截執(zhí)行
是不是非常非常的簡單!舞骆!
然后開始收尾钥弯,execute中最后finall執(zhí)行了 client.dispatcher().finished(this);

  void finished(AsyncCall call) {
    call.callsPerHost().decrementAndGet();
    finished(runningAsyncCalls, call);
  }

可以看到finished了promoteAndExecute()中放入runningAsyncCalls的對象壹罚,然后結束了
大致就分析到這里

開始分析下剛才說的幾個攔截器

RetryAndFollowUpInterceptor,用來實現(xiàn)連接失敗的重試和重定向寿羞。

BridgeInterceptor,用來修改請求和響應的 header 信息赂蠢。

CacheInterceptor绪穆,用來實現(xiàn)響應緩存。比如獲取到的 Response 帶有 Date虱岂,Expires,Last-Modified第岖,Etag 等 header,表示該 Response 可以緩存一定的時間蔑滓,下次請求就可以不需要發(fā)往服務端,直接拿緩存的键袱。

ConnectInterceptor燎窘,用來打開到服務端的連接。其實是調用了 StreamAllocation 的newStream 方法來打開連接的褐健。建聯(lián)的 TCP 握手,TLS 握手都發(fā)生該階段蚜迅。過了這個階段,和服務端的 socket 連接打通俊抵。

CallServerInterceptor谁不,用來發(fā)起請求并且得到響應。上一個階段已經(jīng)握手成功拍谐,HttpStream 流已經(jīng)打開馏段,所以這個階段把 Request 的請求信息傳入流中轩拨,并且從流中讀取數(shù)據(jù)封裝成 Response 返回院喜。

攔截器分類

  • 應用攔截器喷舀,就是我們addInterceptor添加的攔截器
  • 網(wǎng)絡攔截器 addNetworkInterceptor添加的攔截器

兩個的特點分別是:
應用攔截器
1.不需要關心是否重定向或者失敗重連
2.應用攔截器只會調用一次砍濒,即使數(shù)據(jù)來源于緩存
3.自定義的應用攔截器是第一個開始執(zhí)行的攔截器,所以這句話的意思就是樊卓,應用攔截器可以決定是否執(zhí)行其他的攔截器(如果不想繼續(xù)往下傳遞請求杠河,那么就不調用Chain.proceed()),通過Chain.proceed()
看getResponseWithInterceptorChain()方法結合責任鏈券敌,可以很好的理解這幾個特點
網(wǎng)絡攔截器
1.允許像重定向和重試一樣操作中間響應。
2.網(wǎng)絡發(fā)生短路時不調用緩存響應叹坦。
3.在數(shù)據(jù)被傳遞到網(wǎng)絡時觀察數(shù)據(jù)卑雁。
4.有權獲得裝載請求的連接。

以下關于官網(wǎng)介紹的這張圖區(qū)分應用攔截器和網(wǎng)絡攔截器序厉,通過getResponseWithInterceptorChain方法可以很好的理解,最新執(zhí)行的是應用攔截器


END

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末道盏,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子文捶,更是在濱河造成了極大的恐慌荷逞,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件种远,死亡現(xiàn)場離奇詭異顽耳,居然都是意外死亡,警方通過查閱死者的電腦和手機膝迎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門胰耗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人卖漫,你說我怎么就攤上這事⊙蚴迹” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵速警,是天一觀的道長鸯两。 經(jīng)常有香客問我长豁,道長,這世上最難降的妖魔是什么钝侠? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任酸舍,我火速辦了婚禮,結果婚禮上忽舟,老公的妹妹穿的比我還像新娘淮阐。我一直安慰自己,他們只是感情好泣特,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布状您。 她就那樣靜靜地躺著,像睡著了一般膏孟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上颗搂,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天幕垦,我揣著相機與錄音傅联,去河邊找鬼疚察。 笑死,一個胖子當著我的面吹牛比驻,可吹牛的內容都是我干的岛抄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼掸掸,長吁一口氣:“原來是場噩夢啊……” “哼蹭秋!你這毒婦竟也來了?” 一聲冷哼從身側響起仁讨,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤洞豁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后丈挟,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡长酗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年夺脾,在試婚紗的時候發(fā)現(xiàn)自己被綠了茉继。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡菲茬,死狀恐怖,靈堂內的尸體忽然破棺而出婉弹,到底是詐尸還是另有隱情,我是刑警寧澤氯哮,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布商佛,位于F島的核電站,受9級特大地震影響良姆,放射性物質發(fā)生泄漏。R本人自食惡果不足惜舔痕,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一豹缀、第九天 我趴在偏房一處隱蔽的房頂上張望慨代。 院中可真熱鬧,春花似錦侍匙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至互婿,卻和暖如春辽狈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背刮萌。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人池充。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓旋膳,卻偏偏與公主長得像途事,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子义图,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內容