Okhttp 概述

OkHttp 源碼概述

概覽

OkHttp是Android開發(fā)中一個支持Http協(xié)議的高效網(wǎng)絡請求框架,支持同步請求和異步請求。

我們通過配置Request的url,okhttp就可以幫我們配置好其它所有關于http協(xié)議通訊的所有事情,包括緩存(需要手動開啟)、添加請求頭、壓縮文件(GZIP)、自動重連、重定向、cookie、連接共享等等。

需要注意的是,OkHttp完全沒有使用java內(nèi)置的HttpUrlConnection。而是直接基于傳輸層的socket,重寫了一個Android上的Http請求客戶端,更加高效。

OkHttp源碼相關類及其功能

源碼

OkHttp的源碼主要分為兩部分:走攔截器之前的部分,和攔截器部分。攔截器是OkHttp最精髓的地方,同時支持擴展,添加自定義攔截器。

Okio

OkHttp對于socket的寫入數(shù)據(jù)和讀取數(shù)據(jù)是基于Okio的,為什么OkHttp不用java提供的IO流而是使用OKio呢?

  • 較低的cpu和內(nèi)存消耗,okio使用segment組成的鏈表來存儲數(shù)據(jù),segment的內(nèi)部實際就是字節(jié)數(shù)組。segment通過對復制操作共享一個字節(jié)數(shù)組等操作,來節(jié)省內(nèi)存和存儲數(shù)據(jù)
  • API便捷,直接就具有讀取字符串、整數(shù)等數(shù)據(jù)。無需IO流的多層包裝
  • 提供了Gzip、加密操作MD5和SHA-1功能

DiskLruCache

  • 只是一個1000多行的java文件,但功能很強大。內(nèi)部通過一個LinkedHashMap,來達到刪除時先刪除不常用的數(shù)據(jù)的功能。LinkedHashMap類本身就具有這個功能。通過LinkedHashMap的構造函數(shù),將參數(shù)accessOrder指定為true,這樣每次當我們訪問LinkedHashMap的元素時,就會將這個元素移至隊尾,當檢測到指定大小的空間已滿時,DiskLruCache的cleanupRunnable這個清理線程就會啟動,從LinkedHashMap的隊頭(不常用元素的一段)開始清理,直到size < maxSize ;也是就緩存空間小于我們的設定值,就停止清理。

  • DiskLruCache有一個日志文件,記錄我們對緩存的每次操作。這里有一個Dirty和Clean的概念。clean代表可讀,dirty代表正在被寫入。所以每當一個entry出現(xiàn)dirty的操作時,之后往往伴隨著clean操作或者delete操作

  • 每一個entry有valuecount * 2 個文件。OkHttp中valuecount的值默認為2

  • 注意,空間滿,清理的時候是按照當次運行程序的排序來清理的,而不是按照磁盤的整個訪問次序清理的。

  • 每次應用啟動時加載DiskLruCache的時候會根據(jù)日志內(nèi)容初始化LinkedHashMap鏈表,填充內(nèi)容。

攔截器之前的部分

這部分的內(nèi)容很簡單,沒什么說的,
從Dispatcher的成員變量就可以看出他的作用

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

dispatcher內(nèi)部維護三個隊列,一個同步隊列。兩個異步隊列:正準備和正在運行。然后通過一個線程池來執(zhí)行異步請求。
不管是同步請求還是異步請求,真正的網(wǎng)絡請求邏輯就是先加入running的隊列,之后通過getResponseWithInterceptorChain()這個方法完成網(wǎng)絡請求的,也就是攔截器。(當異步執(zhí)行時,會先做判斷:)


  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  
  
synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

okhttp對異步最多支持64個同時請求,而且對同一個host 的url只支持5個同時請求。
不管是同步還是異步,請求完成之后都會執(zhí)行dispatcher.finished()方法,finished方法調(diào)用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.
    }
  }

這里的邏輯就是遍歷readyAsyncCalls,也就是準備隊列,如果當前正在執(zhí)行的請求小于64個或者對同一個hosturl的請求小于5個,就把這個請求移出準備隊列,并加入running隊列,調(diào)用線程池去執(zhí)行這個請求。

