OkHttp 3.14.x 源碼解析-執行流程

本文OkHttp源碼基於3.14.x,版本下載地址:okHttp 3.14.xjava

前言

OkHttp是一個很是優秀的網絡請求框架,使用方便,操做簡單,而且目前比較流行的Retrofit也是默認使用OkHttp。所以從源碼深刻理解OkHttp是很是有必要的。故今天這篇首先將介紹OkHttp請求的執行流程。另外因爲OkHttp從4.x版本開始使用Kotlin來編寫,所以今天的源碼解析是基於Java版的3.14.x版本。git

1、基本使用

既然是開源庫,第一步固然先得添加OkHttp的依賴,另外注意得在清單文件聲明網絡權限。github

implementation 'com.squareup.okhttp3:okhttp:3.14.0'
複製代碼

咱們知道Http經常使用的Http請求有Get和Post,在這裏咱們只以Get請求爲例子進行分析。web

1. 同步GET請求

//在這裏也能夠經過okHttpClient的new操做來建立OkHttpClient實例
//OkHttpClient okHttpClient = new OkHttpClient();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
                    .connectTimeout(100, TimeUnit.SECONDS) //鏈接超時時間,100s
                    .readTimeout(100,TimeUnit.SECONDS) //讀取超時時間,100s
                    .writeTimeout(100,TimeUnit.SECONDS)//寫入超時時間,100s
                    .build();
Request request = new Request.Builder()
        .url("http://wwww.baidu.com")
        .get()//默認就是GET請求,能夠不寫
        .build();
Call call = okHttpClient.newCall(request);
//開啓線程
new Thread(()->{
        try {
            Response response = call.execute();
            //回調響應數據給主線程的調用層
        } catch (IOException e) {
            e.printStackTrace();
        }
	}
}).start();
複製代碼

2. 異步GET請求

//在這裏也能夠經過okHttpClient的new操做來建立OkHttpClient實例
//OkHttpClient okHttpClient = new OkHttpClient();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
                    .connectTimeout(100, TimeUnit.SECONDS) //鏈接超時時間,100s
                    .readTimeout(100,TimeUnit.SECONDS) //讀取超時時間,100s
                    .writeTimeout(100,TimeUnit.SECONDS)//寫入超時時間,100s
                    .build();
final Request request = new Request.Builder()
        .url("http://wwww.baidu.com")
        .get()//默認就是GET請求,能夠不寫
        .build();
Call call = okHttpClient.newCall(request);
//開啓線程
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        //須要回到主線程進行操做
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        //須要回到主線程進行操做
    }
});
複製代碼

比較同步GET請求和異步GET請求,你會發現其實二者區別並非很大,主要就是在最後一步提交請求的方式不一樣,故OkHttp的GET請求的流程能夠總結爲:json

  1. 構造OkhttpClient對象,能夠經過new操做直接構造或者經過構造OkhttpClient內部類Builder對象間接構造
  2. 構造Request對象
  3. 構造Call對象
  4. 提交請求,獲取響應數據Response。若是是同步請求,則經過Call對象的execute方式提交請求;若是是異步請求,則經過Call對象的enqueue方式提交請求

下面咱們將基於源碼來分析這4步操做!設計模式

2、流程

1. 構造OkHttpClient對象,配置屬性

經過上面的分析,咱們知道構造OkHttpClient對象的方式有兩種,因爲直接構造是基於間接構造的基礎上,因此咱們首先看看間接方式是怎麼構造這個OkHttpClient對象的!緩存

1.1 間接構造

經過上面基本方式的介紹,咱們知道首先會構造Builder對象,這個Builder是OkHttpClient類的內部類安全

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;
    @Nullable CertificateChainCleaner certificateChainCleaner;
    HostnameVerifier hostnameVerifier;
    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() {
	  //調度器:經過雙端隊列保存calls,同時在線程池中執行異步請求 
      dispatcher = new Dispatcher();
	  //默認支持的Http協議版本
      protocols = DEFAULT_PROTOCOLS;
	  //okhttp鏈接配置
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
	  //一個call的狀態監聽器
      eventListenerFactory = EventListener.factory(EventListener.NONE);
	  //默認的代理選擇器
      proxySelector = ProxySelector.getDefault();
      if (proxySelector == null) {
        proxySelector = new NullProxySelector();
      }
	  //默認是沒有cookie
      cookieJar = CookieJar.NO_COOKIES;
	  //使用默認的Socket工廠產生Socket
      socketFactory = SocketFactory.getDefault();
	  //安全相關設置
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      certificatePinner = CertificatePinner.DEFAULT;
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;

	  //鏈接池
      connectionPool = new ConnectionPool();
	  //域名解析系統
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      //默認開啓失敗重連
      retryOnConnectionFailure = true;
      callTimeout = 0;
	  //鏈接超時時間,默認10s
      connectTimeout = 10_000;
	  //讀取超時時間,默認10s
      readTimeout = 10_000;
	  //寫入超時時間,默認10s
      writeTimeout = 10_000;
	  //和WebSocket有關。爲了保持長鏈接,咱們必須間隔一段時間發送一個ping指令進行保活;
      pingInterval = 0;
    }
