天天看點

OkHttp源碼分析(一) 請求和響應過程簡單分析

這篇文章簡要介紹OkHttp的請求和響應過程。

文章基于OkHttp3.14.3版本

0 前言

​ OkHttp作為當下Java系程式設計的網絡請求庫,其熱門程度自不必說了。網上有關OkHttp的使用、封裝和源碼分析的文章和代碼也早已是百家齊放、甚至可以說是爛大街了。然而盡管如此,筆者還是希望能夠将對OkHttp的學習和研究記錄下來形成自己的内容,友善以後檢視,于是開始寫吧,好記性不如爛筆頭。

​ 那就從最簡單的開始。這篇文章打算簡要描述一下OkHttp中大緻的請求響應過程。

1 開始

​ 首先看看簡單的同步GET請求和異步GET請求:

  • 同步GET請求:
public static void getSync() {
    // Step 1. 建立一個HttpClient執行個體用于建立請求任務
    OkHttpClient httpClient = new OkHttpClient();
    // Step 2. 建構一個Request用于封裝請求位址、請求類型、參數等資訊
    Request request = new Request.Builder().get()
                                           .url("https://www.baidu.com")
                                           .build();
    // Step 3. 建立一個新的請求任務Call
    Call call = httpClient.newCall(request);
    try {
        // Step 4. 發起請求
        Response response = call.execute();
        // Step 5. 讀取、處理請求結果
        ResponseBody responseBody = response.body();
        if (responseBody != null) {
            System.out.println(responseBody.string());
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}
           
  • 異步GET請求:
public void getAsync() {
    // Step 1. 建立一個HttpClient執行個體用于建立請求任務
    OkHttpClient httpClient = new OkHttpClient();
    // Step 2. 建構一個Request用于封裝請求位址、請求類型、參數等資訊
    Request request = new Request.Builder().get()
                                           .url("https://www.baidu.com")
                                           .build();
    // Step 3. 建立一個新的請求任務Call
    Call call = httpClient.newCall(request);
    // Step 4. 發起請求
    call.enqueue(new Callback() {
        @Override
        public void onFailure(final Call call, final IOException e) {
            e.printStackTrace();
        }

        @Override
        public void onResponse(final Call call, final Response response) throws IOException {
            // Step 5. 讀取、處理請求結果
            ResponseBody responseBody = response.body();
            if (responseBody != null) {
                System.out.println(responseBody.string());
            }
        }
    });

}
           

可以看到,不管是同步還是異步請求,都需要經過Step1~Step3三個步驟建構一個請求任務,并通過調用call.execute()/call.enqueue(callback)來執行同步/異步請求。那接下來就看看這兩個方法的執行過程吧。

首先看看call.execute():

點選檢視Call類,發現Call是一個接口,嘗試跳轉到call.execute()方法的具體實作(ps: AS快捷鍵Ctrl+Alt+B實作快速跳轉到方法的具體實作),來到Call接口的唯一實作類RealCall類,RealCall.execute()具體實作如下:

@Override public Response execute() throws IOException {
  // Step 1.
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  // Step 2.
  transmitter.timeoutEnter();
  transmitter.callStart();
  try {
    // Step 3.
    client.dispatcher().executed(this);
    // Step 4.
    return getResponseWithInterceptorChain();
  } finally {
    // Step 5.
    client.dispatcher().finished(this);
  }
}
           
  • Step 1.執行請求之前,確定該call請求任務尚未被執行過。由此可以看到OkHttp要求每個請求任務隻能被執行一次。
  • Step 2.開始計算逾時時間,并記錄請求開始callStart這個流程。
  • Step 3.通過Dispatcher将本次請求任務記錄到同步請求隊列runningSyncCalls。runningSyncCalls是一個實作為ArrayDeque的雙向隊列:
/** 正在運作的同步請求任務,包括尚未結束就已經取消同步請求. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
synchronized void executed(RealCall call) {
  runningSyncCalls.add(call);
}
           
  • Step 4. 接着調用RealCall.getResponseWithInterceptorChain()發起請求并擷取請求結果;
  • Step 5.在傳回結果給上層調用之前,通過Dispatcher将本次任務從runningSyncCalls隊列中移除以表示本次請求任務結束:
  • Step 6.傳回請求結果給上層,請求結束。

經過以上這幾個步驟,一次同步GET請求就算是結束了。可以發現,RealCall.getResponseWithInterceptorChain()方法負責進行具體的HTTP請求,這裡暫時不跟進去,先來看看異步的GET請求:

Ctrl+Alt+B

快速跳轉到**call.enqueue(callback)**的具體實作RealCall.enqueue(callback):

@Override public void enqueue(Callback responseCallback) {
  // Step 1.
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  // Step 2.
  transmitter.callStart();
  // Step 3.
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
           
  • Step 1.與同步GET請求的Step 1相同。
  • Step 2.記錄該請求過程。
  • Step 3.構造一個AsyncCall對象并加入請求隊列。AsyncCall是RealCall的内部類,用于表示異步的請求任務。AsyncCall的父類NamedRunnable是一個實作了Runnable接口的抽象類并重寫了Runnable.run方法,同時對外提供了**execute()**抽象方法,并在run方法實作中調用。是以,接下來關注AsyncCall類實作的execute()方法,既然AsyncCall是一個Runnable,而run中又調用了execute(),可以判定,異步請求任務最終會執行到這裡并進行實際的HTTP請求:
@Override protected void execute() {
  boolean signalledCallback = false;
  // Step 3.1 開始計算請求逾時時間
  transmitter.timeoutEnter();
  try {
    // Step 3.2 發起請求并擷取請求結果
    Response response = getResponseWithInterceptorChain();
    signalledCallback = true;
    // Step 3.3 請求成功,将結果通過回調接口傳回給上層
    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 {
      // 請求失敗
      responseCallback.onFailure(RealCall.this, e);
    }
  } finally {
    // Step 4.結束本次請求任務,并從隊列中移除
    client.dispatcher().finished(this);
  }
}
}
           

而查閱execute的實作也驗證了這個判定是正确的,至于何時,從哪兒,會執行到這個execute方法,先留個小坑吧後續再填~。同時,對比RealCall.execute()方法(同步請求)和AsyncCall.execute()方法(異步請求)發現,不管是同步還是異步請求,最後都會通過調用getResponseWithInterceptorChain()方法來實作網絡請求求并擷取傳回結果,看看這個方法的實作:

Response getResponseWithInterceptorChain() throws IOException {
  // 建構一個完整的攔截器清單
  List<Interceptor> interceptors = new ArrayList<>();
  // 添加使用者自定義的攔截器
  interceptors.addAll(client.interceptors());
  // 添加用于失敗重試和重定向的攔截器
  interceptors.add(new RetryAndFollowUpInterceptor(client));
  // 添加用于連接配接應用層和網絡層的攔截器,該攔截器會将一個使用者請求轉換為網絡請求,并将網絡請求結果以使用者友好的方式傳回
  interceptors.add(new BridgeInterceptor(client.cookieJar()));
  // 添加用于緩存的攔截器
  interceptors.add(new CacheInterceptor(client.internalCache()));
  // 添加用于打開與伺服器間網絡連接配接的攔截器
  interceptors.add(new ConnectInterceptor(client));
  if (!forWebSocket) {
    // 如果本次請求任務不是websocket請求,則添加網絡攔截器,該攔截器也需要使用者自定義
    interceptors.addAll(client.networkInterceptors());
  }
  // 添加最後一個攔截器,該攔截器負責執行最終的網絡請求并傳回結果
  interceptors.add(new CallServerInterceptor(forWebSocket));
	// 建構一個攔截器鍊chain對象
  Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
                                                     originalRequest, this, client.connectTimeoutMillis(),
                                                     client.readTimeoutMillis(), client.writeTimeoutMillis());

  boolean calledNoMoreExchanges = false;
  try {
    // 執行請求并擷取傳回結果
    Response response = chain.proceed(originalRequest);
    if (transmitter.isCanceled()) {
      closeQuietly(response);
      throw new IOException("Canceled");
    }
    return response;
  } catch (IOException e) {
    calledNoMoreExchanges = true;
    throw transmitter.noMoreExchanges(e);
  } finally {
    if (!calledNoMoreExchanges) {
      transmitter.noMoreExchanges(null);
    }
  }
}
           

可以看到,這個方法最終是通過chain.proceed(originalRequest);實作請求和傳回結果,到這裡,OkHttp的請求過程就結束了。是以,一個OkHttp的請求過程大緻如下:

OkHttp源碼分析(一) 請求和響應過程簡單分析

先到這裡吧。本來想着一篇寫完的但太長了似乎自己都不想看,還是一步步來吧。下一篇開始着重分析以上OkHttp請求過程中接觸到的各個關鍵的類。

2 The End ?

歡迎關注公衆号:
OkHttp源碼分析(一) 請求和響應過程簡單分析
文章首發在個人部落格 https://www.nullobject.cn,公衆号NullObject同步更新。