Android主流三方庫原始碼分析(一、深入理解OKHttp原始碼)

jsonchao發表於2020-01-13

前言

成為一名優秀的Android開發,需要一份完備的知識體系,在這裡,讓我們一起成長為自己所想的那樣~。

更好的閱讀體驗請跳轉至個人部落格

前兩篇我們詳細分析了View的核心原始碼—Android的觸控事件傳遞機制Android View的繪製流程,從這篇開始,筆者接下來將會陪大家深入分析目前Android中大部分的主流開源框架原始碼,從而能夠讓我們真正地去理解這些優秀開源框架背後的思想,真真切切地提升自己的內功。目前,這一系列的分析順序如下:

主流三方庫:
網路:
1、OKHttp
2、Retrofit
圖片:
3、Glide
資料庫:
4、GreenDao
響應式程式設計:
5、RxJava
記憶體洩露:
6、LeakCanary
依賴注入:
7、ButterKnife
8、Dagger2
事件匯流排:
9、EventBus
複製程式碼

總結成思維導圖,就是這樣的:

image

這一篇將會對Android的三方網路庫OKHttp原始碼進行深入的分析,在閱讀過OKHttp原始碼和大量其它優秀的OKHttp原始碼分析文章後,我發現只要搞懂以下這三塊,就能證明你對OKHttp有了一個深入的瞭解

  • OKHttp請求流程
  • 網路請求快取處理
  • 連線池

首先,補充一點網路知識:

一些常用的狀態碼

  • 100~199:指示資訊,表示請求已接收,繼續處理
  • 200~299:請求成功,表示請求已被成功接收、理解
  • 300~399:重定向,要完成請求必須進行更進一步的操作
  • 400~499:客戶端錯誤,請求有語法錯誤或請求無法實現
  • 500~599:伺服器端錯誤,伺服器未能實現合法的請求

一、OKHttp請求流程

OKHttp內部的大致請求流程圖如下所示:

image

如下為使用OKHttp進行Get請求的步驟:

//1.新建OKHttpClient客戶端
OkHttpClient client = new OkHttpClient();
//新建一個Request物件
Request request = new Request.Builder()
        .url(url)
        .build();
//2.Response為OKHttp中的響應
Response response = client.newCall(request).execute();
複製程式碼

1.新建OKHttpClient客戶端

OkHttpClient client = new OkHttpClient();

public OkHttpClient() {
    this(new Builder());
}

OkHttpClient(Builder builder) {
    ....
}
複製程式碼

可以看到,OkHttpClient使用了建造者模式,Builder裡面的可配置引數如下:

public static final class Builder {
    Dispatcher dispatcher;// 分發器
    @Nullable Proxy proxy;
    List<Protocol> protocols;
    List<ConnectionSpec> connectionSpecs;// 傳輸層版本和連線協議
    final List<Interceptor> interceptors = new ArrayList<>();// 攔截器
    final List<Interceptor> networkInterceptors = new ArrayList<>();
    EventListener.Factory eventListenerFactory;
    ProxySelector proxySelector;
    CookieJar cookieJar;
    @Nullable Cache cache;
    @Nullable InternalCache internalCache;// 內部快取
    SocketFactory socketFactory;
    @Nullable SSLSocketFactory sslSocketFactory;// 安全套接層socket 工廠,用於HTTPS
    @Nullable CertificateChainCleaner certificateChainCleaner;// 驗證確認響應證照 適用 HTTPS 請求連線的主機名。
    HostnameVerifier hostnameVerifier;// 驗證確認響應證照 適用 HTTPS 請求連線的主機名。  
    CertificatePinner certificatePinner;// 證照鎖定,使用CertificatePinner來約束哪些認證機構被信任。
    Authenticator proxyAuthenticator;// 代理身份驗證
    Authenticator authenticator;// 身份驗證
    ConnectionPool connectionPool;// 連線池
    Dns dns;
    boolean followSslRedirects; // 安全套接層重定向
    boolean followRedirects;// 本地重定向
    boolean retryOnConnectionFailure;// 重試連線失敗
    int callTimeout;
    int connectTimeout;
    int readTimeout;
    int writeTimeout;
    int pingInterval;

    // 這裡是預設配置的構建引數
    public Builder() {
        dispatcher = new Dispatcher();
        protocols = DEFAULT_PROTOCOLS;
        connectionSpecs = DEFAULT_CONNECTION_SPECS;
        ...
    }