複製代碼

經過註釋咱們能夠知道Builder的構造器中初始化了一系列重要參數的默認值,好比調度器Dispatcher。也能夠在裏面找到咱們熟悉的鏈接超時時間,讀取超時時間,寫入超時時間值的初始化。那咱們要修改這些默認值該怎麼辦呢?沒錯,Builder還提供了一系列方法供咱們修改默認值cookie

OkHttpClient.Builder

public Builder readTimeout(long timeout, TimeUnit unit) {
      readTimeout = checkDuration("timeout", timeout, unit);
      return this;
    }


    public Builder writeTimeout(long timeout, TimeUnit unit) {
      writeTimeout = checkDuration("timeout", timeout, unit);
      return this;
    }

    public Builder connectTimeout(long timeout, TimeUnit unit) {
      connectTimeout = checkDuration("timeout", timeout, unit);
      return this;
    }
......
複製代碼

最後再經過Builder的build來構造OkHttpClient對象

OkHttpClient.Builder#build

public OkHttpClient build() {
      //this爲自身Builder對象 
      return new OkHttpClient(this);
    }
複製代碼

能夠發現其實一系列下來就是利用Builder設計模式來構造OkHttpClient對象,而後咱們來看看OkHttpClient又作了哪些事

OkHttpClient#構造器

//設置默認值
  OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    this.protocols = builder.protocols;
    this.connectionSpecs = builder.connectionSpecs;
    this.interceptors = Util.immutableList(builder.interceptors);
    this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
    .....
  }
複製代碼

OkHttpClient作的事情很簡單,就是將Builder對象的值賦值到本身的成員變量中,因而OkHttpClient對象構形成功。

1.2 直接構造

直接構造只需一行代碼

OkHttpClient okHttpClient = new OkHttpClient();

咱們繼續看下OkHttpClient的構造器

OkHttpClient#構造器

public OkHttpClient() {
  	//構建了Builder對象,而後調用Builder對象爲參數的構造器
    this(new Builder());
  }
複製代碼

看到這裏是否忽然明白,原來直接構造也是構造了Builder對象的,而後再將Builder對象的默認值賦值給OkHttpClient對象的成員變量中,這麼一看好像直接構造和間接構造好像也沒什麼區別啊?

真的是這樣嗎,其實並非,細看的話你就會發現經過直接構造只能構造出默認值,而經過間接構造就能夠修改默認值。而且若是想添加攔截器的話,就必須得經過間接構造。

1.3 小結

構造OkHttpClient對象有兩種方式,二者的使用場景以下:

  • 若是直接使用默認值的話,能夠直接使用new操做來構造OkHttpClient對象。
  • 若是想自行設置其中的屬性,好比鏈接超時時間或者添加攔截器的話,必須得首先構造Builder對象,而後設置相關的屬性,最後調用Builder對象的build來構造OkHttpClient對象。

2. 構造Request對象,配置屬性

Request對象的構造也是經過Builder模式來構造的,代碼來講也是比較容易看懂的。咱們首先看Request中的Builder的構造方法

Request.Builder#構造方法

public Builder() {
	  //默認是GET請求 
      this.method = "GET";
      this.headers = new Headers.Builder();
    }

複製代碼

能夠發如今Builder構造中已經設置了GET的請求方法了,因此若是是GET請求的話後面其實能夠不須要本身設置了。而後咱們還須要設置請求地址。

Request.Builder#url

