OkHttp源碼分析(一)請求流程

這兩天那些事情也弄的差不多了,也就回頭又看了看OkHttp源碼。它太多地方值得深入和學習。也為了提高自己,于是又從頭看了一遍,也希望有更多的收獲。目前已在這家公司超過三年,說不出什么感覺,,不知道是不是麻木了。。。。?。。?!

源碼分析

從發(fā)起一個簡單的http網(wǎng)絡請求開始

//構(gòu)造OkhttpClient對象
OkHttpClient okHttpClient= new OkHttpClient();
//構(gòu)造請求體 url ,tag ,header ext...
Request request = new Request.Builder().url("http://www.baidu.com").build()     
 //發(fā)起一個異步請求
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
          Log.d(tag,e.toString());
}

@Override
public void onResponse(Call call, Response response) throws IOException {
          //處理響應結(jié)果
          int code = response.code();
          ResponseBody body =  response.body();
          Log.d(tag,body.string());
 });

從上面的例子,我們可以知道使用OkHttp發(fā)起一個http請求主要有三步:
1,構(gòu)造一個OkHttpClient對象
2,構(gòu)造請求體(包含url,tag,header,etc...)
3,發(fā)起請求
當然最后一步就是等待服務器響應,根據(jù)狀態(tài)碼處理響應體。
可以看出,使用OkHttp發(fā)起一個網(wǎng)絡請求,是非常簡單的,僅需三步就可以拿到我們服務器的響應結(jié)果,在這么簡單的背后,OkHttp在背后到底為我們做了什么呢?我們提交的請求是怎么被執(zhí)行的呢?響應是如何拿到的呢?其實我們也可以設想一下,內(nèi)部一定是開啟了一個子線程去幫我們處理請求,必定我們從學Android第一天就知道,耗時任務不能在主線程被執(zhí)行,可能猜到這里,我們又會拋出疑問,在哪起的線程,任務在哪被執(zhí)行,又或者是否使用了線程池執(zhí)行任務等等......
帶著這一連串的疑問,我們先看一下請求的提交流程

請求提交流程

從上面的例子,我們看到通過調(diào)用OkHttpClient對象的newCall()方法然后調(diào)用enqueue提交請求,我們進入OkHttpClient,查看一下newCall()方法的源碼:

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

newCall()方法內(nèi)部調(diào)用了RealCall對象的newRealCall()方法,通過newCall()方法的返回值,我們知道 RealCall.newRealCall()返回了一個Call對象。我們繼續(xù)跟蹤RealCall的源碼,發(fā)現(xiàn)RealCall是okhttp3.Call接口的實現(xiàn),okhttp3.Call表示一個等待執(zhí)行的請求,并且它只能被執(zhí)行一次,為什么只能被執(zhí)行一次,我們后面分析原因,暫且放一放,okHttp3.Call中的源碼:

public interface Call extends Cloneable {
 //返回請求關(guān)聯(lián)的Request對象,前面例子中構(gòu)造的Request對象
  Request request();
//立即執(zhí)行請求,等待并阻塞直到返回請求結(jié)果
  Response execute() throws IOException;
//請求入列,異步執(zhí)行,
  void enqueue(Callback responseCallback);
//取消一個請求,
  void cancel();

  boolean isExecuted();

  boolean isCanceled();


  Timeout timeout();

  Call clone();

  interface Factory {
    Call newCall(Request request);
  }
}

從前面的例子okHttpClient.newCall(request).enqueue(),發(fā)起請求,enqueue()這個方法實際是定義在Call中的,我們跟蹤發(fā)現(xiàn)在整個OkHttp源碼中,Call的唯一實現(xiàn)就是RealCall,它表示一個準備好被執(zhí)行的請求,可以發(fā)起請求,取消請求等等...
拿到okHttp3.Call唯一實例,RealCall對象后,我們調(diào)用它的enqueue()方法:

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

前面我們提到okhttp3.Call表示一個等待執(zhí)行的請求,并且它只能被執(zhí)行一次,看到這里,問題就解決了,如果第二次執(zhí)行就會拋出Already Executed異常,原因就在這里。上面最核心的代碼就是
client.dispatcher().enqueue(new AsyncCall(responseCallback));,應該是整個請求的核心地方,它幫我們做了兩件事:
1,創(chuàng)建一個AsyncCall對象
2,調(diào)用Dispatcher中的enqueue方法,將請求入列

AsyncCall是什么,跟進去看一下源碼:

  final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

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

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

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }


    void executeOn(ExecutorService executorService) {
       //...
    }

    @Override protected void execute() {
       //...
    }
  }

我們跟蹤發(fā)現(xiàn),AsyncCall 其實就是一個Runnable,用于執(zhí)行任務。

