OkHttp工作流程

  • 分發(fā)器
  • 線程池
  • 攔截器
  • 五大攔截器

分發(fā)器

Dispatcher類杭跪,這個(gè)類的作用用于分發(fā)提交的網(wǎng)絡(luò)任務(wù)通惫,高并發(fā)任務(wù)分發(fā)和線程池排隊(duì)茂翔;

Dispatcher的工作流程:

首先介紹Dispatcher類的一些核心成員

  • ready隊(duì)列和running隊(duì)列:
  /** 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<>();
  • 最大并發(fā)數(shù)和單位域名最大并發(fā):默認(rèn)是64和5,可以自定義
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
分發(fā)器異步請(qǐng)求工作流程

dispatcher.enqueue()

  synchronized void enqueue(AsyncCall call) {
    //  比較
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

promoteCalls()

  private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

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

線程池

OKHttp內(nèi)部的線程池:

executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
  • 核心線程數(shù):0履腋;無(wú)核心線程數(shù)珊燎,不緩存線程
  • 阻塞隊(duì)列:SynchronousQueue;SynchronousQueue的特點(diǎn)是沒(méi)有容量

當(dāng)提交了一些任務(wù)后遵湖,阻塞隊(duì)列不能緩存任務(wù)悔政,會(huì)直接新建線程去執(zhí)行,從而完成了無(wú)等待延旧,高并發(fā)的特點(diǎn)

攔截器

攔截器是OKHttp最核心的部分谋国,當(dāng)一個(gè)網(wǎng)絡(luò)任務(wù)由分發(fā)器提交給線程池后,會(huì)交給攔截器處理迁沫;

攔截器工作流程

與攔截器相關(guān)的核心API:

Chain接口和Intercept接口
public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;

    /**
     * Returns the connection the request will be executed on. This is only available in the chains
     * of network interceptors; for application interceptors this is always null.
     */
    @Nullable Connection connection();

    Call call();

    int connectTimeoutMillis();

    Chain withConnectTimeout(int timeout, TimeUnit unit);

    int readTimeoutMillis();

    Chain withReadTimeout(int timeout, TimeUnit unit);

    int writeTimeoutMillis();

    Chain withWriteTimeout(int timeout, TimeUnit unit);
  }
}

intercept接口是攔截器實(shí)現(xiàn)的接口芦瘾,自帶五個(gè)默認(rèn)的攔截器都實(shí)現(xiàn)了這個(gè)接口,chain接口是責(zé)任鏈模式的接口集畅,攔截器的調(diào)用是使用責(zé)任鏈模式(單一責(zé)任原則)調(diào)用的

chain接口的實(shí)現(xiàn)類:RealInterceptorChain
  private final List<Interceptor> interceptors;
  private final int index;

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {

....

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

....

    return response;
  }

RealInterceptorChain(一下簡(jiǎn)稱realChain)是攔截器責(zé)任鏈模式的主要實(shí)現(xiàn)類近弟,他有兩個(gè)重要的成員變量 interceptors & index集合用來(lái)存放所有需要執(zhí)行的攔截器,index作為下標(biāo)從0開(kāi)始一個(gè)個(gè)的遍歷集合挺智;

getResponseWithInterceptorChain():
    //   獲取網(wǎng)絡(luò)請(qǐng)求的Response
  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors. 添加攔截器到list中
    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));
    // 通過(guò)list創(chuàng)建RealInterceptChain
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
    //  調(diào)用RealChain的proceed方法
    return chain.proceed(originalRequest);
  }

我們從線程池的execute()開(kāi)始分析攔截器的工作流程祷愉,首先調(diào)用getResponseWithInterceptorChain()添加自定義的攔截器和五個(gè)OKHTTP自帶的攔截器,然后創(chuàng)建一個(gè)index為0的realChain逃贝,調(diào)用realChain(index=0)的proceed();
在proceed方法中獲取當(dāng)前index下標(biāo)所指的intercept,創(chuàng)建一個(gè)新的realChain(index+1=2)迫摔,執(zhí)行攔截器的intercept方法沐扳,并傳入新的realChain;

RetryAndFollowUpInterceptor.intercept(realChain(index = 1))
  @Override public Response intercept(Chain chain) throws IOException {
...
      try {
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } 
...
  }

在intercept中調(diào)用了責(zé)任鏈的proceed()句占,此時(shí)的index+1了沪摄,如此往返將責(zé)任鏈遍歷下去,執(zhí)行每一個(gè)攔截器纱烘;

五大攔截器