/** *設置請求地址 */
    public Builder url(String url) {
      if (url == null) throw new NullPointerException("url == null");

      //判斷是http仍是https地址
      if (url.regionMatches(true, 0, "ws:", 0, 3)) {
        url = "http:" + url.substring(3);
      } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
        url = "https:" + url.substring(4);
      }
      //解析地址,判斷地址是否有效 
      return url(HttpUrl.get(url));
    }

複製代碼

設置完請求地址後,咱們須要經過build方法來構造Request對象

Request.Builder#build

public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      //構建Request對象
      return new Request(this);
    }

複製代碼

Request#構造方法

Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tags = Util.immutableMap(builder.tags);
  }

複製代碼

在Request的構造器中將Builder的值賦值到本身的成員變量中,因而Request構形成功。

3. 構造Call對象

經過基本使用咱們知道Call對象會調用OkHttpClient對象的newCall來構造

OkHttpClient#newCall

@Override public Call newCall(Request request) {
    //this爲自身OkHttpClient對象 
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }

複製代碼

在這個方法中,其實主要目的是將前面構造的OkHttpClient對象和Request對象傳給RealCall的newRealCall方法中

RealCall#newRealCall

static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.transmitter = new Transmitter(client, call);
    return call;
  }

複製代碼

而這個newRealCall方法也是很簡單,就是構造了RealCall對象,RealCall對象保存了OkHttpClient對象和Request對象,因此最後構造出來的Call對象其實就是RealCall對象。

4.提交請求, 獲取響應數據Response

終於來到執行流程的重頭戲了,咱們首先分析同步請求的提交。

4.1 同步

同步請求是調用了Call對象的execute來提交請求,不過需注意的是這種方式會阻塞線程,因此在使用的時候應該開啓子線程而後再調用execute,不然就會引發ANR。經過上面的分析咱們已經知道Call對象實際上是RealCall對象,因此會調用RealCall對象的execute方法來提交請求。

RealCall#execute

// 同步Call,一個Call只能被執行一次
  @Override public Response execute() throws IOException {
    synchronized (this) {
	  //executed表示是否執行
	  //此時表示爲再次被調用,可是一個Call只能被執行一次,因此拋出異常
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.timeoutEnter();
    transmitter.callStart();
    try {
	  //此時this爲RealCall對象
      client.dispatcher().executed(this);
	  //調用各攔截器對請求進行處理
      return getResponseWithInterceptorChain();
    } finally {
	  //不論是否執行成功都關閉當前請求任務
      client.dispatcher().finished(this);
    }
  }

複製代碼

仔細觀察execute方法,你就會發現其實這個方法主要作了三件事:

  1. 將請求加入到同步任務的執行隊列
  2. 調用各攔截器對請求進行處理,獲取響應數據
  3. 關閉當前請求任務

咱們接下來將根據這3件事來進行分析!

1. 將請求加入到同步任務的執行隊列

在execute方法中經過一行代碼來實現這個任務

client.dispatcher().executed(this);

此時的client此時就是OkHttpClient對象,dispatcher就是在構造OkHttpClient對象時初始化的Dispatcher對象,因此咱們接下來須要看Dispatcher的executed方法

Dispatcher#executed

//同步任務的執行隊列
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
  //同步請求,將該同步任務添加到正在執行的Deque中
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

複製代碼

在這裏runningSyncCalls是ArrayDeque對象,ArrayDeque既能夠做爲棧使用,效率高於Stack,又能夠做爲隊列使用,效率也比LinkedList更好一點,在這裏就是看成雙端隊列來使用,因此runningSyncCalls就是同步任務的執行隊列,而Dispatcher的executed方法也很簡單,就是簡單的將同步請求添加到執行隊列的隊尾。

2. 調用各攔截器對請求進行處理,獲取響應數據

下列只介紹攔截器鏈的大概調用流程。因爲攔截器鏈算的上是整個框架的精髓,考慮到篇幅問題,對攔截器鏈的詳細介紹將會在下一篇文章進行分析。

咱們回到RealCall的execute方法,經過調用getResponseWithInterceptorChain來獲取響應數據

RealCall#getResponseWithInterceptorChain

Response getResponseWithInterceptorChain() throws IOException {
    //添加一系列的攔截器,注意添加的順序
    List<Interceptor> interceptors = new ArrayList<>();
	//添加構造HttpClient對象時設置的攔截器
    interceptors.addAll(client.interceptors());
	//重試,重定向攔截器
    interceptors.add(new RetryAndFollowUpInterceptor(client));
	//橋接攔截器,橋接應用層和網絡層,添加必要的頭
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
	//緩存攔截器,從緩存中拿數據
    interceptors.add(new CacheInterceptor(client.internalCache()));
	//網絡攔截器,創建網絡鏈接
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
	  //經過okhttpClient構造時的addNetworkInterceptor攔截器
      interceptors.addAll(client.networkInterceptors());
    }
	//服務器請求攔截器,向服務器發起請求獲取數據
    interceptors.add(new CallServerInterceptor(forWebSocket));
    //構建責任鏈的第一個結點,第4個參數爲0,即該結點的位置爲0
    //結點對應了相對位置的攔截器
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
	  //調用proceed方法來處理責任鏈 
	  //責任鏈設計模式
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
  }

