一步步帶你讀懂 Okhttp 源碼

前言

okHttp, square 公司開源的網絡請求神器,截止到 2019-09-02,在 Github 上面已經超過 34K 的 star,足見他的受歡迎程度。java

到目前爲止,他的最新版本是 4.1.0, 使用 kotlin 語言寫的,因爲本人對 kotlin 語言不是很熟悉,這篇文章已 3.5.0 的版本爲基礎進行分析。程序員

簡介

Rxjava+Okhttp+Refrofit 現在已經成爲項目網絡請求的首選,在講解原理以前,咱們先來看一下 Okhttp 的基本使用。web

使用 OkHttp 基本是如下四步:面試

  1. 建立 OkHttpClient 對象
  2. 建立Request請求對象
  3. 建立Call對象
  4. 同步請求調用call.execute();異步請求調用call.enqueue(callback)

同步執行設計模式

//建立OkHttpClient對象
OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
   //建立Request請求對象
  Request request = new Request.Builder()
      .url(url)
      .build();

   //建立Call對象,並執行同步獲取網絡數據
  Response response = client.newCall(request).execute();
  return response.body().string();
}

複製代碼

異步執行緩存

void runAsync(String url, Callback callback) {
    OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Request.Builder builder = request.newBuilder().addHeader("name", "test");
            return chain.proceed(builder.build());
        }
    }).build();
    //建立Request請求對象
    Request request = new Request.Builder()
            .url(url)
            .build();

    client.newCall(request).enqueue(callback);

}
複製代碼

接下來我會從這四步,分析 Okhttp 的基本原理。bash

OkHttpClient

建立 OkHttpClient 通常有兩種方法,一種是直接 new OkHttpClient(),另一種是經過 OkHttpClient.Builder()服務器

OkhttpClient client = new OkHttpClient
                    .Builder()
                    .connectTimeout(5, TimeUnit.SECONDS)
                    .writeTimeout(10,TimeUnit.SECONDS)
                    .readTimeout(10, TimeUnit.SECONDS)
                    .build();
複製代碼

第二種建立方式主要是經過建造者模式,來配置一些參數,好比鏈接超時時間,讀寫超時時間,超時重試次數等。這樣有一個好處,能夠對外屏蔽掉構建 client 的細節。關於建造者模式的,有興趣的能夠讀個人這一篇文章建造者模式(Builder)及其應用微信

Request

public final class Request {
  final HttpUrl url;
  final String method;
  final Headers headers;
  final RequestBody body;
  final Object tag;

  private volatile CacheControl cacheControl; // Lazily initialized.
}
複製代碼

Request 對象主要封裝的是一些網絡請求的信息,好比請求 url,請求方法,請求頭,請求 body 等,也比較簡單,這裏再也不展開闡述。cookie

Call 對象

@Override public Call newCall(Request request) {
  return new RealCall(this, request, false /* for web socket */);
}

複製代碼

能夠看到 call 對象實際是 RealCall 的實例化對象

RealCall#execute()

@Override public Response execute() throws IOException {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  try {
    // 執行 client.dispatcher() 的 executed 方法
    client.dispatcher().executed(this);
    Response result = getResponseWithInterceptorChain();
    if (result == null) throw new IOException("Canceled");
    return result;
  } finally {
    // 最後再執行 dispatcher 的 finish 方法
    client.dispatcher().finished(this);
  }
}


複製代碼

在 execute 方法中,首先會調用 client.dispatcher().executed(this) 加入到 runningAsyncCalls 隊列當中,接着執行 getResponseWithInterceptorChain() 獲取請求結果,最終再執行 client.dispatcher().finished(this) 將 realCall 從 runningAsyncCalls 隊列中移除 。

咱們先來看一下 getResponseWithInterceptorChain 方法

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);
    return chain.proceed(originalRequest);
  }
複製代碼

能夠看到,首先,他會將客戶端的 interceptors 添加到 list 當中,接着,再添加 okhhttp 裏面的 interceptor,而後構建了一個 RealInterceptorChain 對象,並將咱們的 List<Interceptor> 做爲成員變量,最後調用 RealInterceptorChain 的 proced 方法。

  • client.interceptors() -> 咱們本身添加的請求攔截器,一般是作一些添加統一的token之類操做
  • retryAndFollowUpInterceptor -> 主要負責錯誤重試和請求重定向
  • BridgeInterceptor -> 負責添加網絡請求相關的必要的一些請求頭,好比Content-Type、Content-Length、Transfer-Encoding、User-Agent等等
  • CacheInterceptor -> 負責處理緩存相關操做
  • ConnectInterceptor -> 負責與服務器進行鏈接的操做
  • networkInterceptors -> 一樣是咱們能夠添加的攔截器的一種,它與client.interceptors() 不一樣的是兩者攔截的位置不同。
  • CallServerInterceptor -> 在這個攔截器中才會進行真實的網絡請求

