OKHttp源碼分析

OKHttp源碼解讀

本次分享主要目標

  • 1.大概流程解讀,同步流程,異步流程
  • 2.Interceptor簡讀

一、OKHttp的簡單使用

    private String get(String url) throws IOException {

        Request request = new Request.Builder()
                .url(url)
                .build();
        Response response = client.newCall(request).execute();

        return response.body().string();
    }
    

 
private void useOkHttp() throws IOException {
        //1.創(chuàng)建OkHttpClient對象
        OkHttpClient okhttp = new OkHttpClient.Builder()
                              .readTimeout(15, TimeUnit.SECONDS)
                              .writeTimeout(15, TimeUnit.SECONDS)
                              .build();
        //2.創(chuàng)建請求對象Request
        Request request = new Request.Builder()
                              .url("url")
                              .addHeader("key","value")                     
                              .get()//設置請求方式是get
                              .build();
 
 
        Call call = okhttp.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                ResponseBody body = response.body();
                String string = body.string();
            }
        });
    }

以上是OKHttp很簡單的兩個用法,一個是同步的,一個是異步的。

二、回顧Retrofit源碼與OKhttp的初始化

  • Rerofit的初始化
  • ServiceMethon的初始化
  • 調用OKHttp
  • 裝飾器 adapt的使用

OKHttp與Retrofit都是同一幫人寫的,所以寫法是類似的。

Builder重度使用,OKHttpCient與Retrofit的作用類似。

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {


  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 SSLSocketFactory sslSocketFactory;
  final 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 callTimeout;
  final int connectTimeout;
  final int readTimeout;
  final int writeTimeout;
  final int pingInterval;

請求過程中用到的各種設置都可以從這里拿到,Retrofit的next方法是不是還有印象,后面的各種方法都是有retrofit的參數(shù)傳進去,獲取初始化參數(shù)。

三、執(zhí)行過程簡析

 Call call = okhttp.newCall(request);
 call.enqueue
 call.execute

這個是先拿到一個request,然后excute或者enqueue

1.我們先分析同步的代碼流程


@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.timeoutEnter();
    transmitter.callStart();
    try {
      client.dispatcher().executed(this);
      return getResponseWithInterceptorChain();
    } finally {
      client.dispatcher().finished(this);
    }
  }

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

  client.dispatcher().executed(this);
  
  /** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }
  
  /** Used by {@code Call#execute} to signal completion. */
  void finished(RealCall call) {
    finished(runningSyncCalls, call);
  }
  

我們總結總結同步的流程

  1. RealCall.excute
  2. client.dispatcher().executed
  3. RealCall.getResponseWithInterceptorChain
  4. client.dispatcher().finished;

同步執(zhí)行的時候,runningSyncCalls 進行了添加刪除用作記錄,目前沒有數(shù)量控制,也沒有線程調度什么。

看到這個Call,有沒有想到Retrofit里的Call?

  • Retrofit Call接口,OKHttpCall
  • OKHttp里也是有個Call接口,RealCall,作為他的實現(xiàn)類,還有一個AsyncCall,但是這個不是Call的實現(xiàn)類,復制RealCall做異步使用的。

2.異步流程解析

RealCall.enqueue

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

Dispatcher.enqueue

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

Dispatcher.promoteAndExecute

private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    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();
        asyncCall.callsPerHost().incrementAndGet();
        executableCalls.add(asyncCall);
        runningAsyncCalls.add(asyncCall);
      }
      isRunning = runningCallsCount() > 0;
    }

    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      asyncCall.executeOn(executorService());
    }

    return isRunning;
  }

AsyncCall.executeOn

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!
        }
      }
    }

AsyncCall.execute->RealCall.getResponseWithInterceptorChain()->AsyncCall.responseCallback

  @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);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }

我們總結總結異步的流程

  1. RealCall.enqueue
  2. Dispatcher.enqueue
  3. Dispatcher.promoteAndExecute
  4. AsyncCall.executeOn
  5. AsyncCall.execute
  6. RealCall.getResponseWithInterceptorChain
  7. AsyncCall.responseCallback

從流程上來看的話,不管是同步異步實際請求都是調用

RealCall.getResponseWithInterceptorChain

只是異步請求Dispatcher 做了線程管理,這塊內容下次再做分享

三、Interceptor簡析

getResponseWithInterceptorChain的實現(xiàn)

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

這里的核心是

  1. interceptors.add
  2. Response response = chain.proceed(originalRequest);

添加攔截器,執(zhí)行攔截器。


攔截器的執(zhí)行采用的是遞歸原理,理解了遞歸原理的話,攔截器這塊就沒什么內容了,就是解讀各個攔截器的實現(xiàn)。


我們看下一個簡單的遞歸例子

function Recursion(depth) {
    console.log('抱著');
    if (!depth) {
        console.log('我的小鯉魚')
    } else {
        Recursion(--depth);  // 遞歸調用
    }
    console.log('的我');
}

console.log('嚇得我抱起了');
Recursion(2)

日志打印出來

  1. 嚇得我抱起了
  2. 抱著
  3. 抱著
  4. 抱著
  5. 我的小鯉魚
  6. 的我
  7. 的我
  8. 的我

遞歸的特點

  • 遞歸結束的條件
  • 執(zhí)行順序,前置的按照順序從上往下執(zhí)行,結果是從下往上