複製代碼

在這個方法中,首先會添加一系列的攔截器,按添加的順序分別是:

  • 構造okHttpClient對象時經過addInterceptor配置的全局攔截器
  • 重試,重定向攔截器
  • 橋接攔截器,橋接應用層和網絡層,添加必要的頭
  • 緩存攔截器,從緩存中拿數據
  • 網絡攔截器,創建網絡鏈接
  • 構造okhttpClient對象時經過addNetworkInterceptor配置的非網頁請求攔截器
  • 服務器請求攔截器,向服務器發起請求獲取數據

能夠看出各攔截器各司其職,因爲在這裏使用的是相似責任鏈的設計模式,因此在這裏須要特別注意添加的順序,由於添加的順序就是執行攔截器的順序。

而後接着構造了攔截器鏈的頭結點,這裏需注意其中的第4個參數爲0,即該結點對應了0位置的攔截器。

最後調用了該結點的proceed方法來處理該請求。讓咱們看看這個proceed方法

RealInterceptorChain#proceed

@Override public Response proceed(Request request) throws IOException {
    return proceed(request, transmitter, exchange);
    }


    public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange) throws IOException {
    //傳入的index若是大於添加的攔截器列表的大小,則拋出異常 
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    ...

    //構造了責任鏈的下一個結點,重點關注第四個參數,傳入的爲當前index+1
    RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
        index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
	//拿到當前位置的攔截器
    Interceptor interceptor = interceptors.get(index);
	//調用攔截器的intercept方法來處理該請求,並將下一請求做爲參數傳遞進去
    Response response = interceptor.intercept(next);
    .....

    return response;
  }

複製代碼

這個方法首先構造了頭結點的下一個結點,其位置爲index+1。而後根據index和攔截器列表拿到當前位置的攔截器,最後再調用當前攔截器的intercept方法來處理請求,這裏還需特別注意的是再處理請求時也將攔截器鏈的下一節點傳進去了。接着就是責任鏈模式的傳遞過程,在這裏咱們假設構造OkHttpClient沒有添加全局攔截器,因此根據上面的分析,頭結點的攔截器應該是RetryAndFollowUpInterceptor,錯誤重連攔截器。

RetryAndFollowUpInterceptor#intercept

@Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Transmitter transmitter = realChain.transmitter();
    .......
      Response response;
      boolean success = false;
      try {
        //realChain就是下一節點
        response = realChain.proceed(request, transmitter, null);
        success = true;
      } 
      ......
    }
  }

複製代碼

這個攔截器的詳細工做留到下篇文章分析,這裏咱們只分析大概的執行流程。能夠發現這個攔截器還會調用下一結點的proceed方法,這樣就能夠實現遞歸遍歷在一開始添加的攔截器列表中Interceptor中的intercept方法。當遍歷到最後一個服務器請求攔截器時,會直接返回Response,而後又將這個Response一直向上返回,最後返回攔截器鏈處理事後的響應數據。

3. 關閉當前請求任務

無論有沒有獲得響應數據,最後咱們都得關閉當前的請求任務,咱們繼續看回RealCall的execute方法