攔截器部分

Response getResponseWithInterceptorChain() throws IOException {
        // Build a full stack of interceptors.
        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));

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

        return chain.proceed(originalRequest);
    }

上面這一段代碼就是OkHttp的核心,完整的攔截器流程。從Request向下遞,至Reponse向上歸

責任鏈模式

每一個攔截器的intercept方法,被chain.proceed(request)攔截成兩部分,chain.proceed(request)上面的第一次發(fā)出請求依次往下執(zhí)行 的過程,chain.proceed(request)下方的代碼在請求完成執(zhí)行 的過程,就像這張圖

責任鏈模式

攔截器的職責

從上到下一共有五個攔截器,他們的職責簡單總結(jié)一下,需要注意的是每一次網(wǎng)絡請求都會經(jīng)歷每個攔截器兩次,請求報文一次,響應報文一次。

  1. RetryAndFollowUpInterceptor :根據(jù)Http協(xié)議的策略、Http請求和響應報文,來自動進行重定向與重連。
  2. BridgeInterceptor:“ 這個攔截器的職能相對簡單,就是對請求報文添加一些必要的headers,比如gzip,Host,Content-Length,Transfer-Encoding,Connection,User-Agent等等,對回來的響應報文也會刪除一些headers。
  3. CacheInterceptor: 這一個攔截器如其名,就是緩存策略相關,緩存策略也是根據(jù)HTTP協(xié)議來制定的。
  4. ConnectInterceptor :這個攔截器是真正通過socket與服務器建立連接的地方
  5. CallServerInterceptor:通過Okio與上一個攔截器建立起連接的socket向服務器發(fā)送請求報文,并且接受響應報文并將之構建成Respose一層一層傳遞給上層的攔截器。

自定義攔截器

而我們可以自己配置兩種攔截器,interceptorsnetworkInterceptors,OkHttp官方分別叫他們 應用攔截器網(wǎng)絡攔截器 。從getResponseWithInterceptorChain方法中可以看到二者的順序,分別是第一個攔截器和倒數(shù)第二個攔截器,具體說一下兩個攔截器的區(qū)別:

兩個自定義攔截器的位置

應用攔截器
  • 這里得到的請求沒有BridgeInterceptor的重定向和BridgeInterceptor給它添加請求頭,所以這里得到的請求報文是最原始的。相對應的,這里得到的響應報文也是最終的客戶端接收到的響應報文。
  • 這個攔截器是第一個執(zhí)行的,所以在這里有能力去決定是否要執(zhí)行Chain.proceed(),讓請求向下傳遞,通過這個特點可以實現(xiàn)一些有趣的功能
網(wǎng)絡攔截器
  • 這里能夠修改最終向服務器發(fā)送的響應報文
  • 緩存得到的response不會走這個攔截器,因為他在cacheIntercepter那里就返回了
  • 可以獲得服務器返回的最原始的響應報文
自定義攔截器

可以通過自定義攔截器來完成,動態(tài)添加Token,網(wǎng)絡信息日志打印等功能
,下面就是一個簡單的網(wǎng)絡請求信息日志打印攔截器的例子。

public class LoggingInterceptor extends BaseInterceptor {

    private static final String TAG = "LoggingInterceptor";

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

        long t1 = System.nanoTime();
        LogUtils.i(TAG, "intercept: " + String.format("Sending request %s on %s%n%s",
                request.url(), chain.connection(), request.headers()));

        Response response = chain.proceed(request);

        long t2 = System.nanoTime();
        LogUtils.i(TAG, "intercept: " + String.format("Received response for %s in %.1fms%n%s",
                response.request().url(), (t2 - t1) / 1e6d, response.headers()));

        return response;
    }
}

自定義攔截器的核心是要理解責任鏈模式和遞歸,在chain.proceed(request) 上方的代碼是 遞 的過程,這個方法return之后是 歸 的過程,代碼寫在 遞 過程里,還是寫在 歸 過程里,是需要根據(jù)場景具體分析的

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

友情鏈接更多精彩內(nèi)容