    // 這裡傳入自己配置的構建引數
    Builder(OkHttpClient okHttpClient) {
        this.dispatcher = okHttpClient.dispatcher;
        this.proxy = okHttpClient.proxy;
        this.protocols = okHttpClient.protocols;
        this.connectionSpecs = okHttpClient.connectionSpecs;
        this.interceptors.addAll(okHttpClient.interceptors);
        this.networkInterceptors.addAll(okHttpClient.networkInterceptors);
        ...
    }
複製程式碼

2.同步請求流程

Response response = client.newCall(request).execute();

/**
* Prepares the {@code request} to be executed at   some point in the future.
*/
@Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
}

// RealCall為真正的請求執行者
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.eventListener = client.eventListenerFactory().create(call);
    return call;
}

@Override public Response execute() throws IOException {
    synchronized (this) {
        // 每個Call只能執行一次
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
    }
    captureCallStackTrace();
    timeout.enter();
    eventListener.callStart(this);
    try {
        // 通知dispatcher已經進入執行狀態
        client.dispatcher().executed(this);
        // 通過一系列的攔截器請求處理和響應處理得到最終的返回結果
        Response result = getResponseWithInterceptorChain();
        if (result == null) throw new IOException("Canceled");
        return result;
    } catch (IOException e) {
        e = timeoutExit(e);
        eventListener.callFailed(this, e);
        throw e;
    } finally {
        // 通知 dispatcher 自己已經執行完畢
        client.dispatcher().finished(this);
    }
}

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    // 在配置 OkHttpClient 時設定的 interceptors;
    interceptors.addAll(client.interceptors());
    // 負責失敗重試以及重定向
    interceptors.add(retryAndFollowUpInterceptor);
    // 請求時,對必要的Header進行一些新增,接收響應時,移除必要的Header
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    // 負責讀取快取直接返回、更新快取
    interceptors.add(new CacheInterceptor(client.internalCache()));
    // 負責和伺服器建立連線
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
        // 配置 OkHttpClient 時設定的 networkInterceptors
        interceptors.addAll(client.networkInterceptors());
    }
    // 負責向伺服器傳送請求資料、從伺服器讀取響應資料
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    // 使用責任鏈模式開啟鏈式呼叫
    return chain.proceed(originalRequest);
}

// StreamAllocation 物件,它相當於一個管理類,維護了伺服器連線、併發流
// 和請求之間的關係,該類還會初始化一個 Socket 連線物件,獲取輸入/輸出流物件。
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
  RealConnection connection) throws IOException {
    ...

    // Call the next interceptor in the chain.
    // 例項化下一個攔截器對應的RealIterceptorChain物件
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    // 得到當前的攔截器
    Interceptor interceptor = interceptors.get(index);
    // 呼叫當前攔截器的intercept()方法,並將下一個攔截器的RealIterceptorChain物件傳遞下去,最後得到響應
    Response response = interceptor.intercept(next);

    ...
    
    return response;
}
複製程式碼

3.非同步請求的流程

Request request = new Request.Builder()
    .url("http://publicobject.com/helloworld.txt")
    .build();

client.newCall(request).enqueue(new Callback() {
    @Override 
    public void onFailure(Call call, IOException e) {
      e.printStackTrace();
    }

    @Override 
    public void onResponse(Call call, Response response) throws IOException {
        ...
    }
    
void enqueue(AsyncCall call) {
    synchronized (this) {
        readyAsyncCalls.add(call);
    }
    promoteAndExecute();
}

// 正在準備中的非同步請求佇列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

// 執行中的非同步請求
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

// 同步請求
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

// Promotes eligible calls from {@link #readyAsyncCalls} to {@link #runningAsyncCalls} and runs
// them on the executor service. Must not be called with synchronization because executing calls
// can call into user code.
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();

        // 如果其中的runningAsynCalls不滿,且call佔用的host小於最大數量,則將call加入到runningAsyncCalls中執行,
        // 同時利用執行緒池執行call;否者將call加入到readyAsyncCalls中。
        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
        if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.

        i.remove();
        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;
}
複製程式碼

最後,我們在看看AsynCall的程式碼。

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

    String host() {
      return originalRequest.url().host();
    }

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }

    /**
     * Attempt to enqueue this async call on {@code    executorService}. This will attempt to clean up
     * if the executor has been shut down by reporting    the call as failed.
     */
    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);
        eventListener.callFailed(RealCall.this, ioException);
        responseCallback.onFailure(RealCall.this, ioException);
      } finally {
        if (!success) {
          client.dispatcher().finished(this); // This call is no longer running!
        }
      }
    }

    @Override protected void execute() {
      boolean signalledCallback = false;
      timeout.enter();
      try {
        // 跟同步執行一樣,最後都會呼叫到這裡
        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) {
        e = timeoutExit(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 {
        client.dispatcher().finished(this);
      }
    }
}
複製程式碼

從上面的原始碼可以知道,攔截鏈的處理OKHttp幫我們預設做了五步攔截處理,其中RetryAndFollowUpInterceptor、BridgeInterceptor、CallServerInterceptor內部的原始碼很簡潔易懂,此處不再多說,下面將對OKHttp最為核心的兩部分:快取處理和連線處理(連線池)進行講解。

二、網路請求快取處理之CacheInterceptor

@Override public Response intercept(Chain chain) throws IOException {
    // 根據request得到cache中快取的response
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();

    // 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.
    }

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

    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    Response networkResponse = null;
    try {
        // 呼叫下一個攔截器,決定從網路上來得到response
        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());
        }
    }

    // If we have a cache response too, then we're doing a conditional get.
    // 如果本地已經存在cacheResponse,那麼讓它和網路得到的networkResponse做比較,決定是否來更新快取的cacheResponse
    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.
        // 快取未經快取過的response
        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;
}
複製程式碼