RetryAndFollowUpInterceptor:重試和重定向

  • retry重試
  • followUp重定向

當(dāng)請(qǐng)求發(fā)生異常時(shí)杨拐,RetryAndFollowUpInterceptor會(huì)根據(jù)異常的條件判斷是否需要重試;

RetryAndFollowUpInterceptor
RetryAndFollowUpInterceptor.intercept
  @Override public Response intercept(Chain chain) throws IOException {
...
    while (true) {
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;
      try {
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {   // 如果是路由異常/io異常就通過(guò)recover()判斷是否需要重試
        // The attempt to connect via a route failed. The request will not have been sent.
        if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
          throw e.getLastConnectException();
        }
        releaseConnection = false;
        continue;
      } catch (IOException e) {  // 如果是路由異常/io異常就通過(guò)recover()判斷是否需要重試
        // An attempt to communicate with a server failed. The request may have been sent.
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
        releaseConnection = false;
        continue;
      } finally {
        // We're throwing an unchecked exception. Release any resources.
        if (releaseConnection) {
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }
    }
...
  }

在while循環(huán)中catch RouteException & IOException 用recover()判斷這個(gè)異常是否需要重試擂啥,如果需要就continue哄陶,不需要?jiǎng)t拋出異常結(jié)束循環(huán);

RetryAndFollowUpInterceptor.recover()
private boolean recover(IOException e, StreamAllocation streamAllocation,
      boolean requestSendStarted, Request userRequest) {
    streamAllocation.streamFailed(e);

    // The application layer has forbidden retries.      client配置是否允許重試()默認(rèn)允許
    if (!client.retryOnConnectionFailure()) return false;

    // We can't send the request body again.        // http2.0才可能不通過(guò)
    if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;

    // This exception is fatal.      判斷是否是重大異常
    if (!isRecoverable(e, requestSendStarted)) return false;

    // No more routes to attempt.  當(dāng)前域名只有一個(gè)ip
    if (!streamAllocation.hasMoreRoutes()) return false;

    // For failure recovery, use the same route selector with a new connection.
    return true;
  }
重大異常的判斷
 private boolean isRecoverable(IOException e, boolean requestSendStarted) {
    // If there was a protocol problem, don't recover.
    if (e instanceof ProtocolException) {      // 協(xié)議異常 哺壶,不重試
      return false;
    }

    // If there was an interruption don't recover, but if there was a timeout connecting to a route
    // we should try the next route (if there is one).
    if (e instanceof InterruptedIOException) {    
      return e instanceof SocketTimeoutException && !requestSendStarted;     //  是Socket超時(shí)異常直接返回true重試屋吨,不是繼續(xù)往下判斷
    }

    // Look for known client-side or negotiation errors that are unlikely to be fixed by trying
    // again with a different route.
    if (e instanceof SSLHandshakeException) {
      // If the problem was a CertificateException from the X509TrustManager,
      // do not retry.
      if (e.getCause() instanceof CertificateException) {        //  證書(shū)異常蜒谤,不重試
        return false;
      }
    }
    if (e instanceof SSLPeerUnverifiedException) {
      // e.g. a certificate pinning error.
      return false;
    }

    // An example of one we might want to retry with a different route is a problem connecting to a
    // proxy and would manifest as a standard IOException. Unless it is one we know we should not
    // retry, we return true and try a new route.
    return true;
  }

總的來(lái)說(shuō),如果是服務(wù)端的問(wèn)題(證書(shū)異常至扰,協(xié)議異常)就不會(huì)重試鳍徽,因?yàn)樵僦卦囈彩且粯咏Y(jié)果,而如果是網(wǎng)絡(luò)波動(dòng)超時(shí)異常敢课,就會(huì)觸發(fā)攔截器重試(需要滿足其他的要求)阶祭;

當(dāng)重試環(huán)節(jié)結(jié)束后(即while循環(huán)跳出后),會(huì)進(jìn)入重定向階段followUp直秆,非重點(diǎn)濒募,了解就好

重定向:

客戶端向服務(wù)器發(fā)送一個(gè)請(qǐng)求,服務(wù)端返回一個(gè)url給客戶端切厘,客戶端訪問(wèn)這個(gè)新的url萨咳,叫做重定向,這個(gè)url放在響應(yīng)頭的Location中疫稿;

    while (true) {
...
      try {
        //  循環(huán)執(zhí)行新的重定向
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } 

      //  通過(guò)響應(yīng)碼判定是否有重定向
      Request followUp = followUpRequest(response, streamAllocation.route());
      // 如果沒(méi)有直接返回response
      if (followUp == null) {
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }
      //  重定向次數(shù)限制
      if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }
      //  重定向
      request = followUp;
      priorResponse = response;
