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

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

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幫我們封裝好的。但具體的請求原理,內部怎么實現的呢?下面進入源碼看看

Okhttpclient

通過流程我們知道,要請求首先要構造一個okhttpClient對象??匆幌滤臉嬙旆椒ǎ?/p>

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

看一下靜態(tà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的構造函數里面需要傳一個內部類對象builder,request可以通過其內部類對象builder修改其攜帶的請求信息

執(zhí)行請求

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

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

newCall(Request) 方法調用了 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實現了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 {
    //實際調用的是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.調用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);
  //實際是調用dispatcher的enqueue()方法
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

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

public final class Dispatcher {
  //最大并發(fā)數,最多發(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內部實現了懶加載的無邊界限制的線程池。參數解析

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

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

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

RealCall中的異步請求調用:client.dispatcher().enqueue(new AsyncCall(responseCallback)),首先是創(chuàng)建了一個AsyncCall對象,然后傳給了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;
}

@Override protected void execute() {
  boolean signalledCallback = false;
  try {
    //異步執(zhí)行執(zhí)行請求拿到數據(這是在線程池中創(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 只是一個實現了runnable接口的抽象類而已,run方法執(zhí)行的時候會執(zhí)行execute()方法,AsyncCall繼承了NamedRunnable,實現了excute()方法,在excute()中會去請求網絡拿到響應,然后結束請求。

繼續(xù)回到前面的RealCall中的異步請求調用: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í)行的請求數是否小于最大數64,并且同一個host的請求數是否小于最大值5
2.如果2個條件都滿足直接把當前請求添加到正在執(zhí)行的請求隊列,然后通過線程池提交執(zhí)行請求任務
3.如果條件不滿足,則添加到等待的請求隊列中

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

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

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

不同的是:

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

結束請求

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

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

同步或異步結束請求時都是調用這個代碼,只是第三個參數不同,同步請求時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!");
    //如果需要將等待請求調整為正在執(zhí)行的請求,就將等待的請求添加到正在執(zhí)行的請求隊列,并執(zhí)行
    if (promoteCalls) promoteCalls();
    //統計正在執(zhí)行請求的數量
    runningCallsCount = runningCallsCount();
    idleCallback = this.idleCallback;
  }

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

可以看到如果是同步請求,第三個參數promoteCalls為false,不會去調用promoteCalls()方法,如果是異步請求,promoteCalls為true,會去調用promoteCalls()將等待的請求調整為執(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上的請求數小于5個,就將當前的請求從等待調整為執(zhí)行
    if (runningCallsForHost(call) < maxRequestsPerHost) {
      i.remove();
      runningAsyncCalls.add(call);
      executorService().execute(call);
    }

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

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

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

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

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容