快取攔截器會根據請求的資訊和快取的響應的資訊來判斷是否存在快取可用,如果有可以使用的快取,那麼就返回該快取給使用者,否則就繼續使用責任鏈模式來從伺服器中獲取響應。當獲取到響應的時候,又會把響應快取到磁碟上面。

三、ConnectInterceptor之連線池

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

    // We need the network to satisfy this request.     Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    // HttpCodec是對 HTTP 協議操作的抽象,有兩個實現:Http1Codec和Http2Codec,顧名思義,它們分別對應 HTTP/1.1 和 HTTP/2 版本的實現。在這個方法的內部實現連線池的複用處理
    HttpCodec httpCodec = streamAllocation.newStream(client, chain,     doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
}



// Returns a connection to host a new stream. This // prefers the existing connection if it exists,
// then the pool, finally building a new connection.
// 呼叫 streamAllocation 的 newStream() 方法的時候,最終會經過一系列
// 的判斷到達 StreamAllocation 中的 findConnection() 方法
private RealConnection findConnection(int   connectTimeout, int readTimeout, int writeTimeout,
    int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
      ...
    
      // Attempt to use an already-allocated connection. We need to be careful here because our
      // already-allocated connection may have been restricted from creating new streams.
      // 嘗試使用已分配的連線,已經分配的連線可能已經被限制建立新的流
      releasedConnection = this.connection;
      // 釋放當前連線的資源,如果該連線已經被限制建立新的流,就返回一個Socket以關閉連線
      toClose = releaseIfNoNewStreams();
      if (this.connection != null) {
        // We had an already-allocated connection and it's good.
        result = this.connection;
        releasedConnection = null;
      }
      if (!reportedAcquired) {
        // If the connection was never reported acquired, don't report it as released!
        // 如果該連線從未被標記為獲得,不要標記為釋出狀態,reportedAcquired 通過 acquire()   方法修改
        releasedConnection = null;
      }
    
      if (result == null) {
        // Attempt to get a connection from the pool.
        // 嘗試供連線池中獲取一個連線
        Internal.instance.get(connectionPool, address, this, null);
        if (connection != null) {
          foundPooledConnection = true;
          result = connection;
        } else {
          selectedRoute = route;
        }
      }
    }
    // 關閉連線
    closeQuietly(toClose);
    
    if (releasedConnection != null) {
      eventListener.connectionReleased(call, releasedConnection);
    }
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
    }
    if (result != null) {
      // If we found an already-allocated or pooled connection, we're done.
      // 如果已經從連線池中獲取到了一個連線,就將其返回
      return result;
    }
    
    // If we need a route selection, make one. This   is a blocking operation.
    boolean newRouteSelection = false;
    if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
      newRouteSelection = true;
      routeSelection = routeSelector.next();
    }
    
    synchronized (connectionPool) {
      if (canceled) throw new IOException("Canceled");
    
      if (newRouteSelection) {
        // Now that we have a set of IP addresses,   make another attempt at getting a   connection from
        // the pool. This could match due to   connection coalescing.
         // 根據一系列的 IP地址從連線池中獲取一個連結
        List<Route> routes = routeSelection.getAll();
        for (int i = 0, size = routes.size(); i < size;i++) {
          Route route = routes.get(i);
          // 從連線池中獲取一個連線
          Internal.instance.get(connectionPool, address, this, route);
          if (connection != null) {
            foundPooledConnection = true;
            result = connection;
            this.route = route;
            break;
          }
        }
      }
    
      if (!foundPooledConnection) {
        if (selectedRoute == null) {
          selectedRoute = routeSelection.next();
        }
    
        // Create a connection and assign it to this allocation immediately. This makes it   possible
        // for an asynchronous cancel() to interrupt the handshake we're about to do.
        // 在連線池中如果沒有該連線,則建立一個新的連線,並將其分配,這樣我們就可以在握手之前進行終端
        route = selectedRoute;
        refusedStreamCount = 0;
        result = new RealConnection(connectionPool, selectedRoute);
        acquire(result, false);
      }
    }
    // If we found a pooled connection on the 2nd time around, we're done.
    if (foundPooledConnection) {
    // 如果我們在第二次的時候發現了一個池連線,那麼我們就將其返回
      eventListener.connectionAcquired(call, result);
      return result;
    }

    // Do TCP + TLS handshakes. This is a blocking     operation.
     // 進行 TCP 和 TLS 握手
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
      connectionRetryEnabled, call, eventListener);
    routeDatabase().connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
      reportedAcquired = true;

      // Pool the connection.
      // 將該連線放進連線池中
      Internal.instance.put(connectionPool, result);

      // If another multiplexed connection to the same   address was created concurrently, then
      // release this connection and acquire that one.
      // 如果同時建立了另一個到同一地址的多路複用連線,釋放這個連線並獲取那個連線
      if (result.isMultiplexed()) {
        socket = Internal.instance.deduplicate(connectionPool, address, this);
        result = connection;
      }
    }
    closeQuietly(socket);

    eventListener.connectionAcquired(call, result);
    return result;
}
複製程式碼

