Okhttp源碼學習之總體流程

這個庫一直想要去學習,不過一直沒有時間去做,打算一點點梳理Okhttp。本文將按照發起網絡請求的順序來詳細描述下okhttp是如何發起網絡請求的,其中包含一些優秀文章可供大家參考。

使用實例

先放一段Okhttp的使用代碼。

       //1
        OkHttpClient client=new OkHttpClient();
        //2
        Request request = new Request.Builder()
        .url("url)
        .build();
        //3
		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 {
    			String results = response.body().string(); 
    		}
		}); 

我們閱讀源碼就要從暴露給用戶使用的那個類先去入手,對應Okhttp就是**(創建)OkhttpClient**,以及RequestCallback,在新寫Callback的時候要重寫onFailure(Call call, IOException e)OnResponse(Call call, Response response)兩個函數,於是另一個重要的類Response類簡單來說,使用的時候,只需要傳入網絡的url地址,然後加上新創建的Callback對象(包括重寫兩個函數)就完成了對url地址的訪問(獲取數據或上傳數據)。

整體流程

1.創建OkhttpClient實例

在讀源碼之前,每個類都會有一些註釋,先閱讀下注釋。

1.1 用戶使用時,創建全局(單例)實例

這裏的意思就是說,最好在整個應用中只包含一個(全局)OkhttpClient實例,在每一個發起Http請求的時候重用該實例即可(單例),同時複用的還有Response緩存、共用線程池以及共用連接池。

 * <h3>OkHttpClients should be shared</h3>
 *
 * <p>OkHttp performs best when you create a single {@code OkHttpClient} instance and reuse it for
 * all of your HTTP calls. This is because each client holds its own connection pool and thread
 * pools. Reusing connections and threads reduces latency and saves memory. Conversely, creating a
 * client for each request wastes resources on idle pools.

1.2 構造方式

可以使用兩種方式創建OkHttpClient的實例(構造函數和使用Builder),以及一種特殊的構造方式。
還是先看註釋。

<p>Use {@code new OkHttpClient()} to create a shared instance with the default settings:
 * <pre>   {@code
 *
 *   // The singleton HTTP client.
 *   public final OkHttpClient client = new OkHttpClient();
 * }</pre>
 *
 * <p>Or use {@code new OkHttpClient.Builder()} to create a shared instance with custom settings:
 * <pre>   {@code
 *
 *   // The singleton HTTP client.
 *   public final OkHttpClient client = new OkHttpClient.Builder()
 *       .addInterceptor(new HttpLoggingInterceptor())
 *       .cache(new Cache(cacheDir, cacheSize))
 *       .build();
 * }</pre>

我們來讀源碼。
這是第一種默認的方式

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

最後會調用該函數,使用的就是默認的Builder

private OkHttpClient(Builder builder) {
	this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    this.protocols = builder.protocols;
    this.connectionSpecs = builder.connectionSpecs;
    ...
}

第二種方式:想要配置OkhttpClient的一些參數,就需要使用Builder。

public final OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(new HttpLoggingInterceptor())
        .cache(new Cache(cacheDir, cacheSize))
        ...//其他設置
        .build();

最後調用Builder類內的build函數,這裏的this就是Builder,所以會調用OkhttpClient(Builder builder)來更改client的數據域。

 public OkHttpClient build() {
      return new OkHttpClient(this);
    }