@Override public Response execute() throws IOException {
    synchronized (this) {
	.....
    try {
      client.dispatcher().executed(this);
      return getResponseWithInterceptorChain();
    } finally {
	  //不論是否執行成功都關閉當前請求任務
      client.dispatcher().finished(this);
    }
  }

複製代碼

這時候會調用Dispathcer的finished方法來關閉當前請求任務,需注意this爲RealCall對象

Dispatcher#finished

//同步請求
  void finished(RealCall call) {
    finished(runningSyncCalls, call);
  }

  //同步和異步請求
  private <T> void finished(Deque<T> calls, T call) {
    Runnable idleCallback;
    synchronized (this) {
	  //將請求從執行隊列中移除
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      idleCallback = this.idleCallback;
    }
    //判斷是否有須要執行的異步任務
    boolean isRunning = promoteAndExecute();
	//當全部請求執行完畢後執行這個runnable
    if (!isRunning && idleCallback != null) {
      idleCallback.run();
    }
  }

複製代碼

從finished方法能夠看出,其實關閉當前請求任務就是將該請求從執行隊列中移除,而promoteAndExecute方法實際上是異步任務完成後處理,同步任務執行promoteAndExecute方法是沒有效果的。

到這裏同步獲取響應數據分析完畢!接着讓咱們繼續分析異步請求是如何獲取響應數據的。

4.2 異步

異步是經過ReaCall的enqueue來提交請求的,咱們看看enqueue方法

RealCall#enqueue

//異步call,一個call只能被執行一次
  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
	  //一個call只能被執行一次 
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.callStart();
	//調用調度器Dispatcher的enqueue方法,此時傳入的是AsyncCall對象,其實現了Runnable接口
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

複製代碼

這裏的responseCallback其實就是咱們在使用異步請求時的匿名Callback類的回調對象,在這裏根據Callback回調接口對象構造了AsyncCall對象,而後調用了Dispatcher的enqueue方法

Dispathcer#enqueue

//異步任務的就緒隊列
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  void enqueue(AsyncCall call) {
    synchronized (this) {
	  //將請求放到就緒隊列 
      readyAsyncCalls.add(call);

      //判斷是否爲WebSocket
      if (!call.get().forWebSocket) {
        AsyncCall existingCall = findExistingCallWithHost(call.host());
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
      }
    }
    //準備執行
    promoteAndExecute();
  }

複製代碼

跟同步請求同樣,這裏的readyAsyncCalls也是雙端隊列,不過readyAsyncCalls並非執行隊列,而是異步任務的就緒隊列,而後調用promoteAndExecute方法

Dispathcer#promoteAndExecute

private int maxRequests = 64;//容許執行的最大請求數
  private int maxRequestsPerHost = 5;//一個主機容許執行的最大請求數
  //異步任務的執行隊列
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
  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();
		//若是正在執行的異步數量達到了最大要求64,則跳出循環 
        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
        //若是單個Host正在執行的請求大於最大要求5個,則該請求跳過
        if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.

		//不然,將該請求從異步就緒隊列中移除
        i.remove();
        asyncCall.callsPerHost().incrementAndGet();
		//添加到異步任務的集合中
        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;
  }

複製代碼

這個方法首先要遍歷就緒隊列,OkHttp對執行任務的數量作了要求:

  • 當前正在執行的異步任務不能超過64個
  • 一個主機正在執行的異步任務不能超過5個

若是知足上述條件的話,就將當前請求從異步任務的就緒隊列中移除,而且添加到異步任務的集合和異步任務的執行隊列中。接着就繼續遍歷當前要執行的異步任務,而後執行異步任務。

在執行異步任務時能夠發現這裏調用了executorService方法。咱們來瞧瞧

Dispatcher#executorService

public synchronized ExecutorService executorService() {
    //單例實現:返回了一個執行線程池的對象
    if (executorService == null) {
	  //核心線程數爲0,空閒存活期60s
	  //容納的最大線程數爲Max,實際狀況下並不會超過64,由於上面的enqueue已經作了判斷 
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

複製代碼

這個方法也挺簡單的,就是返回了一個執行線程池的對象,接着就會調用AsyncCall的executeOn方法

RealCall.AsyncCall#executeOn

void executeOn(ExecutorService executorService) {
      assert (!Thread.holdsLock(client.dispatcher()));
      boolean success = false;
      try {
	  	//線程池執行任務,this爲AsyncCall對象
        executorService.execute(this);
        success = true;
      } catch (RejectedExecutionException e) {
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        transmitter.noMoreExchanges(ioException);
        responseCallback.onFailure(RealCall.this, ioException);
      } finally {
        if (!success) {
          client.dispatcher().finished(this); // This call is no longer running!
        }
      }
    }

複製代碼

這個方法就是調用了剛傳進去的執行線程池的對象來執行任務,而後就會執行AsyncCall的run方法。而後你就會發現AsyncCall中根本沒有run方法,想必機智的你應該想到下一步應該怎麼作了,沒錯!在AsyncCall父類找這個方法

NamedRunnable

final class AsyncCall extends NamedRunnable {
      ....
  }

public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }
  //AsyncCall沒有run方法,在父類找到run
  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
	  //重點關注
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }
  //空實現,實際調用了子類,即AsyncCall的execute方法 
  protected abstract void execute();
}