...
    }

BridgeInterceptor:橋接

最簡(jiǎn)單的攔截器培他,主要的功能:

  • 補(bǔ)充請(qǐng)求頭
  • 設(shè)置和讀取Cookie
  • 設(shè)置gzip壓縮和解壓(Accept-Encoding/Content-Encoding)

CacheInterceptor:緩存

緩存攔截器內(nèi)部有一個(gè)緩存策略類,他有兩個(gè)重要的成員變量:

  /** The request to send on the network, or null if this call doesn't use the network. */
  public final @Nullable Request networkRequest;

  /** The cached response to return or validate; or null if this call doesn't use a cache. */
  public final @Nullable Response cacheResponse;

networkRequest用來(lái)表示這個(gè)請(qǐng)求的網(wǎng)絡(luò)訪問(wèn)請(qǐng)求細(xì)節(jié)(Request)遗座,cacheResponse用來(lái)表示這個(gè)請(qǐng)求的緩存(如果沒(méi)有為null)舀凛;

1 cache == null && request == null

緩存為空,請(qǐng)求為空途蒋,fail猛遍,直接返回一個(gè)504空的response

2 cache == null && request != null

緩存為空,請(qǐng)求不空号坡,訪問(wèn)服務(wù)器

3 cache != null && request == null

緩存不為空懊烤,請(qǐng)求為空,使用緩存

4 cache != null && request != null

緩存不為空宽堆,請(qǐng)求不為空腌紧,訪問(wèn)服務(wù)器,對(duì)比緩存(響應(yīng)碼304)

  @Override public Response intercept(Chain chain) throws IOException {
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
    
    //   通過(guò) CacheStrategy  獲取 cache 和 request
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;

    if (cache != null) {
      cache.trackResponse(strategy);
    }

    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    //  第一種情況畜隶,緩存為空壁肋,請(qǐng)求為空
    // If we're forbidden from using the network and the cache is insufficient, fail.
    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();
    }

    //  request為空,直接使用緩存籽慢, (緩存為空則return null)
    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    //   request不為空浸遗, 使用責(zé)任鏈發(fā)起請(qǐng)求,獲取networkResponse 
    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());
      }
    }
  
     //  第四種情況箱亿,request 不為空跛锌, 緩存也不為空, 對(duì)比緩存
    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      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());
      }
    }

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

緩存策略的實(shí)現(xiàn):

我們先看一下緩存攔截器的緩存策略是如何使用;

    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;

使用了內(nèi)部類的get方法獲取CacheStrategy 對(duì)象届惋;

在緩存策略的內(nèi)部有一個(gè)Factory內(nèi)部類:在構(gòu)造Factory時(shí)Factory的成員變量用來(lái)存儲(chǔ)response的一些基本信息備用Date Expires Last-Modified ETag Age

  public static class Factory {
    final long nowMillis;
    final Request request;
    final Response cacheResponse;

    /** The server's time when the cached response was served, if known. */
    private Date servedDate;
    private String servedDateString;

    /** The last modified date of the cached response, if known. */
    private Date lastModified;
    private String lastModifiedString;

    /**
     * The expiration date of the cached response, if known. If both this field and the max age are
     * set, the max age is preferred.
     */
    private Date expires;

    /**
     * Extension header set by OkHttp specifying the timestamp when the cached HTTP request was
     * first initiated.
     */
    private long sentRequestMillis;

    /**
     * Extension header set by OkHttp specifying the timestamp when the cached HTTP response was
     * first received.
     */
    private long receivedResponseMillis;

    /** Etag of the cached response. */
    private String etag;

    /** Age of the cached response. */
    private int ageSeconds = -1;

    public Factory(long nowMillis, Request request, Response cacheResponse) {
      this.nowMillis = nowMillis;
      this.request = request;
      this.cacheResponse = cacheResponse;

      if (cacheResponse != null) {
        this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
        this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
        Headers headers = cacheResponse.headers();
        for (int i = 0, size = headers.size(); i < size; i++) {
          String fieldName = headers.name(i);
          String value = headers.value(i);
          if ("Date".equalsIgnoreCase(fieldName)) {
            servedDate = HttpDate.parse(value);
            servedDateString = value;
          } else if ("Expires".equalsIgnoreCase(fieldName)) {
            expires = HttpDate.parse(value);
          } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
            lastModified = HttpDate.parse(value);
            lastModifiedString = value;
          } else if ("ETag".equalsIgnoreCase(fieldName)) {
            etag = value;
          } else if ("Age".equalsIgnoreCase(fieldName)) {
            ageSeconds = HttpHeaders.parseSeconds(value, -1);
          }
        }
      }
    }
  }