總結:OkHttpClient本身不能設置參數,需要藉助於其內部類Builder設置參數,參數設置完成後,調用Builder的build方法得到一個配置好參數的OkHttpClient對象。這些配置的參數會對該OkHttpClient對象所生成的所有HTTP請求都有影響。
第三種方式:我們先看註釋中的文字。
您可以使用{@link#NewBuilder()}自定義共享的OkHttpClient實例。 這將構建一個共享相同連接池,線程池和配置的客戶端。 使用生成器方法爲特定目的配置派生客戶端。
共享相同連接池,線程池和配置的客戶端,共享誰的呢?當然是共享那個client實例的。有時候我們想單獨給某個網絡請求設置特別的幾個參數,比如只想設置某個請求的超時時間,但是還想保持OkHttpClient對象(client)中的其他的參數設置,那麼可以調用OkHttpClient對象的newBuilder()方法。

* <h3>Customize your client with newBuilder()</h3>
 *
 * <p>You can customize a shared OkHttpClient instance with {@link #newBuilder()}. This builds a
 * client that shares the same connection pool, thread pools, and configuration. Use the builder
 * methods to configure the derived client for a specific purpose.
 *
 * <p>This example shows a call with a short 500 millisecond timeout: <pre>   {@code
 *
 *   OkHttpClient eagerClient = client.newBuilder()
 *       .readTimeout(500, TimeUnit.MILLISECONDS)
 *       .build();
 *   Response response = eagerClient.newCall(request).execute();
 * }</pre>

這裏的this是Okhttp的實例

public Builder newBuilder() {
    return new Builder(this);
  }

之後會調用Builder類中的這個構造函數,這樣就在builder中保存了OkHttpClient中的參數。

 Builder(OkHttpClient okHttpClient) {
      this.dispatcher = okHttpClient.dispatcher;
      this.proxy = okHttpClient.proxy;
      this.protocols = okHttpClient.protocols;
      this.connectionSpecs = okHttpClient.connectionSpecs;
      ...
    }

1.3 優點

不需要將其殺掉。
保持空閒的線程和連接將自動釋放。

<h3>Shutdown isn't necessary</h3>

1.4 涉及的模式

1.構造者模式

不用說了,都有builder了,肯定就是構造者模式了。

2.單例模式

有點單例模式的意思,但這裏沒有,就是我們構建的OkhttpCilent最好要單例,只構造一個來複用。

3.外觀模式

用戶可以使用這個類來進行一些操作來實現內部的功能。

2.構造Request對象

構造方法

Request也是需要通過構造者模式來構造。

private Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tag = builder.tag != null ? builder.tag : this;
  }

下面是Builder中的部分代碼,可以看到無參構造方法時Builder會默認method爲get,其實我們可以簡單理解爲就是需要將url傳給Request,以便於它發送網絡請求。也可以看到我們如果想要構建Request對象就一定要構建一個Builder對象因爲Builder類中的每個設置參數的函數都是返回this,所以可以使用鏈式結構賦值,最後使用build構建時,直接調用new Request(this),這裏的this是Builder對象。

//使用代碼
Request request=new Request.Builder().url(address).build();
//源碼
 public static class Builder {
    private HttpUrl url;
    private String method;
    private Headers.Builder headers;
    private RequestBody body;
    private Object tag;

    public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
    }

    private Builder(Request request) {
      this.url = request.url;
      this.method = request.method;
      this.body = request.body;
      this.tag = request.tag;
      this.headers = request.headers.newBuilder();
    }

    public Builder url(HttpUrl url) {
      if (url == null) throw new NullPointerException("url == null");
      this.url = url;
      return this;
    }
    ...
    public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
    }

get與post

get方式很簡單,因爲我們RequestBuilder的無參構造函數默認將method設置爲GET。

Request request = new Request.Builder()
        .url("http://www.baidu.com")
        .build();

而post方式則不同,我們需要藉助RequestBody類與post方法。因爲post方法會將所上傳的信息封裝在請求數據中併發給服務器,這裏請求數據就是RequestBody,具體代碼如下。

首先要創建一個RequestBody(傳入當前MediaType類型和json數據),同時要調用Builder的post方法,並傳入RequestBody實例。在post方法中會將method置爲post,對body賦值。

public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
    RequestBody body = RequestBody.create(JSON, json);
    Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
    Response response = client.newCall(request).execute();
    if (response.isSuccessful()) {
        return response.body().string();
    } else {
        throw new IOException("Unexpected code " + response);
    }
}

3.同步模式與異步模式

3.1 簡介

當構建完Request之後,我們就需要構建一個Call,Okhttp中使用異步模式就是使用enqueue方法,而同步模式就是使用execute方法。同步方式沒有傳入Callback對象,我們可以通過判斷返回的response的isSuccessfull字段來判斷是否請求成功。
同步方式:

