Android—OkHttp同步異步請求源碼分析與區別

OkHttp同步請求步驟:

  1. 創建OkHttpClient,客戶對象
  2. 創建Request,請求主體,在請求主體設置請求的url,超時時間等
  3. 用newCall(request)將Reuqest對象封裝成Call對象,然後用Call對象的execute()發起同步請求。
  4. execute()返回的是Response對象。可以用execute().body().toString()得到請求所返回的主體內容。
val client = OkHttpClient()
val request = Request.Builder()
    .url("https://www.baidu.com")
    .build()
val response = client.newCall(request).execute().body().toString()

注意:發送請求後,就會進入阻塞狀態,直到收到響應。

OkHttp異步請求步驟:

  1. 創建OkHttpClient,客戶對象
  2. 創建Request,請求主體,在請求主體設置請求的url,超時時間等
  3. 用newCall(request)將Reuqest對象封裝成Call對象,然後用Call對象的enqueue()發起異步請求。
  4. enqueue(object: Callback{重寫onFailure、onResponse方法}) 在onResponse方法中獲取申請數據內容。
val client = OkHttpClient()
val request = Request.Builder()
    .url("https://www.baidu.com")
    .build()
val response = client.newCall(request).enqueue(object: Callback {
    override fun onFailure(call: Call, e: IOException) {
        TODO("Not yet implemented")
    }

    override fun onResponse(call: Call, response: Response) {
        response.body().toString()
    }

})

源碼分析:

注意:下面的源代碼段可能來自不同一個類文件,只是將他們放一起,容易觀察,主要放一些關鍵代碼,其他會有...代替。

1.關於創建OkHttpClient對象,下面源碼:

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

public Builder() {
      dispatcher = new Dispatcher();   
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;   
      ......
      connectionPool = new ConnectionPool();
      .....
    }

可以看到OkHttp採用了建造者模式,在Builder()裏面封裝各種需要的屬性,關鍵的主要有dispatcher分發器,connectionSpecs決定是異步還是同步,connectionPool 連接池。每個連接都會放入連接池中,由它進行管理。 

總結新建Client對象時,新建了一個分發器和一個連接池,還有一些屬性的初始化。

2.創建Request對象時,源碼:

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

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類,Builder構造函數默認請求方式爲Get,還有對請求頭部的封裝。

總結:新建一個Request對象裏面主要封裝了請求路徑,頭部信息等。

3.用newCall(request)將Reuqest對象封裝成Call對象時,源碼:

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

//可以看到newCall方法裏面是調用了RealCall類的newRealCall方法,下面到RealCall類裏看看。

  static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// 調用RealCall的構造函數
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
  }

//下面是RealCall類構造函數

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

總結:newCall方法實際生成RealCall對象,對象裏面包含了Client客戶對象和Request的請求對象,還新建了一個RetryAndFollowUpInterceptor 重定向攔截器。

4.execute()同步請求方法,源碼:

@Override public Response execute() throws IOException {
   .....
//開啓事件監聽
   eventListener.callStart(this);   
   try {
//分發器用executed方法將Call對象添加進同步運行隊列
     client.dispatcher().executed(this); 
//結果是從攔截器鏈方法中獲取的
     Response result = getResponseWithInterceptorChain();  
     ......
   } finally {
//finish方法裏將Call對象從Calls隊列中移出
     client.dispatcher().finished(this);  
   }
}

//下面進到client.dispatcher().executed(this)的excuted方法裏面

 synchronized void executed(RealCall call) {
//runningSyncCalls是正在運行的同步隊列
    runningSyncCalls.add(call);  
 }

總結:excute()同步申請方法,分發器將Call對象添加到同步運行隊列,當然其中會有一個promoteAndExecute()方法來計算隊列中請求數量,有限定最大申請數量,上面代碼沒列出來,最後結果是經過一系列攔截器的方法後的數據。

5.enqueue異步請求方法,源碼:

@Override public void enqueue(Callback responseCallback) {
//判斷是否請求過這個Call對象
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
//異常檢測
    captureCallStackTrace();
//事件監聽
    eventListener.callStart(this);
//調用分發器的enqueue方法,分發器在client創建時新建的。
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

可以看到enqueue方法裏面又調用了分發器的enqueue方法,在enqueue方法裏新建了一個AsyncCall對象,

AsyncCall對象傳入我們上一層傳入enqueue方法的CallBack對象。

接下來看看上面的AsyncCall類是什麼東西。

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

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

看到AsyncCall繼承自NamedRunnable,再來看看NamedRunnable是什麼東西

public abstract class NamedRunnable implements Runnable {
  .....
  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }
  .....
}

可以看到NamedRunnable實現了Runnable接口,裏面最核心的就是在run方法裏面運行了execute()方法,這個方法的具體實現在AsyncCall類裏,我們來看看。

@Override protected void execute() {
      .......
      Response response = getResponseWithInterceptorChain(); 
      .....
  }

我把大部分代碼都省了,最重要的就上面那句,可以看到跟同步請求一樣,最後結果都是經過一系列攔截器的方法後的數據。

那麼同步跟異步有什麼區別呢?

異步傳入enqueue方法的CallBack的對象實現了Runnable接口,讓它在子線程中運行。

還有,接下來回到開頭看看client.dispatcher().enqueue(new AsyncCall(responseCallback));這句,分發器類裏的變量和它的enqueue方法(剛剛看的是AsyncCall類)。

public final class Dispatcher {
  //默認的最大併發請求量 
  private int maxRequests = 64;
  //單個host支持的最大併發量
  private int maxRequestsPerHost = 5;  
  .........
  //異步等待隊列
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  //異步運行隊列
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  //同步運行隊列
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

  void enqueue(AsyncCall call) {
      synchronized (this) {
  //把Call對象添加進readyAsyncCalls異步等待隊列
        readyAsyncCalls.add(call);
      }
  //計算隊列內請求數量的方法,如果異步請求滿足不超過64,5的條件則進行請求操作。
      promoteAndExecute();
  }

可以看到分發器把Call對象添加進readyAsyncCalls異步等待隊列,而在同步請求時分發器是把Call對象直接添加到同步運行隊列,promoteAndExecute()方法就是前面說到的計算隊列內請求數量的方法,這個方法裏面會進行判斷,如果請求數量符合上面兩個最大值的要求就會把請求放入隊列等操作。

總結:enqueue方法傳入CallBack對象,CallBack對象被封裝爲AsyncCall,AsyncCall內部實現了Runnable接口,分發器把AsyncCall傳入了異步等待對列,最後運行promoteAndExecute()方法,如果請求數量符合條件進行異步請求,在子線程獲取數據。

篇幅有點長了,關於Response response = getResponseWithInterceptorChain(); 攔截器鏈也是重點,在下一篇再寫。

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