3.OkHttp請求流程分析

Call和RealCall

經(jīng)過上面的初始化之后 okhttpClient 調(diào)用public Call newCall(Request request) 方法去構建一個Call,

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

可以看到 真實的Call是RealCall ,get知識點 一般來說,一個組織或者個人的代碼風格是差不多的 這里面Call的實現(xiàn)類是RealCall,其他的應該也是Real開頭的。

這里我們發(fā)現(xiàn)他把OkHttpClient和Reques傳了過去,他的構造方法是

  RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    final EventListener.Factory eventListenerFactory = client.eventListenerFactory();

    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    //重試和跟進攔截器
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);

    // TODO(jwilson): this is unsafe publication and not threadsafe.  
    // 這是不安全的發(fā)布,不是線程安全的。
    this.eventListener = eventListenerFactory.create(this);
  }

這里面比較面生的是的是RetryAndFollowUpInterceptor 按照字面意思重試和跟進攔截器 進去大概看一下

/**
 * This interceptor recovers from failures and follows redirects as necessary. It may throw an
 * {@link IOException} if the call was canceled.
 * 這個攔截器從故障中恢復,并根據(jù)需要遵循重定向。如果呼叫被取消,它可能會拋出IOException。
 */
public final class RetryAndFollowUpInterceptor implements Interceptor {
  /**
   * How many redirects and auth challenges should we attempt? Chrome follows 21 redirects; Firefox,
   * curl, and wget follow 20; Safari follows 16; and HTTP/1.0 recommends 5.
   * 我們應該嘗試多少次重定向和認證挑戰(zhàn)? Chrome遵循21次重定向; Firefox,curl和wget遵循20; Safari遵循16; HTTP / 1.0建議5。
   */
  private static final int MAX_FOLLOW_UPS = 20;

  private final OkHttpClient client;
  private final boolean forWebSocket;
  private StreamAllocation streamAllocation;
  private Object callStackTrace;
  private volatile boolean canceled;

  public RetryAndFollowUpInterceptor(OkHttpClient client, boolean forWebSocket) {
    this.client = client;
    this.forWebSocket = forWebSocket;
  }
  
  (....此處省略以后的代碼)

果然和字面意思一樣,這面看參數(shù)的話最大支持20次的重定向。后面的暫時不需要看 等用到的時候再看也不遲,避免陷入之間樹木不見森林的坑

后面又用工廠方法創(chuàng)建了一個EventListener 的類,看字面意思就是時間的監(jiān)聽類看里面的方法

Factory
fetchStart
dnsStart
dnsEnd
connectStart
secureConnectStart
secureConnectEnd
connectEnd
requestHeadersStart
requestHeadersEnd
requestBodyStart
requestBodyEnd
responseHeadersStart
responseHeadersEnd
responseBodyStart
responseBodyEnd
fetchEnd

通過這個事件我們大致能看出來OkHttp請求的流程,然后回到RealCall ,之后調(diào)用的是一個execute或者是enqueue 我們在Android項目里由于主線程是不允許有網(wǎng)絡請求的,所以我們先來搞enqueue,話不多說,進去看

@Override 
public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    //捕獲呼叫堆棧跟蹤
    captureCallStackTrace();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

Already Executed 這個異常大家在平時用的時候應該偶爾會碰到,原因看到了吧。當你的這個請求應在運行的時候你在去調(diào)用的時候就異常了

然后第二個是捕獲呼叫堆棧跟蹤器,這個就忽略掉,看真正的重頭戲

前方高能預警,提起精神看

Dispatcher

相關的更詳細的分析在第五篇 OkHttp的請求調(diào)度分析

調(diào)用的OkHttpClient 的dispatcher的enqueue方法,dispatcher的初始化是在OkHttpClient的Builder里面 看上面的代碼 是直接new 了一個 Dispatcher ,我們?nèi)ispatcher里看

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

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

  /** Ready async calls in the order they'll be run. 按照他們將要運行的順序進行準備就緒的異步調(diào)用 */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. 運行異步調(diào)用包括尚未完成的取消請求*/
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet.  運行同步調(diào)用包括尚未完成的取消呼叫*/
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

  public Dispatcher(ExecutorService executorService) {
    this.executorService = executorService;
  }

  public Dispatcher() {
  }
  
  (....此處省略N行代碼)
  
   synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }
}

我們看到他的構造函數(shù)就是一個空的 然后全局變量的話有個線程池,和三個雙端隊列 有可能有同學不知道Deque是什么,deque 即雙端隊列。是一種具有隊列和棧的性質(zhì)的數(shù)據(jù)結(jié)構。雙端隊列中的元素可以從兩端彈出,其限定插入和刪除操作在表的兩端進行。

