Okhttp源碼學習一(基本請求流程)

最近學習了一下okhttp的源碼,發(fā)現(xiàn)okhttp是真滴復雜。因為okhttp是一個網(wǎng)絡請求庫,它涉及了網(wǎng)絡請求的方方面面,比如:http協(xié)議,socket通信,計算機網(wǎng)絡,線程的調(diào)度等等。所以我們不扣具體的實現(xiàn)細節(jié),只學習其中的原理和大概實現(xiàn),okhttp的內(nèi)容很多,所以分篇學習記錄。

okhttp的基本使用

 OkHttpClient.Builder builder=new OkHttpClient.Builder();
 OkHttpClient okHttpClient = builder.build();    //構造 OkHttpClient

     //構造get請求
    Request request = new Request.Builder()
            .get()  //Method GET
            .url("https://www.baidu.com")
            .build();    //構造請求信息

    //構造post請求
    RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"),
            "json");
    Request request = new Request.Builder()
            .post(body) //Method POST
            .url("https://www.baidu.com")
            .build();    //構造請求信息
  
  //發(fā)起同步請求
    try {
           Response response=okHttpClient.newCall(request).execute();    //發(fā)起同步請求
    } catch (IOException e) {
           e.printStackTrace();
    }

    //發(fā)起異步請求
    okHttpClient.newCall(request)
            .enqueue(new Callback() {    //發(fā)起異步請求
                @Override
                public void onResponse(final Call call, final Response response) throws IOException {
                    //成功拿到響應
                    int code = response.code();
                    ResponseBody body = response.body();
                    String string = body.string();
                    Log.d("hx","code:"+code+"body:"+string);
                }

                @Override
                public void onFailure(final Call call, final IOException e) {
                    e.printStackTrace();
                }
            });

通過基本的get,post,同步或異步請求示例,可以看出okhttp的請求流程:

  • 1.構造OkhttpClient
  • 2.構造請求信息Request(get或post)
  • 3.執(zhí)行請求(同步或異步)

請求流程非常簡單,只有3步,這是okhttp幫我們封裝好的。但具體的請求原理,內(nèi)部怎么實現(xiàn)的呢?下面進入源碼看看

Okhttpclient

通過流程我們知道,要請求首先要構造一個okhttpClient對象。看一下他的構造方法:

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

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對象的創(chuàng)建需要傳一個Builder對象,它的無參構造函數(shù)其實還是調(diào)用帶有Builder參數(shù)的構造函數(shù)。Builder是OkhttpClient的靜態(tài)內(nèi)部類,通過OkhttpClient的構造函數(shù)可以看到,okhttpClient對象在創(chuàng)建的時候需要初始化很多的網(wǎng)絡請求配置,所以就采用了建造者模式,方便調(diào)用者使用鏈式調(diào)用。

看一下靜態(tài)內(nèi)部類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;
......

public Builder() {
  dispatcher = new Dispatcher();
  protocols = DEFAULT_PROTOCOLS;
  connectionSpecs = DEFAULT_CONNECTION_SPECS;
  eventListenerFactory = EventListener.factory(EventListener.NONE);
  proxySelector = ProxySelector.getDefault();
  cookieJar = CookieJar.NO_COOKIES;
  socketFactory = SocketFactory.getDefault();
  hostnameVerifier = OkHostnameVerifier.INSTANCE;
  ....
}

可以看到,使用 OkhttpClient okhttpClient=new OkhttpCient()這種方式創(chuàng)建的okhttpClient對象,會使用okhttp的默認配置。包括在前面的基本使用中使用的方式:

OkHttpClient.Builder builder=new OkHttpClient.Builder();
OkHttpClient okHttpClient = builder.build();    //構造 OkHttpClient

看一下Builder類的build();

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

使用默認配置的話兩者都一樣可以創(chuàng)建okHttpClient對象。但是如果想自定義okhttp請求過程中的配置的話,就要通過builder.build()的方式創(chuàng)建。okhttp有很多的優(yōu)點,其中之一就是請求配置可以高度定制:

builder配置.png

圖中列出的方法都是可以配置的屬性,其實還有很多沒列出來,有興趣的自己去文檔或源碼吧

Request

Request代表著一個請求信息

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

private volatile CacheControl cacheControl; // Lazily initialized.

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

通過它的成員可以看到,request包含的請求信息有:請求的url,請求方法method,請求頭header,請求體requestBody等。request的構造函數(shù)里面需要傳一個內(nèi)部類對象builder,request可以通過其內(nèi)部類對象builder修改其攜帶的請求信息

執(zhí)行請求

看一下執(zhí)行請求 okHttpClient.newCall(request)的源碼:

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

newCall(Request) 方法調(diào)用了 RealCall.newRealCall() 方法:

static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
  RealCall call = new RealCall(client, originalRequest, forWebSocket);
  call.eventListener = client.eventListenerFactory().create(call);
  return call;
}