get():

    public CacheStrategy get() {
      CacheStrategy candidate = getCandidate();

      if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
        // We're forbidden from using the network and the cache is insufficient.
        return new CacheStrategy(null, null);
      }

      return candidate;
    }

getCandidate():

    private CacheStrategy getCandidate() {
      // No cached response.
      if (cacheResponse == null) {
        return new CacheStrategy(request, null);
      }

      // Drop the cached response if it's missing a required handshake.
      if (request.isHttps() && cacheResponse.handshake() == null) {
        return new CacheStrategy(request, null);
      }

      // If this response shouldn't have been stored, it should never be used
      // as a response source. This check should be redundant as long as the
      // persistence store is well-behaved and the rules are constant.
      if (!isCacheable(cacheResponse, request)) {
        return new CacheStrategy(request, null);
      }

      CacheControl requestCaching = request.cacheControl();
      if (requestCaching.noCache() || hasConditions(request)) {
        return new CacheStrategy(request, null);
      }

      CacheControl responseCaching = cacheResponse.cacheControl();
      if (responseCaching.immutable()) {
        return new CacheStrategy(null, cacheResponse);
      }

      long ageMillis = cacheResponseAge();
      long freshMillis = computeFreshnessLifetime();

      if (requestCaching.maxAgeSeconds() != -1) {
        freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
      }

      long minFreshMillis = 0;
      if (requestCaching.minFreshSeconds() != -1) {
        minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
      }

      long maxStaleMillis = 0;
      if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
      }

      if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
        Response.Builder builder = cacheResponse.newBuilder();
        if (ageMillis + minFreshMillis >= freshMillis) {
          builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
        }
        long oneDayMillis = 24 * 60 * 60 * 1000L;
        if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
          builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
        }
        return new CacheStrategy(null, builder.build());
      }

      // Find a condition to add to the request. If the condition is satisfied, the response body
      // will not be transmitted.
      String conditionName;
      String conditionValue;
      if (etag != null) {
        conditionName = "If-None-Match";
        conditionValue = etag;
      } else if (lastModified != null) {
        conditionName = "If-Modified-Since";
        conditionValue = lastModifiedString;
      } else if (servedDate != null) {
        conditionName = "If-Modified-Since";
        conditionValue = servedDateString;
      } else {
        return new CacheStrategy(request, null); // No condition! Make a regular request.
      }

      Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
      Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

      Request conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build();
      return new CacheStrategy(conditionalRequest, cacheResponse);
    }

getCandidate()是獲取CacheStrategy 類的核心代碼:

緩存策略的實(shí)現(xiàn)流程(設(shè)置`networkRequest`與`cacheResponse`的組合)

ConnectInterceptor 連接

連接攔截器的作用是建立Socket連接和Socket連接池的復(fù)用;

相關(guān)類 作用
StreamAllocation 協(xié)調(diào)請(qǐng)求察净、連接與數(shù)據(jù)流三者之間的關(guān)系驾茴,它負(fù)責(zé)為一次請(qǐng)求尋找連接,然后獲得流來(lái)實(shí)現(xiàn)網(wǎng)絡(luò)通信氢卡,簡(jiǎn)單來(lái)說(shuō)就是維護(hù)連接锈至,在重定向攔截器創(chuàng)建,連接攔截器使用
RealConnection 封裝了Socket與一個(gè)Socket連接池译秦,建立與服務(wù)器的Socket連接
ConnectionPool Socket連接池峡捡,復(fù)用Socket連接,關(guān)閉一定時(shí)間內(nèi)空閑的連接
Socket連接的建立:
三種連接建立情況
  • 無(wú)代理:直接向Socket指定目標(biāo)服務(wù)器
  • Socket代理:向Socket傳入Proxy對(duì)象筑悴,Proxy指定代理服務(wù)器们拙,Socket連接目標(biāo)服務(wù)器
  • HTTP代理:Socket連接代理服務(wù)器,請(qǐng)求報(bào)文請(qǐng)求行的第二個(gè)參數(shù)加上目標(biāo)服務(wù)器域名前置
  • HTTPS代理:Socket連接代理服務(wù)器阁吝,請(qǐng)求報(bào)文需要先請(qǐng)求CONNECT砚婆,服務(wù)器返回connect成功,再包裝一層SSLSocket與服務(wù)器通信