Request request = new Request.Builder()
        .url(url)
        .build();
Response response = client.newCall(request).execute();
 if (response.isSuccessful()) {
        return response.body().string();
    } else {
        throw new IOException("Unexpected code " + response);
    }

異步方式:enqueue()方法的內部已經開好子線程了,在子線程中去執行HTTP請求,並將最終的請求結果回調OKhttp.Callback當中,在調用它的時候要重寫onResponse(),得到服務器返回的具體內容,重寫onFailure(),在這裏對異常情況進行處理。不可以再這裏進行UI操作。如果要進行UI操作一定要通過runOnUiThread(new Runnable(){});切換回UI線程

Request request = new Request.Builder()
        .url(url)
        .build();
client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        e.printStackTrace();
         runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(WeatherActivity.this, "獲取天氣信息失敗",
                                Toast.LENGTH_SHORT).show();
                        //刷新事件結束,隱藏刷新進度條
                        swipeRefresh.setRefreshing(false);
                    }
                });
    }
    @Override
    public void onResponse(Call call, Response response) throws IOException {
    	String results = response.body().string(); 
    	runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                    	...
                    	}
                   });
    }
});

3.2 異步方式

3.2.1 newcall()方法

這裏我們是使用異步方式,所以先看異步的代碼。

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

我們回到OkhttpClient,去看newCall方法,發現返回了的其實是 RealCall對象

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

能看到OkHttpClient是實現了接口Call.Factory,很明顯這裏是工廠模式將(Call)構建的細節交給實現它的類實現,頂層只需要返回Call對象即可,我們不管是怎麼構建的call。

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {...}
interface Factory {
    Call newCall(Request request);
  }

繼續回到RealCall方法,能看到剛剛調用的構造函數,基本賦值包括OkHttpClient,Request,以及一個boolean變量,這裏默認是給false。

 RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
  }

涉及到知識盲點了,默認創建一個RetryAndFollowUpInterceptor對象,這個類被叫做過濾器/攔截器

3.2.2 enqueue()方法

現在Call創建完成(RealCall),接下來將請求加入調度,代碼如下:

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

這裏是調用了Call對象的enqueue方法,但其實是RealCall對象的enqueue方法,繼續回到RealCall類。可以看到這裏使用了synchronized加入了對象鎖,防止多線程同時調用,如果用過就會拋出異常,沒有用過就會將boolean變量賦值爲true。
百度百科:synchronized
synchronized方法後面的括號中是對象,就是對象鎖,如果線程進入,則得到當前對象鎖,那麼別的線程在該類所有對象上的任何操作都不能進行

之後使用了captureCallStackTrace()方法。

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

captureCallStackTrace()方法中相當於爲RetryAndFollowUpInterceptor對象(攔截器)加入了一個用於追蹤堆棧信息的callStackTrace。

 private void captureCallStackTrace() {
    Object callStackTrace = Platform.get().getStackTraceForCloseable("response.body().close()");
    retryAndFollowUpInterceptor.setCallStackTrace(callStackTrace);
  }

之後使用了 client.dispatcher().enqueue(new AsyncCall(responseCallback));創建了一個AsyncCall的實例

看下 AsyncCall類,這裏可以簡單先理解爲把Callback對象先存儲在該類中,之後會敘述該類的作用。

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

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