Interceptor 裏面是怎樣實現的,這裏咱們暫不討論,接下來,咱們來看一下 proceed 方法

proceed 方法

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      Connection connection) throws IOException {
     
     // 省略無關代碼
    

    //  生成 list 當中下一個 interceptot 的 chain 對象
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    // 當前的 interceptor
    Interceptor interceptor = interceptors.get(index);
    // 當前的 intercept 處理下一個 intercept 包裝的 chain 對象
    Response response = interceptor.intercept(next);

    // ----

    return response;
  }
複製代碼

proceed 方法也很簡單,proceed方法每次從攔截器列表中取出攔截器,並調用 interceptor.intercept(next)。

熟悉 Okhttp 的應該都在回到,咱們在 addInterceptor 建立 Interceptor 實例,最終都會調用 chain.proceed(Request request),從而造成一種鏈式調用。關於責任鏈模式的能夠看個人這一篇文章 責任鏈模式以及在 Android 中的應用

OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Request.Builder builder = request.newBuilder().addHeader("name","test");
        return chain.proceed(builder.build());
    }
}).build();

複製代碼

而 OkHttp 是怎樣結束循環調用的,這是由於最後一個攔截器 CallServerInterceptor 並無調用chain.proceed(request),因此可以結束循環調用。

dispatcher

public final class Dispatcher {
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private Runnable idleCallback;

  /** Executes calls. Created lazily. */
  private ExecutorService executorService;

  // 異步的請求等待隊列
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  // 異步的正在請求的隊列
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  // 絨布的正在請求的隊列
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
  
}
複製代碼

異步請求 enqueue(Callback responseCallback)

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


複製代碼

首先,咱們先來看一下 AsyncCall 這個類

final class AsyncCall extends NamedRunnable {
  private final Callback responseCallback;

  // ----

  @Override protected void execute() {
    boolean signalledCallback = false;
    try {
      Response response = getResponseWithInterceptorChain();
      // 判斷請求是否取消了,若是取消了,直接回調 onFailure
      if (retryAndFollowUpInterceptor.isCanceled()) {
        signalledCallback = true;
        responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
      } else { // 請求成功
        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);
    }
  }
}


複製代碼
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 繼承 NamedRunnable, 而 NamedRunnable 是 Runnable 的子類,當執行 run 方法時,會執行 execute 方法。

咱們再來看一下 dispatcher 的 enqueue 方法

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


複製代碼

能夠看到當正在請求的請求數小於 maxRequests 的時候而且當前正在請求的隊列裏面至關 host 的小於 maxRequestsPerHost, 直接添加到 runningAsyncCalls 隊列中,並添加到線程池裏面執行,不然添加到準備隊列 readyAsyncCalls 裏面。

當執行 executorService().execute(call) 的時候,會調用 run 方法, run 方法又會調用到 execute 方法進行網絡請求,請求完成以後,會調用 client.dispatcher().finished(this) 從隊列裏面移除。

到此, Okhttp 的主要流程已經講完


小結

  1. 有一個分發器 Dispatcher,裏面有三個請求隊列,一個是正在請求的隊列,一個是等待隊列,另一個是同步的正在請求的隊列,當咱們執行 enqueue 方法的時候,他會判斷正在請求隊列數量是否超過容許的最大併發數量(默認是 64)(線程池的原理),若是超過了,會添加到等待隊列裏面。 excute 方法是同步執行的,每次執行會添加到同步請求隊列當中,執行完畢以後會移除
  2. 設計的核心思想責任鏈模式,當咱們須要攔截的時候,能夠實現 Interceptor 接口,會按照添加的順序執行 Chain.proceed 方法。
  3. 職責分明,OkhttpClient 對象主要處理一些基礎的配置,好比鏈接超時,讀寫超時,添加攔截器。Request 主要配置請求方法,請求頭等。

推薦閱讀

責任鏈模式以及在 Android 中的應用

觀察者設計模式 Vs 事件委託(java)

裝飾者模式及其應用

建造者模式(Builder)及其應用

二次封裝圖片第三方框架——簡單工廠模式的運用

Android 二次封裝網絡加載框架

java 代理模式詳解

Rxjava 2.x 源碼系列 - 基礎框架分析

Rxjava 2.x 源碼系列 - 線程切換 (上)

Rxjava 2.x 源碼系列 - 線程切換 (下)

Rxjava 2.x 源碼系列 - 變換操做符 Map(上)

butterknife 源碼分析

一步步拆解 LeakCanary

java 源碼系列 - 帶你讀懂 Reference 和 ReferenceQueue

掃一掃,歡迎關注個人微信公衆號 stormjun94(徐公碼字), 目前是一名程序員,不只分享 Android開發相關知識,同時還分享技術人成長曆程,包括我的總結,職場經驗,面試經驗等,但願能讓你少走一點彎路。

相關文章
相關標籤/搜索