從以上的原始碼分析可知:

  • 判斷當前的連線是否可以使用:流是否已經被關閉,並且已經被限制建立新的流;
  • 如果當前的連線無法使用,就從連線池中獲取一個連線;
  • 連線池中也沒有發現可用的連線,建立一個新的連線,並進行握手,然後將其放到連線池中。

在從連線池中獲取一個連線的時候,使用了 Internal 的 get() 方法。Internal 有一個靜態的例項,會在 OkHttpClient 的靜態程式碼快中被初始化。我們會在 Internal 的 get() 中呼叫連線池的 get() 方法來得到一個連線。並且,從中我們明白了連線複用的一個好處就是省去了進行 TCP 和 TLS 握手的一個過程。因為建立連線本身也是需要消耗一些時間的,連線被複用之後可以提升我們網路訪問的效率。

接下來,我們來詳細分析下ConnectionPool是如何實現連線管理的。

OkHttp 的快取管理分成兩個步驟,一邊當我們建立了一個新的連線的時候,我們要把它放進快取裡面;另一邊,我們還要來對快取進行清理。在 ConnectionPool 中,當我們向連線池中快取一個連線的時候,只要呼叫雙端佇列的 add() 方法,將其加入到雙端佇列即可,而清理連線快取的操作則交給執行緒池來定時執行。

private final Deque<RealConnection> connections = new ArrayDeque<>();