之後看下client.dispatcher()方法返回了OkHttpClient類中的Dispatcher對象(調度器)。該Dispatcher對象是在OkHttpClient對象構造時,獲取Bulider對象中的Dispatcher對象。(總結一下:所有OkHttpClient中的數據域對象,全是Builder類中的對象,在Builder類構造函數中被賦值

public Dispatcher dispatcher() {
    return dispatcher;
  }

3.2.3 Dispatcher

進入Dispathcer類,首先看該類的成員變量(數據域)。

  //最大異步請求數爲64
  private int maxRequests = 64;
  //每個主機的最大請求數爲5
  private int maxRequestsPerHost = 5;
  private Runnable idleCallback;
	
	//線程池
  /** Executes calls. Created lazily. */
  private 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<>();

我好菜,連Deque是雙端隊列都不知道。簡單來說雙端隊列就是可以對隊首和隊尾插入和移除元素。這裏稍微補充一下Deque的方法。
在這裏插入圖片描述
看起來與LinkedList有點相似。都是即可作爲棧也可以作爲隊列
在這裏插入圖片描述
之後去看該類的enqueue方法。該方法進行了上鎖,線程獲得了成員鎖,即一次只可以有一個線程訪問該方法,其他線程想要訪問的時候就會被阻塞,需要等到該線程用完纔可以訪問。

synchronized void enqueue(AsyncCall call) {
	//判斷正在執行的異步請求數沒有達到閾值,並且該對象需要的主機請求數也沒有達到閾值
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    //加入到執行隊列,並立即執行。
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
    //加入等待隊列
      readyAsyncCalls.add(call);
    }
  }

runningCallsForHost函數 返回該請求(AsyncCall對象)需要的主機請求數。

/** Returns the number of running calls that share a host with {@code call}. */
  private int runningCallsForHost(AsyncCall call) {
    int result = 0;
    for (AsyncCall c : runningAsyncCalls) {
      if (c.host().equals(call.host())) result++;
    }
    return result;
  }

加入到執行隊列後,還有一行代碼。可以理解爲使用線程池創建一個線程去執行該請求(任務)
這裏可以看到放入線程池中,線程池會創建一個線程去執行該請求,因此前面說的enqueue中會爲任務自己開啓線程去執行。

 executorService().execute(call);

executorService()方法是通過懶漢單例模式創建了一個線程池。各個參數的含義如下

  • 0(corePoolSize):核心線程的數量爲 0,空閒一段時間後所有線程將全部被銷燬。
  • Integer.MAX_VALUE(maximumPoolSize): 最大線程數,當任務進來時可以擴充的線程最大值,相當於無限大。
  • 60(keepAliveTime): 非核心線程的處於空閒後的最大存活時間。
  • TimeUnit.SECONDS:存活時間的單位是秒。
  • new SynchronousQueue():工作隊列,先進先出。
  • Util.threadFactory(“OkHttp Dispatcher”, false):單個線程的工廠 ,通過線程工廠創建線程。

簡單來說,在實際運行中,當收到10個併發請求時,線程池會創建十個線程,當工作完成後,線程池會在60s後相繼關閉所有線程。(因爲都是非核心線程

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

execute()方法可以理解爲將任務添加到線程去執行(當將一個任務添加到線程池中的時候,線程池會爲每個任務創建一個線程,該線程會在之後的某個時刻自動執行。)。
什麼樣的類叫做任務呢。任務就是一個實現了Runnable接口的類。線程就會在接下來自動執行(可以理解爲執行該任務類的run()方法)。
綜上所述,可以總結下enqueue方法。
該方法的核心思想如下:

首先做出判斷:
1執行隊列的大小(隊列中AsyncCall的數量也相當於正在執行的異步請求數)是否小於閾值
2.執行該請求(AsyncCall對象)的主機請求數是否小於主機的最大請求數。
滿足1和2就將該請求(任務)添加到執行隊列,立刻執行任務(將任務添加到線程池之後,線程池會爲任務創建一個線程),否則進入等待隊列
接下來要看一下我們的從後臺獲取的數據是從哪裏返回的。

3.2.4 AsyncCall

前面說到簡單先理解AsyncCall存儲了我們的Callback對象,而且使用過Okhttp的人都知道,返回數據的操作都是在Callback對象的兩個函數(onFailure()與OnResponse()方法)中。

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 {
    	String results = response.body().string(); 
    }
});

那麼這兩個方法是在哪裏調用的呢,別跟我說是請求成功和請求失敗的時候調用的。我說的是具體位置好吧。

我們把目光放到AsyncCall類去,AsyncCall是RealCall的內部類
爲什麼要看該類,因爲線程執行的是該任務,我們去找下他是怎麼實現的Runnable接口,也可以想到數據應該是在run()函數中獲取到的。

final class AsyncCall extends NamedRunnable