連接攔截器Socket建立過(guò)程

連接攔截器類并沒(méi)有多少代碼突勇,大部分的邏輯都被封裝在了RealConnection和StreamAllocation中装盯,從connect()開(kāi)始,如果是HTTPS請(qǐng)求甲馋,會(huì)創(chuàng)建隧道代理埂奈,隧道代理的功能代理服務(wù)器可以發(fā)出身份質(zhì)疑,關(guān)閉連接定躏,保證HTTPS的安全性

HTTPS代理請(qǐng)求CONNECT

CallServerInterceptor 請(qǐng)求服務(wù)

將request拼成請(qǐng)求報(bào)文账磺,將服務(wù)器的響應(yīng)報(bào)文轉(zhuǎn)化成response

一般出現(xiàn)于上傳大容量請(qǐng)求體或者需要驗(yàn)證。代表了先詢問(wèn)服務(wù)器是否原因接收發(fā)送請(qǐng)求體數(shù)據(jù)痊远,
OkHttp的做法:
如果服務(wù)器允許則返回100垮抗,客戶端繼續(xù)發(fā)送請(qǐng)求體;
如果服務(wù)器不允許則直接返回給用戶碧聪。
同時(shí)服務(wù)器也可能會(huì)忽略此請(qǐng)求頭冒版,一直無(wú)法讀取應(yīng)答,此時(shí)拋出超時(shí)異常矾削。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末壤玫,一起剝皮案震驚了整個(gè)濱河市豁护,隨后出現(xiàn)的幾起案子哼凯,更是在濱河造成了極大的恐慌,老刑警劉巖楚里,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件断部,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡班缎,警方通過(guò)查閱死者的電腦和手機(jī)蝴光,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)她渴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蔑祟,你說(shuō)我怎么就攤上這事趁耗。” “怎么了疆虚?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵苛败,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我径簿,道長(zhǎng)罢屈,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任篇亭,我火速辦了婚禮缠捌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘译蒂。我一直安慰自己曼月,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布蹂随。 她就那樣靜靜地躺著十嘿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪岳锁。 梳的紋絲不亂的頭發(fā)上绩衷,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音激率,去河邊找鬼咳燕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛乒躺,可吹牛的內(nèi)容都是我干的招盲。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼嘉冒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼曹货!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起讳推,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤顶籽,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后银觅,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體礼饱,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了镊绪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匀伏。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蝴韭,靈堂內(nèi)的尸體忽然破棺而出够颠,到底是詐尸還是另有隱情,我是刑警寧澤榄鉴,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布摧找,位于F島的核電站,受9級(jí)特大地震影響牢硅,放射性物質(zhì)發(fā)生泄漏蹬耘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一减余、第九天 我趴在偏房一處隱蔽的房頂上張望综苔。 院中可真熱鬧,春花似錦位岔、人聲如沸如筛。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)杨刨。三九已至,卻和暖如春擦剑,著一層夾襖步出監(jiān)牢的瞬間妖胀,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工惠勒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赚抡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓纠屋,卻偏偏與公主長(zhǎng)得像涂臣,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子售担,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 工作分析: 1赁遗、創(chuàng)建Call 同步: 2、將Call直接添加到Dispatcher的runningAsyncCal...
    Jcccccccccc閱讀 808評(píng)論 0 51
  • 前言 用OkHttp很久了族铆,也看了很多人寫(xiě)的源碼分析岩四,在這里結(jié)合自己的感悟,記錄一下對(duì)OkHttp源碼理解的幾點(diǎn)心...
    Java小鋪閱讀 1,494評(píng)論 0 13
  • 上一篇我們說(shuō)完了dispatcher分發(fā)器骑素,我們知道了請(qǐng)求任務(wù)是如何分發(fā)出去的炫乓,那響應(yīng)是如何獲取到的呢?再看一下R...
    Thisislife閱讀 860評(píng)論 0 1
  • 介紹 OkHttp是一個(gè)用于Android和Java應(yīng)用程序的HTTP客戶端献丑。相關(guān)參數(shù)和配置都是用Builder模...
    m1Ku閱讀 1,344評(píng)論 0 1
  • 在謊言風(fēng)暴襲擊我的腦海時(shí)末捣,我才知道是“現(xiàn)實(shí)”光臨了。 遙遠(yuǎn)的鐘聲不停地倒退著创橄,時(shí)間里被滲透入許多的未知函數(shù)箩做,反比例...
    浣久閱讀 222評(píng)論 0 0