我們還是回到我們之前提到的 client.dispatcher().enqueue(new AsyncCall(responseCallback));
client.dispatcher()返回了調(diào)度器Dispatcher對象,Dispatcher是OkHttp中相當核心的類:

  //同時最多發(fā)起64個請求
  private int maxRequests = 64;
  //同一host做多發(fā)起5個請求
  private int maxRequestsPerHost = 5;
  private @Nullable Runnable idleCallback;

  /** Executes calls. Created lazily. */
  //將會異步創(chuàng)建的線程池
  private @Nullable ExecutorService executorService;

  /** Ready async calls in the order they'll be run. */
  //等待異步執(zhí)行的請求隊列
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  //正在執(zhí)行的異步請求隊列(其中包括取消沒有完成的請求)
  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<>();

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

  public Dispatcher() {
  }

  public synchronized ExecutorService executorService() {
    if (executorService == null) {
      //默認核心線程為0,最多數(shù)量不限制,消息隊列為SynchronousQueue,有請求時會不斷創(chuàng)建線程
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }
//...

最后調(diào)用了Dispatcher分發(fā)器的enqueue()方法:

  void enqueue(AsyncCall call) {
    synchronized (this) {
      readyAsyncCalls.add(call);
    }
    promoteAndExecute();
  }

上面我們可以得到兩個信息:
1,加入等待的readyAsyncCalls隊列
2,推進并執(zhí)行(暫且這樣理解,字面翻譯)

promoteAndExecute()字面是推進并執(zhí)行,我們跟進去一探究竟,到底是怎么執(zhí)行的:

  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 asyncCall = i.next();
        //如果當前請求數(shù)大于64,直接跳出
        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
        //如果當前主機連接數(shù)大于5,繼續(xù)遍歷
        if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.

        i.remove();
        //加入executableCalls集合
        executableCalls.add(asyncCall);
        //加入正在執(zhí)行的隊列
        runningAsyncCalls.add(asyncCall);
      }
      //標識是否正在執(zhí)行請求隊列
      isRunning = runningCallsCount() > 0;
    }

    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      //調(diào)用AsyncCall.executeOn()開始執(zhí)行請求任務
      asyncCall.executeOn(executorService());
    }

    return isRunning;
  }

我們從promoteAndExecute()中知道:
1,在OkHttp中一般會存在一個Dispather對象,并且一個okHttpClienet能發(fā)起的最多請求是Diapatcher中定義的64個
2,同一個主機最多能發(fā)起的求請是5個
3,Dispatcher中有三個隊列保持同步和異步請求
4,OkHttp中默認的線程核心數(shù)為0,最多數(shù)量Int的最大值,也就是不限制,消息隊列為SynchronousQueue,有請求時會不斷創(chuàng)建線程

任務是怎樣被執(zhí)行的呢?在方法的最后,有一個列表executableCalls存入了要執(zhí)行的任務,然后遍歷,調(diào)用了 asyncCall.executeOn(executorService());這里面又做了什么:

  void executeOn(ExecutorService executorService) {
      assert (!Thread.holdsLock(client.dispatcher()));
      boolean success = false;
      try {
        executorService.execute(this);
        success = true;
      } catch (RejectedExecutionException e) {
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        eventListener.callFailed(RealCall.this, ioException);
        responseCallback.onFailure(RealCall.this, ioException);
      } finally {
        if (!success) {
          client.dispatcher().finished(this); // This call is no longer running!
        }
      }
    }

還記得我們之前提到的AsyncCall,他本質(zhì)是一個Runnable(),這里我們還是看不出,我們只是知道AsyncCall實現(xiàn)了NamedRunnable,NamedRunnable是個啥:

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

是不是恍然大悟,當我們在executeOn()中調(diào)用了executorService.execute(this);實質(zhì)就是把我們的任務加入到了executorService并執(zhí)行任務,到這里我么們了解了一個請求從提交到執(zhí)行背后所經(jīng)歷的所有流程,用一張小圖總結(jié)一下:


請求流程圖.png

最后我們還是分析OkHttp線程池配置參數(shù)情況,

//核心線程書,最大線程數(shù),閑置時間,SynchronousQueue,線程池工程                             
SynchronousQueue<Runnable> synchronousQueue = new SynchronousQueue(); 
ExecutorService executorService =                                     
        new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECO
                synchronousQueue,                                     
                Util.threadFactory("OkHttp Dispatcher", false));      

1,OkHttp線程池配置:
1,核心線程數(shù)為0
2,最大線程數(shù),不限制
3,線程閑置時間為60s
4,隊列為SynchronousQueue
5,線程池工廠,無守護線程
這樣配置有什么特點呢?答:高并發(fā),最大吞吐量,這也是我們前面提到有請求時不斷創(chuàng)建線程的原因。
2,可不可以換成別的隊列代替SynchronousQueue?先看下面這個例子:

 //核心線程書,最大線程數(shù),閑置時間,SynchronousQueue,線程池工程                                                                   
//SynchronousQueue<Runnable> synchronousQueue = new SynchronousQueue();                                     
ArrayBlockingQueue<Runnable> arrayBlockingQueue = new ArrayBlockingQueue<>(1);                              
ExecutorService executorService =                                                                           
        new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,                                  
                arrayBlockingQueue,                                                                         
                Util.threadFactory("OkHttp Dispatcher", false));                                            
  executorService.execute(()->{                                                                             
      System.out.println("任務一");                                                                            
      for (;;);                                                                                             
  });                                                                                                       

  executorService.execute(()->{                                                                             
      System.out.println("任務二");                                                                            
  });                                                                                                       
image.png

我們發(fā)現(xiàn)任務二,永遠無法執(zhí)行,所以ArrayBlockingQueue不能代替SynchronousQueue,
LinkedBlockingQueue可不可以代替?它的內(nèi)部實現(xiàn)是使用鏈表,區(qū)別是我們初始化的時候可以不傳容量,如果不傳容量為默認的Integer.MAX_VALUE,如果我現(xiàn)在往隊列里,放了一百個任務,后面的99個永遠得不到執(zhí)行。
從上面分析,就可以知道為什么OkHttp使用了SynchronousQueue并且沒有核心線程數(shù)的原因。

總結(jié)

通過上面的分析,主要弄明白了OkHttp發(fā)起一個請求的基本流程,以及線程池為什么要這樣配置,總的來說發(fā)起請求流程還是非常容易理解的,后續(xù)我們繼續(xù)分析OkHttp的攔截器,也是它的核心。如果有疑問,歡迎留言討論。

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

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

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