void put(RealConnection connection) {
assert (Thread.holdsLock(this));
    if (!cleanupRunning) {
      cleanupRunning = true;
      // 使用執行緒池執行清理任務
      executor.execute(cleanupRunnable);
    }
    // 將新建的連線插入到雙端佇列中
    connections.add(connection);
}

 private final Runnable cleanupRunnable = new Runnable() {
@Override public void run() {
    while (true) {
        // 內部呼叫 cleanup() 方法來清理無效的連線
        long waitNanos = cleanup(System.nanoTime());
        if (waitNanos == -1) return;
        if (waitNanos > 0) {
          long waitMillis = waitNanos / 1000000L;
          waitNanos -= (waitMillis * 1000000L);
          synchronized (ConnectionPool.this) {
            try {
              ConnectionPool.this.wait(waitMillis, (int) waitNanos);
            } catch (InterruptedException ignored) {
            }
          }
        }
    }
};

long cleanup(long now) {
    int inUseConnectionCount = 0;
    int idleConnectionCount = 0;
    RealConnection longestIdleConnection = null;
    long longestIdleDurationNs = Long.MIN_VALUE;

    // Find either a connection to evict, or the time that the next eviction is due.
    synchronized (this) {
        // 遍歷所有的連線
        for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
          RealConnection connection = i.next();
    
          // If the connection is in use, keep     searching.
          // 遍歷所有的連線
          if (pruneAndGetAllocationCount(connection, now) > 0) {
            inUseConnectionCount++;
            continue;
          }
    
          idleConnectionCount++;
    
          // If the connection is ready to be     evicted,     we're done.
          // 如果找到了一個可以被清理的連線,會嘗試去尋找閒置時間最久的連線來釋放
          long idleDurationNs = now - connection.idleAtNanos;
          if (idleDurationNs > longestIdleDurationNs) {
            longestIdleDurationNs = idleDurationNs;
            longestIdleConnection = connection;
          }
        }
    
        // maxIdleConnections 表示最大允許的閒置的連線的數量,keepAliveDurationNs表示連線允許存活的最長的時間。
        // 預設空閒連線最大數目為5個,keepalive 時間最長為5分鐘。
        if (longestIdleDurationNs >= this.keepAliveDurationNs
            || idleConnectionCount > this.maxIdleConnections) {
          // We've found a connection to evict. Remove it from the list, then close it     below (outside
          // of the synchronized block).
          // 該連線的時長超出了最大的活躍時長或者閒置的連線數量超出了最大允許的範圍,直接移除
          connections.remove(longestIdleConnection);
        } else if (idleConnectionCount > 0) {
          // A connection will be ready to evict soon.
          // 閒置的連線的數量大於0,停頓指定的時間(等會兒會將其清理掉,現在還不是時候)
          return keepAliveDurationNs - longestIdleDurationNs;
        } else if (inUseConnectionCount > 0) {
          // All connections are in use. It'll be at least the keep alive duration 'til we run again.
          // 所有的連線都在使用中,5分鐘後再清理
          return keepAliveDurationNs;
        } else {
          // No connections, idle or in use.
           // 沒有連線
          cleanupRunning = false;
          return -1;
      }
}
複製程式碼

從以上的原始碼分析可知,首先會對快取中的連線進行遍歷,以尋找一個閒置時間最長的連線,然後根據該連線的閒置時長和最大允許的連線數量等引數來決定是否應該清理該連線。同時注意上面的方法的返回值是一個時間,如果閒置時間最長的連線仍然需要一段時間才能被清理的時候,會返回這段時間的時間差,然後會在這段時間之後再次對連線池進行清理。

四、總結

經過上面對OKHttp內部工作機制的一系列分析,我相信你已經對OKHttp已經有了一個比較深入的瞭解了。首先,我們會在請求的時候初始化一個Call的例項,然後執行它的execute()方法或enqueue()方法,內部最後都會執行到getResponseWithInterceptorChain()方法,這個方法裡面通過攔截器組成的責任鏈,依次經過使用者自定義普通攔截器、重試攔截器、橋接攔截器、快取攔截器、連線攔截器和使用者自定義網路攔截器以及訪問伺服器攔截器等攔截處理過程,來獲取到一個響應並交給使用者。其中,除了OKHttp的內部請求流程這點之外,快取和連線這兩部分內容也是兩個很重要的點,相信經過本文的講解,讀者對著三部分重點內容已經有了自己的理解。後面,將會為大家帶來OKHttp的封裝框架Retrofit原始碼的深入分析,敬請期待~

參考連結:

1、OKHttp V3.12.0原始碼

1、Android進階之光

2、OKHttp原始碼解析

3、Andriod 網路框架 OkHttp 原始碼解析

讚賞

如果這個庫對您有很大幫助,您願意支援這個專案的進一步開發和這個專案的持續維護。你可以掃描下面的二維碼,讓我喝一杯咖啡或啤酒。非常感謝您的捐贈。謝謝!

Android主流三方庫原始碼分析(一、深入理解OKHttp原始碼)Android主流三方庫原始碼分析(一、深入理解OKHttp原始碼)

Contanct Me

● 微信:

歡迎關注我的微信:bcce5360

● 微信群:

微信群如果不能掃碼加入,麻煩大家想進微信群的朋友們,加我微信拉你進群。

Android主流三方庫原始碼分析(一、深入理解OKHttp原始碼)

● QQ群:

2千人QQ群,Awesome-Android學習交流群,QQ群號:959936182, 歡迎大家加入~

About me

很感謝您閱讀這篇文章,希望您能將它分享給您的朋友或技術群,這對我意義重大。

希望我們能成為朋友,在 Github掘金上一起分享知識。

相關文章