可以看到他繼承自NamedRunnable,我們進入該抽象類。沒的說,該類實現了Runnable接口。可以看到run函數中執行的是execute()函數execute函數是一個抽象的函數。那麼一目瞭然,AsyncCall類繼承該抽象類就一定會實現execute()函數。同時,將一個AsyncCall任務添加到線程池中,線程在執行的就是所傳入任務(AsyncCall實例)的execute()函數。

/**
 * Runnable implementation which always sets its thread name.
 */
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類的execute()方法。果不其然,首先通過**getResponseWithInterceptorChain()**方法獲得Response,之後根據判斷過濾器retryAndFollowUpInterceptor是否被取消了,來判斷請求成功還是失敗進而回調Callback的onFailure還是onResponse。在回調函數中傳遞RealCall與response/IOException。那麼核心就是getResponseWithInterceptorChain()方法了。

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }
    ...
     @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 {
        client.dispatcher().finished(this);
      }
    }
    }

3.2.5 getResponseWithInterceptorChain

接着看getResponseWithInterceptorChain的代碼,簡單來看,首先創建了一個List用來存儲過濾器,其實在創建Okhttp對象的時候,都幫我們默認實現了這些過濾器。這裏涉及了每個過濾器完成自己的任務,互不耦合。

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    //失敗和重定向過濾器
    interceptors.add(retryAndFollowUpInterceptor);
    //封裝request和response過濾器
    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);
       return chain.proceed(originalRequest);
  }

之後執行過濾器,其他可以先不考慮,單單看index,這個index是幹嘛用的呢,我們繼續向下看代碼。

public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, Connection connection, int index, Request request) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
  }

創建RealInterceptorChain後,會調用該對象的proceed方法,直接去看下面的第二個方法。

 @Override public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
  }

  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      Connection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !sameConnection(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    return response;
  }

首先就有下面的代碼,interceptors是我們之前創建RealInterceptorChain對象時傳入的list。

if (index >= interceptors.size()) throw new AssertionError();

省略兩個判斷,直接到這裏,註釋寫着調用鏈中的下一個攔截器。而且創建了新的RealInterceptorChain也使用了下個的index。根據我的理解就是首先取出當前index的攔截器(interceptors.get(index)),然後將其與下一個連接起來(或者可以理解爲去處理下一個index的攔截器)。這裏就有了遞歸調用的感覺

// Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

我們去看一下Interceptor的intercept方法。可以看到他繼續調用了RealInterceptorChain的proceed方法,這就是遞歸調用了。最後返回經過所有攔截器之後的Response,這就是getResponseWithInterceptorChain() 方法返回的結果。

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

    streamAllocation = new StreamAllocation(
        client.connectionPool(), createAddress(request.url()), callStackTrace);

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response = null;
      boolean releaseConnection = true;
      try {
        response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      }
      ...
      }
}

這裏應該是使用了責任鏈模式。
總結下getResonseWithInterceptorChain方法()做的事情。

  1. 創建一個List(interceptors)用來存儲攔截器
  2. 創建RealInterceptorChain對象,其中傳入的包括interceptorsindex(爲0,從第一個開始)
  3. 調用RealInterceptorChain對象的proceed方法,在該方法中創建新的RealInterceptorChain對象(next),傳入list與index(1)。取出lsit中當前index的攔截器(interceptor),聲明Response對象,調用該攔截器的intercept方法,並傳入新創建的RealInterceptorChain,並將其返回值付給新創建的Response對象。(Response response = interceptor.intercept(next)),在proceed方法的結束會返回Response對象。
  4. 攔截器的intercept方法會繼續調用傳入的RealInterceptorChain對象的proceed方法,這樣就會遞歸處理攔截器鏈中的下一個攔截器。
  5. 遞歸結束後會返回一個Response對象。

3.3同步方式

先回顧一下同步方式是如何請求數據的。

Request request = new Request.Builder()
        .url("http://www.baidu.com")
        .build();
Response response = client.newCall(request).execute();

我們看到了熟悉的身影。

 client.newCall(request)