我們看到enqueue里面就是操作運行異步調(diào)用包括尚未完成的取消請求的runningAsyncCalls,他的判斷條件是:

1.當前隊列里面的請求數(shù)量小于最大請求數(shù)也就是64

2.當前隊列里面的鏈接的總host數(shù)量小于最大請求Host數(shù)

如果條件成立就添加到這個隊列里面,否則的話就添加到readyAsyncCalls里,也就是按照他們將要運行的順序進行準備就緒的異步調(diào)用的隊列

加入到運行隊列里后,執(zhí)行executorService().execute(call);方法這個方法就是個new出了一個線程池,然后執(zhí)行

  public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

可能關于線程池的一些東西大家不是特別清楚 這里稍微解釋一下,首先是他的構造函數(shù)

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, 
BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) 
corePoolSize: 線程池維護線程的最少數(shù)量 
maximumPoolSize:線程池維護線程的最大數(shù)量 
keepAliveTime: 線程池維護線程所允許的空閑時間 
unit: 線程池維護線程所允許的空閑時間的單位 
workQueue: 線程池所使用的緩沖隊列 
handler: 線程池對拒絕任務的處理策略

上面的SynchronousQueue可能一般同學看的不是特別熟悉這里解釋一下:

SynchronousQueue是一個沒有數(shù)據(jù)緩沖的BlockingQueue,生產(chǎn)者線程對其的插入操作put必須等待消費者的移除操作take,反過來也一樣。

SynchronousQueue的一個使用場景的典型就是在線程池里。Executors.newCachedThreadPool()就使用了SynchronousQueue,這個線程池根據(jù)需要(新任務到來時)創(chuàng)建新的線程,如果有空閑線程則會重復使用,線程空閑了60秒后會被回收。執(zhí)行是調(diào)用execute方法。

峰回路轉(zhuǎn) ,回到ReallCall的enqueue里面

這里執(zhí)行的正式AsyncCall,new AsyncCall(responseCallback) AsyncCalls是RealCall的一個內(nèi)部類,繼承NamedRunnable,NamedRunnable是一個實現(xiàn)了Runnable接口的抽象類,

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

這里他做了兩件事

  1. 給當前線程設設置了個名字
  2. 新增了一個抽象方法execute

把握住這兩個 再來看AsyncCall

AsyncCall

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      //命名規(guī)則 OkHttp+協(xié)議名+域名
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

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

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }

    @Override protected void execute() {
      boolean signalledCallback = false;
      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) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        //調(diào)用dispatcer的finshed方法,(重要,在下文的請求調(diào)度分析會將)
        client.dispatcher().finished(this);
      }
    }
  }

上面比較重要的是這一句

  Response response = getResponseWithInterceptorChain();

如果重試和跟進攔截器沒有被取消的話,返回請求成功調(diào)用 responseCallback.onResponse,如果中間有什么異常的話調(diào)用responseCallback.onFailure(RealCall.this, e);

getResponseWithInterceptorChain() 這個方法非常重要,是整個OkHttp請求的核心,他是組裝了一系列的攔截鏈,進行鏈式調(diào)用,最后返回組裝的請求結(jié)果

 /*一共五個攔截器  包括
  *   RetryAndFollowUpInterceptor 重試和跟進攔截器
  *   BridgeInterceptor           橋攔截器
  *   CacheInterceptor            緩存攔截器
  *   ConnectInterceptor          鏈接攔截器
  *   CallServerInterceptor       呼叫服務攔截器
  *
  *   RealInterceptorChain        實際攔截鏈
  * */
  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);
  }

接下來 我們來走進這些請求鏈的世界,去分析整個OkHttp請求的具體

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

相關閱讀更多精彩內(nèi)容

  • 這篇文章主要講 Android 網(wǎng)絡請求時所使用到的各個請求庫的關系,以及 OkHttp3 的介紹。(如理解有誤,...
    小莊bb閱讀 1,331評論 0 4
  • 1.OkHttp源碼解析(一):OKHttp初階2 OkHttp源碼解析(二):OkHttp連接的"前戲"——HT...
    隔壁老李頭閱讀 16,101評論 36 72
  • OkHttp源碼的samples的簡單使用的示例: public static void main(String....
    _warren閱讀 878評論 0 1
  • OkHttp源碼分析-同步篇 很早就想拿okhttp開刀了,這次就記一次使用OKhttp的網(wǎng)絡請求。首先需要說明的...
    埃賽爾閱讀 1,060評論 1 2
  • 只是被今天的陽光和藍天吸引了,所以心情才那么好,只是不想又被人誤解是不是戀愛了?唉,俗人俗事糾纏不清!
    安一木閱讀 414評論 0 0

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