複製代碼

果真如此,可是在NamedRunnable的run方法中又會從新調用AsyncCall的execute方法,兜兜轉轉,又回到了AsyncCall中

AsyncCall#execute

@Override protected void execute() {
      boolean signalledCallback = false;
      transmitter.timeoutEnter();
      try {
	  	//調用各攔截器對請求進行處理,獲取數據
        Response response = getResponseWithInterceptorChain();
        signalledCallback = true;
	    //獲取數據成功,調用callback的onResponse將響應數據返回給調用層
        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 {
          //若是捕獲到異常,回調Callback對象的onFailure方法
          responseCallback.onFailure(RealCall.this, e);
        }
      } catch (Throwable t) {
        cancel();
        if (!signalledCallback) {
          IOException canceledException = new IOException("canceled due to " + t);
          canceledException.addSuppressed(t);
		  //捕獲到異常,回調Callback對象的onFailure方法
          responseCallback.onFailure(RealCall.this, canceledException);
        }
        throw t;
      } finally {
	  	//不管是否獲取到數據,最後都要調用Dispatcher對象的finished方法
        client.dispatcher().finished(this);
      }
    }
  }

複製代碼

仔細看這個方法,你會發現這個方法其實主要工做有三件:

  1. 調用各攔截器對請求進行處理,獲取響應數據
  2. 將請求結果回調給調用層
  3. 關閉當前請求任務

按照老套路,咱們將對這三件工做逐個分析。

1. 調用各攔截器對請求進行處理,獲取響應數據

這個分析與同步請求時的分析是同樣,這裏再也不進行分析,忘記的小夥伴能夠向上翻翻

2. 將請求結果回調給調用層

這裏的回調仍然是在子線程中,在實際使用時須要回到主線程對響應數據進行操做。

當獲取到響應數據時,會調用responseCallback的onResponse將響應數據回調出去,這裏的responseCallback就是咱們在實際使用時用匿名內部類的構造的Callback對象,經過回調就能執行Callback中的onResponse方法。若是捕獲到異常就回調Callback的onFailure方法。

3. 關閉當前請求任務

無論請求結果是否成功,都要關閉當前的請求任務,這裏跟同步關閉請求任務同樣,調用了Dispatcher的finished,不過注意參數不同,這裏傳遞的參數爲AsyncCall對象

Dispatcher#finished

//異步請求
  void finished(AsyncCall call) {
    call.callsPerHost().decrementAndGet();
    finished(runningAsyncCalls, call);
  }

  //同步和異步請求
  private <T> void finished(Deque<T> calls, T call) {
    Runnable idleCallback;
    synchronized (this) {
	  //將請求從執行隊列中移除
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      idleCallback = this.idleCallback;
    }
    //判斷是否有須要執行的異步任務
    boolean isRunning = promoteAndExecute();
	//當全部請求執行完畢後執行這個runnable
    if (!isRunning && idleCallback != null) {
      idleCallback.run();
    }
  }

複製代碼

首先仍是得將請求完任務從異步任務的執行隊列中移除,而後還須要判斷是否有須要執行的任務,這裏又會調用promoteAndExecute方法,這個方法是否似曾相識呢?其實這個方法已經在上面分析過了,就是接着執行下一個異步任務。

4.3 小結

同步和異步的GET請求的區別就在於提交請求這一流程不一樣,二者提交請求的不一樣之處在於:

  • 異步提交請求比同步多了一個就緒隊列,緣由是異步一次可以執行多個任務,而同步一次只能執行一個任務
  • 關閉當前請求任務時,同步只是將當前請求任務從同步任務的執行隊列中刪除,而異步將請求從異步任務的執行隊列中移除後,還會遍歷就緒隊列來執行下一次任務。

總結

到這裏OkHttp的同步和異步請求的執行流程就分析完畢了。

參考文章:

相關文章
相關標籤/搜索