這里是不是可以理解,每個interceptor都能拿到網絡請求后的response結果了,因為最后一個拿到的response,然后從下往上每個都能拿到了。

avatar

Interceptor的原理明白了嗎?

也就是開始被執(zhí)行的類request的信息是做少的,越執(zhí)行reqeust信息越多,response,則是最后被執(zhí)行的的是最原始的,然后被修改添加的越多。

我們再來學習下Interceptor的1個知識點

  1. interceptor與networkInterceptor的區(qū)別
  2. RetryAndFollowUpInterceptor代碼解讀

通過 addInterceptor() 方法添加的攔截器是放在最前面的。而通過 addNetworkInterceptor() 方法添加的網絡攔截器,則是在非 WebSocket 請求時,添加在 ConnectInterceptor 和 CallServerInterceptor 之間的。

addInterceptor(應用攔截器):

  1. 不需要擔心中間過程的響應,如重定向和重試.
  2. 總是只調用一次,即使HTTP響應是從緩存中獲取.
  3. 觀察應用程序的初衷. 不關心OkHttp注入的頭信息如: If-None-Match.
  4. 允許短路而不調用 Chain.proceed(),即中止調用.
  5. 允許重試,使 Chain.proceed()調用多次.
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    addNetworkInterceptor(網絡攔截器):
  6. 能夠操作中間過程的響應,如重定向和重試.
  7. 當網絡短路而返回緩存響應時不被調用.
  8. 只觀察在網絡上傳輸?shù)臄?shù)據(jù).
  9. 攜帶請求來訪問連接.

RetryAndFollowUpInterceptor

  @Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Transmitter transmitter = realChain.transmitter();

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      transmitter.prepareToConnect(request);

      if (transmitter.isCanceled()) {
        throw new IOException("Canceled");
      }

      Response response;
      boolean success = false;
      try {
        response = realChain.proceed(request, transmitter, null);
        success = true;
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        if (!recover(e.getLastConnectException(), transmitter, false, request)) {
          throw e.getFirstConnectException();
        }
        continue;
      } catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, transmitter, requestSendStarted, request)) throw e;
        continue;
      } finally {
        // The network call threw an exception. Release any resources.
        if (!success) {
          transmitter.exchangeDoneDueToException();
        }
      }

      // Attach the prior response if it exists. Such responses never have a body.
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

      Exchange exchange = Internal.instance.exchange(response);
      Route route = exchange != null ? exchange.connection().route() : null;
      Request followUp = followUpRequest(response, route);

      if (followUp == null) {
        if (exchange != null && exchange.isDuplex()) {
          transmitter.timeoutEarlyExit();
        }
        return response;
      }

      RequestBody followUpBody = followUp.body();
      if (followUpBody != null && followUpBody.isOneShot()) {
        return response;
      }

      closeQuietly(response.body());
      if (transmitter.hasExchange()) {
        exchange.detachWithViolence();
      }

      if (++followUpCount > MAX_FOLLOW_UPS) {
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      request = followUp;
      priorResponse = response;
    }
  }

這里有個比較關關鍵的一點

  while (true) {
      transmitter.prepareToConnect(request);

      if (transmitter.isCanceled()) {
        throw new IOException("Canceled");
      }

      Response response;
      boolean success = false;
      try {
        response = realChain.proceed(request, transmitter, null);
        

while(true) 這里是跟其他interceptor不一樣的地方,對于重定向大家有沒有了解?

退出while的條件

     if (followUp == null) {
        if (exchange != null && exchange.isDuplex()) {
          transmitter.timeoutEarlyExit();
        }
        return response;
      }

      RequestBody followUpBody = followUp.body();
      if (followUpBody != null && followUpBody.isOneShot()) {
        return response;
      }
      
      if (++followUpCount > MAX_FOLLOW_UPS) {
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }
      

一個是返回后沒有再重定向了,另外一個則是重定向次數(shù)超過了閾值

注意點

重定向執(zhí)行的時候,是沒有再調用Interceptor,但是netInterceptor還是被調用的。

本次分享到此結束,下次詳解,dispatch與其他interceptor

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 那么我今天給大家簡單地講一下Okhttp這款網絡框架及其原理。它是如何請求數(shù)據(jù),如何響應數(shù)據(jù)的 有什么優(yōu)點?它的應...
    卓而不群_0137閱讀 348評論 0 1
  • OkHttp源碼分析 在現(xiàn)在的Android開發(fā)中,請求網絡獲取數(shù)據(jù)基本上成了我們的標配。在早期的Android開...
    BlackFlag閱讀 414評論 0 5
  • 版本號:3.13.1 一.基本使用 Call可以理解為Request和Response之間的橋梁,Http請求過程...
    慕涵盛華閱讀 1,197評論 0 8
  • 1、說明 經翻譯之后的結果如下HTTP是現(xiàn)代應用網絡的方式。這是我們交換數(shù)據(jù)和媒體的方式。高效地執(zhí)行HTTP可以使...
    Kevin_Lv閱讀 484評論 0 1
  • 主目錄見:Android高級進階知識(這是總目錄索引)?OkHttp的知識點實在是不少,優(yōu)秀的思想也有很多,這里只...
    ZJ_Rocky閱讀 2,401評論 2 6

友情鏈接更多精彩內容