okHttpClient.newCall(request)最終返回了一個RealCall對象。RealCall實現(xiàn)了Call接口,Call接口代表著一個準備被執(zhí)行的請求任務:

 public interface Call extends Cloneable {
    //返回這個call初始化時傳入的原始request
    Request request();
    //同步執(zhí)行請求任務(立即執(zhí)行),阻塞拿到響應
    Response execute() throws IOException;
    //異步執(zhí)行請求任務(通過線程池提交執(zhí)行)
    void enqueue(Callback responseCallback);
    //是否已執(zhí)行完畢
    boolean isExecuted();
    //是否取消
    boolean isCanceled();

    okhttp3.Call clone();

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

可以看到前面okhttp用法示例中發(fā)起同步請求的方法:

  //發(fā)起同步請求
Response response=okHttpClient.newCall(request).execute();    //發(fā)起同步請求

以及異步請求的方法:

 okHttpClient.newCall(request) .enqueue(new Callback() {.... }  //發(fā)起異步請求

都是在Call中定義的. 拿到了Call的實例,RealCall 對象后,就可以發(fā)起請求了。首先看一下同步請求:

同步請求

 //RealCall.execute()
 @Override
 public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
  try {
    //實際調(diào)用的是dispatch.execute()
    client.dispatcher().executed(this);
    //執(zhí)行請求,拿到響應結果
    Response result = getResponseWithInterceptorChain();
    if (result == null) throw new IOException("Canceled");
    return result;
  } catch (IOException e) {
    eventListener.callFailed(this, e);
    throw e;
  } finally {
    //結束請求
    client.dispatcher().finished(this);
 }
}

同步請求做了三步操作:

  • 1.調(diào)用dispatcher的execute()
  • 2.執(zhí)行請求任務
  • 3.結束請求

再看一下異步請求:

異步請求

// //RealCall.enqueue()
@Override 
public void enqueue(Callback responseCallback) {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  eventListener.callStart(this);
  //實際是調(diào)用dispatcher的enqueue()方法
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

很簡單,只是調(diào)用dispatcher的enqueue().所以不管是同步請求還是異步請求,都會去調(diào)用Dispatcher的相關方法。Dispatcher是負責請求任務的調(diào)度,是比較重要的一個類,大概的看一下:

public final class Dispatcher {
  //最大并發(fā)數(shù),最多發(fā)起64個請求
  private int maxRequests = 64;
  //同一個host最多發(fā)起5個請求
  private int maxRequestsPerHost = 5;
  //空閑的任務
  private @Nullable Runnable idleCallback;
  //執(zhí)行異步請求任務的線程池
  private @Nullable ExecutorService executorService;
  //等待的異步請求隊列
  final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
  //正在執(zhí)行的異步請求隊列
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
  //正在執(zhí)行的同步請求隊列
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

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

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

  ....
}

由上面代碼可以得出Dispatcher內(nèi)部實現(xiàn)了懶加載的無邊界限制的線程池。參數(shù)解析

  1. 0:核心線程數(shù)量,保持在線程池中的線程數(shù)量(即使已經(jīng)空閑),為0代表線程空閑后不會保留,等待一段時間后停止。
  2. Integer.MAX_VALUE:表示線程池可以容納最大線程數(shù)量
  3. TimeUnit.SECOND:當線程池中的線程數(shù)量大于核心線程時,空閑的線程就會等待60s才會被終止,如果小于,則會立刻停止。
  4. new SynchronousQueue<Runnable>():線程等待隊列。同步隊列,按序排隊,先來先服務
  5. Util.threadFactory("OkHttp Dispatcher", false):線程工廠,直接創(chuàng)建一個名為OkHttp Dispatcher的非守護線程。

回到之前RealCall中的同步請求調(diào)用:client.dispatcher().executed(this):

  //Dispatcher.executed
 synchronized void executed(RealCall call) {
  //只是把請求添加到正在執(zhí)行的請求隊列中
  runningSyncCalls.add(call);
}

RealCall中的異步請求調(diào)用:client.dispatcher().enqueue(new AsyncCall(responseCallback)),首先是創(chuàng)建了一個AsyncCall對象,然后傳給了enqueue()。先看一下AsyncCall:

final class AsyncCall extends NamedRunnable {
  //響應回調(diào)
  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;
}

@Override protected void execute() {
  boolean signalledCallback = false;
  try {
    //異步執(zhí)行執(zhí)行請求拿到數(shù)據(jù)(這是在線程池中創(chuàng)建的線程里面執(zhí)行的,所以是異步的)
    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 {
      eventListener.callFailed(RealCall.this, e);
      responseCallback.onFailure(RealCall.this, e);
    }
  } finally {
    //結束請求
    client.dispatcher().finished(this);
  }
}

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

可以看到NamedRunnable 只是一個實現(xiàn)了runnable接口的抽象類而已,run方法執(zhí)行的時候會執(zhí)行execute()方法,AsyncCall繼承了NamedRunnable,實現(xiàn)了excute()方法,在excute()中會去請求網(wǎng)絡拿到響應,然后結束請求。

繼續(xù)回到前面的RealCall中的異步請求調(diào)用:client.dispatcher().enqueue(new AsyncCall(responseCallback)):

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

可以看到dispatcher().enqueue()里面的操作:

1.先判斷當前正在并發(fā)執(zhí)行的請求數(shù)是否小于最大數(shù)64,并且同一個host的請求數(shù)是否小于最大值5
2.如果2個條件都滿足直接把當前請求添加到正在執(zhí)行的請求隊列,然后通過線程池提交執(zhí)行請求任務
3.如果條件不滿足,則添加到等待的請求隊列中

在第二步,線程池執(zhí)行請求了,就會去執(zhí)行AsyncCall的execute()里面的代碼,去執(zhí)行請求,拿到響應結果,然后結束。

對比同步請求和異步請求的調(diào)用結果,可以發(fā)現(xiàn)他們的請求流程基本是一樣的:

  1. runningAsyncCalls.add(call) 都是先把當前請求Call添加到正在運行的請求隊列中
  2. Response response = getResponseWithInterceptorChain() 執(zhí)行請求,拿到響應結果
  3. client.dispatcher().finished(this) 結束請求

不同的是:

  1. 同步請求是直接添加到請求隊列,去請求。而異步請求不會立即請求,會先判斷當前正在運行的請求是否超過最大并發(fā)數(shù)以及同一host的請求數(shù)是否超過最大值5個,如果都不超過最大值,直接添加到請求隊列,去請求。超過了,則添加到等待隊列等待執(zhí)行。
  2. 同步請求并沒有創(chuàng)建工作線程去執(zhí)行,而異步請求是在線程池創(chuàng)建的異步線程中執(zhí)行的,包括Callback 回調(diào)也是。所以用異步請求拿到響應后,不能直接做UI的更新操作,因為不是在主線程。

結束請求

前面總結了同步請求或異步請求的基本請求流程,在請求的三步都是結束請求,那么結束請求具體做了什么操作呢?看一下RealCall里面無論是同步請求還是異步請求,結束請求調(diào)用client.dispatcher().finished(this)的源碼:

//dispatcher.finished(RealCall call)
void finished(RealCall call) {
  finished(runningSyncCalls, call, false);
}

同步或異步結束請求時都是調(diào)用這個代碼,只是第三個參數(shù)不同,同步請求時false,異步請求時true.再看一下finished()

 //dispatcher. finished(Deque<T> calls, T call, boolean promoteCalls)
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
  int runningCallsCount;
  Runnable idleCallback;
  synchronized (this) {
    //從請求隊列移除請求call
    if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
    //如果需要將等待請求調(diào)整為正在執(zhí)行的請求,就將等待的請求添加到正在執(zhí)行的請求隊列,并執(zhí)行
    if (promoteCalls) promoteCalls();
    //統(tǒng)計正在執(zhí)行請求的數(shù)量
    runningCallsCount = runningCallsCount();
    idleCallback = this.idleCallback;
  }

  if (runningCallsCount == 0 && idleCallback != null) {
    idleCallback.run();
  }
}

可以看到如果是同步請求,第三個參數(shù)promoteCalls為false,不會去調(diào)用promoteCalls()方法,如果是異步請求,promoteCalls為true,會去調(diào)用promoteCalls()將等待的請求調(diào)整為執(zhí)行請求:

  //dispatcher.promoteCalls
 private void promoteCalls() {
  if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
  if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

  for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
    AsyncCall call = i.next();
    //同一host上的請求數(shù)小于5個,就將當前的請求從等待調(diào)整為執(zhí)行
    if (runningCallsForHost(call) < maxRequestsPerHost) {
      i.remove();
      runningAsyncCalls.add(call);
      executorService().execute(call);
    }

    if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
  }
}

結束請求的基本操作就是從請求隊列移除當前請求,然后根據(jù)是否是異步請求來調(diào)整請求的狀態(tài)。如果是異步請求,就會去查看等待隊列中的請求是否能夠調(diào)整為執(zhí)行請求,如果可以調(diào)整,就添加到執(zhí)行請求隊列并提交給線程池執(zhí)行。

至此,一次網(wǎng)絡請求的流程就結束了,再最后總結一下okhttp的基本請求過程:

1.通過OkhttpClient的內(nèi)部類builder創(chuàng)建一個OkhttpClient對象
2.創(chuàng)建一個Request對象,通過Request對象封來裝請求信息
3.通過OkhttpClient拿到Call接口的實現(xiàn)類對象RealCall,再根據(jù)Requset的請求信息去調(diào)用RealCall的同步或異步請求方法來完成網(wǎng)絡請求

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,835評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,676評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,730評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,118評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,873評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,266評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,482評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,036評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,846評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,025評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,279評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,684評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,953評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,751評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,016評論 2 375

推薦閱讀更多精彩內(nèi)容