和異步相同都是先創建一個RealCall對象(實現了接口的newCall方法,運用了工廠模式,不管具體怎麼製作,只要一個Call對象。)

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

繼續看execute方法。首先調用了Dispatcher對象的executed方法。

 @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }

Dispatcher的executed方法很簡單,就是將請求加入同步進行隊列中。

synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

之後還是通過getResponseWithInterceptorChain()方法獲得Response對象。
到這裏,同步方法和異步方法就全部講解完畢。

4.總結

最後以文字形式總結下總體請求流程,幾乎可以當做面試回答去回答面試官了有木有。
首先通過OkHttpClientBuilder創建OkHttpClient實例,之後通過OkHttpClient對象的newCall方法,(傳入創建好的Request對象),去創建RealCall對象。

異步方式調用RealCall對象的enqueue方法並傳入創建好的Callback對象(創建Callback對象需要重寫其OnResponse方法與OnFailure方法分別對應網絡請求成功與失敗),在該方法下,會繼續調用Dispatcher調度器的enqueue方法並傳入通過Callback對象創建的AsynCall對象,在該方法中會判斷是否立即執行該請求,如果可以就加入異步執行隊列,並使用線程池創建一個線程來執行該請求(AsynCall)對象(會調用AsynCall對象的execute方法(不返回Response對象,會根據重定向重試攔截器的isCanceled函數來判斷在其中調用OnResponse方法或OnFailure方法))。否則進入等待隊列

同步方式則直接調用RealCall對象的execute方法,在該方法中調用Dispatcher調度器的executed方法(返回Response對象),直接進入同步執行隊列。

之後兩個execute方法都會調用getResponseWithInterceptorChain方法去獲取Response。在getResponseWithInterceptorChain方法中會遞歸的去處理下一個Interceptor(攔截器),最終返回Response。

本文精髓

1.OkHttpClient實現Call.Factory,負責爲Request創建Call(RealCall)。

2.異步方式中加入到執行隊列後線程池創建線程並執行該請求會調用AsynCall對象的executed方法,這是因爲抽象類NamedRunnable實現了Runnable接口並在run函數中執行execute方法(在類中爲抽象方法)而AsynCall類又繼承了NamedRunnable類,因此執行請求相當於調用AsynCall對象的execute方法。

3.同步方式調用的是RealCall對象的execute方法,在該方法中首先調用Dispatcher的executed方法,將RealCall對象加入同步執行隊列,之後會調用getResponseWithInterceptorChain方法。也就是說同步方式和異步方式調用的最終調用的雖然都是executed方法,但並不是同一個executed方法。網上有的圖片直接會都指向同一個executed,難免會讓讀者疑惑。

4.總結下getResonseWithInterceptorChain方法()做的事情。

  1. 創建一個List(interceptors)用來存儲攔截器
  2. 創建RealInterceptorChain對象,其中傳入的包括interceptorsindex(爲0,從第一個開始)
  3. 調用RealInterceptorChain對象的proceed方法,在該方法中創建新的RealInterceptorChain對象(next),傳入list與index(1)。取出lsit中當前index的攔截器(interceptor),聲明Response對象,調用該攔截器的intercept方法,並傳入新創建的RealInterceptorChain,並將其返回值付給新創建的Response對象。(Response response = interceptor.intercept(next)),在proceed方法的結束會返回Response對象。
  4. 攔截器的intercept方法會繼續調用傳入的RealInterceptorChain對象的proceed方法,這樣就會遞歸處理攔截器鏈中的下一個攔截器。
  5. 遞歸結束後會返回一個Response對象。

流程圖

下圖是我總結的總體請求流程,可以說是非常詳細了。
在這裏插入圖片描述

參考鏈接(優秀文章彙總)

1.okhttp源碼分析(一)——基本流程(超詳細)
2.OkHttp-Request-請求執行流程
3.Okhttp使用詳解
4.Okhttp官方網站
5.拆輪子系列:拆 OkHttp
6.拆輪子:OkHttp 的源碼解析(三):任務分發器(Dispatcher)
7.OkHttp Dispatcher的調度過程分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章