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.Call對象調用的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對象添加到同步運行隊列。請求數據從Response result = getResponseWithInterceptorChain();  中獲取。

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()方法,這個方法的具體實現其實跟同步請求execute方法一樣,在AsyncCall類裏,和同步請求最後的execute()是同一個方法。

@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<>();
  ...........
  //計算隊列內請求數量的方法,如果異步請求滿足不超過64,5的條件則進行請求操作。
  //有的版本OkHttp是通過promoteAndExecute()進行條件判斷,原理差不多
  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
  //把Call對象添加進runningAsyncCalls異步進行隊列
      runningAsyncCalls.add(call);
  //創建線程池並執行Call請求
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }
  ......
}    

分發器對Request類型進行判斷,把Call對象添加進readyAsyncCalls異步等待隊列或runningAsyncCalls,而在同步請求時分發器是把Call對象直接添加到runningSyncCalls同步運行隊列。異步請求最後開啓線程池獲取數據。

總結:enqueue方法傳入CallBack對象,CallBack對象被封裝爲AsyncCall,AsyncCall內部實現了Runnable接口,分發器進行判斷,如果符合條件就把AsyncCall傳入了異步進行對列,開啓線程池在子線程獲取數據。否則添加進異步等待隊列。

readyAsyncCalls異步等待隊列的請求什麼時候能運行呢?

我們已經知道異步跟同步請求通過分發器分發隊列,但是最後都要經過AsyncCall類的execute()方法來獲取數據,execute()方法最後finally裏面運行client.dispatcher().finished(this);方法,我們進去finished方法看看。

//異步請求的finished方法 
void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }
//同步請求的finished方法
 void finished(RealCall call) {
    finished(runningSyncCalls, call, false);
  }
//具體的finished方法
 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!");
//注意promoteCalls是第三個參數,既如果是異步請求才會運行該方法,重新整理隊列。
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }
    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }

進入promoteCalls()方法

  private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
//循環到隊列最後一個元素,call爲最後一個請求
    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();
//如果符合條件就把call從等待隊列移除加入運行隊列。
      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }
      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

總結:在readyAsyncCalls隊列中的請求會在異步請求的finished方法裏進行判斷,如果符合條件則進入runningAsyncCalls。

Dispatcher分發器:

從上面的Dispatcher類可以看出分發器有3個隊列,異步有兩個隊列是運用了消費者模式,類中還有excuted,enqueue,finished方法,Dispatcher主要作用就是根據Request類型將Call對象調入不同隊列,最後用finished將完成的請求移除隊列並把等待的請求調進運行隊列。

ExecutorService線程池: 

下面進到executorService().execute(call)看看,

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

第一個參數代表核心線程數量,爲0就代表線程空閒之後不會被保留,會被銷燬;如果大於0,即使本地任務執行完畢,核心線程也不會被銷燬。

第二個參數是int整數的最大值,他表示的是線程池中可以容納的最大線程數量。但是確實它得滿足64跟5的條件。

第三個keepAliveTime,當我們的線程池中線程數量大於核心線程數量時,空閒線程需要等待60秒的時間纔會被終止。

OkHttp攔截器

我們已經知道了請求最後都是從Response result = getResponseWithInterceptorChain()這句中獲取的數據。

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }

總歸創建了6個攔截器

全部攔截器的基本流程:

  1. 在發起請求前對request進行處理。
  2. 調用chain.proceed()方法,獲取下一個攔截器的response。
  3. 對reponse進行處理,返回給上一個攔截器。

RetryAndFollowUpInterceptor 重定向攔截器

負責失敗重連的攔截器。

  1. 創建StreamAllocation對象,但是沒有使用,它的使用是在ConnectInterceptor。它負責爲一次“請求”尋找“連接”並建立“流”。Connection是建立在Socket之上的物理通信信道,而Stream則是代表邏輯的流,如果有多個stream(即多個 Request) 都是連接在一個 host 和 port上,那麼它們就可以共同使用同一個 socket ,這樣做的好處就是可以減少TCP的一個三次握手的時間。
  2. 調用RealInterceptorChain.proceed(...)進行網絡請求
  3. 根據異常結果或響應結果判斷是否進行重新請求(20次)
  4. 調用下一個攔截器,對response進行處理,返回上一個攔截器

BridgeInterceptor  橋攔截器

該攔截器是鏈接客戶端代碼和網絡代碼的橋樑,它首先將客戶端構建的Request對象信息構建成真正的網絡請求;然後發起網絡請求,最後就是講服務器返回的消息封裝成一個Response對象。

  1. 將用戶構建的Request請求轉化爲能夠進行網絡訪問的請求
  2. 執行符合條件的請求
  3. 將Response轉化爲用戶可用的Response,OkHttp支持Gzip壓縮,GzipSource類對數據進行解壓。

CacheInterceptor 緩存攔截器

該攔截器用於處理緩存的功能,主要取得緩存 response 返回並刷新緩存。

  1. 底層使用的是 DiskLruCache 緩存機制。
  2. CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
  3. get() 方法獲取一個 CacheStrategy 對象。CacheStrategy,它是一個策略器,負責判斷是使用緩存還是請求網絡獲取新的數據。
  4. responseCache.put(userResponse);
  5. put(userResponse)方法將 userResponse 緩存到本地。 

爲什麼需要緩存 Response?

  • 客戶端緩存就是爲了下次請求時節省請求時間,可以更快的展示數據。
  • OKHTTP 支持緩存的功能

ConnectInterceptor  連接攔截器

該攔截器的功能就是負責與服務器建立 Socket 連接,並且創建了一個 HttpCodec它包括通向服務器的輸入流和輸出流。

  1. 獲取到第一個攔截器生成的StreamAllocation對象,
  2. 通過StreamAllocation對象,streamAllocation.newStream()創建HttpCodec對象,
  3. streamAllocation.connection()獲取一個RealConnection對象
  4. 將HttpCodec、RealConnection對象傳遞給攔截器

NetworkInterceptors 網絡攔截器

  • 允許像重定向和重試一樣操作中間響應。
  • 網絡發生短路時不調用緩存響應。
  • 在數據被傳遞到網絡時觀察數據。
  • 有權獲得裝載請求的連接。

CallServerInterceptor 調用服務攔截器

該攔截器的功能使用 HttpCodec與服務器進行數據的讀寫操作的。

  1. 首先是獲取了httpCodec對象,該對象的主要功能就是對不同http協議(http1.1和http/2)的請求和響應做處理,該對象的初始化是在ConnectIntercepor的intercept裏面
  2. OkHttp通過OKIO的Sink對象(該對象可以看做Socket的OutputStream對象)的writeRequest來向服務器發送請求的。
  3. 將OKIO的Source對象作爲輸入流InputStream對象讀取數據封裝爲Response對象。
  4. 100-continue用於客戶端在發送POST數據給服務器前,徵詢服務器情況,看服務器是否處理POST的數據,如果不處理,客戶端則不上傳POST數據,如果處理,則POST上傳數據。

ConnectionPool

OkHttp中所有的連接(RealConnection)都是通過ConnectionPool來管理。

  1. StreamAllocation裏面包含了RealConnection對象,該對象歸根是由ConnectionPool的get() 方法遍歷 connections 中的所有 RealConnection 尋找同時滿足條件的RealConnection,重複利用RealConnection。
  2. ConnectionPool類裏put方法,採用GC回收算法,異步觸發清理任務,然後將健康的connection添加到connections隊列中。調用cleanup方法執行清理,並等待一段時間,持續清理,其中cleanup方法返回的值來來決定而等待的時間長度。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章