OkHttp原始碼徹底解析(二)OkHttp架構及API原始碼
前言
OkHttp是一個處理網路請求的開源專案,是安卓端最火熱的輕量級框架,由移動支付Square公司貢獻(該公司還貢獻了Picasso)
用於替代HttpUrlConnection和Apache HttpClient(android API23 6.0裡已移除HttpClient,現在已經打不出來),這是現在非常主流的一個網路請求框架了。
可能有人會說Retrofit+RxJava才是最主流的,好吧,其實Retrofit的強大也是基於OkHttp,其是在OkHttp的基礎上進一步封裝,所以OkHttp也是切入Retrofit原始碼學習的入口。
博主對Retrofit2.x和OkHttp3.0也是比較熟悉的,剛好最近比較有空,接著週末時間總結了OkHttp。
本系列將帶領大家從原始碼的出發,做到儘可能詳細地剖析OkHttp的每一個知識點。
該系列的第一篇文章中我們已經瞭解了OkHttp從發起一個請求到收到網路回撥資料的流程(Request——>Response)的過程。
本文的主要是從原始碼出發,帶大家瞭解從發起一個Request請求到呼叫Dispatcher(分發器)呼叫執行緒池,來實現同/非同步發起請求資料的流程,及內部涉及的設計模式和原理。
本系列文章:
OkHttp原始碼徹底解析(二)OkHttp架構及API原始碼
OkHttp原始碼徹底解析(三)OkHttp3.0攔截器原理——責任鏈模式
目錄
客戶端請求的資料Request、服務端返回的資料Response——Builder模式
OkHttp整體流程
這是OkHttp請求的流程,也是本章講解的流程,圖中省略處為攔截器部分(責任鏈模式)本章涉及,將在下一篇部落格提到
我們都知道,要是有網路請求的API之前,必須先有請求的資訊,也就是request
客戶端請求的資料Request、服務端返回的資料Response——Builder模式
首先要明白,Requset與Response為什麼使用Builder模式
- 因為它們需要的引數滿足這兩點中的一點:
- 1.引數多且雜 ; 2.引數不是必須要傳入的
- 值得注意的一點是:資料請求類 的 url 是必須傳的,會在 build() 方法裡檢查,如果 為空會報異常
Request:
這是一個 請求資料 的封裝類(封裝了請求頭、請求地址等等)
public final class Request {
//url字串和埠號資訊,預設埠號:http為80,https為443.其他自定義資訊
private final HttpUrl url;
//"get","post","head","delete","put"....
private final String method;
//包含了請求的頭部資訊,name和value對。最後的形勢為:$name1+":"+$value1+"\n"+ $name2+":"+$value2+$name3+":"+$value3...
private final Headers headers;
//請求的資料內容
private final RequestBody body;
//請求的附加欄位。對資原始檔的一種摘要。儲存在頭部資訊中:ETag: "5694c7ef-24dc"。客戶端可以在二次請求的時候,在requst的頭部新增快取的tag資訊(如If-None-Match:"5694c7ef-24dc"),服務端用改資訊來判斷資料是否發生變化。
private final Object tag;
//各種附值函式和Builder類
...
}
其中,ResponseBody是請求的具體內容,是抽象類
public abstract class RequestBody {
...
//返回內容型別
public abstract MediaType contentType();
//返回內容長度
public long contentLength() throws IOException {
return -1;
}
//如何寫入緩衝區。BufferedSink是第三方庫okio對輸入輸出API的一個封裝,不做詳解。
public abstract void writeTo(BufferedSink sink) throws IOException;
}
OKHttp3中給出了兩個requestBody的實現FormBody 和 MultipartBody,分別對應了兩種不同的MIME型別:"application/x-www-form-urlencoded"和"multipart/"+xxx.作為的預設實現
其中,有一個重要的抽象方法writeTo
public abstract void writeTo(BufferedSink sink) throws IOException;
該方法的引數BufferedSink是Okio的封裝,就是一個sink就是從本地寫出的特殊的IO流。
這個抽象方法會在最後一個攔截器CallServerInterceptor裡面,也就是最終發起網路請求的部分被呼叫
request.body().writeTo(bufferedRequestBody);
把request裝換成bufferedRequestBody,並作為IO流通過Socket寫到目標網路中,當然,在這一步之前還有好多好多好多操作,這裡簡單先提一下。
Response:
public final class Response implements Closeable {
//網路請求的資訊
private final Request request;
//網路協議,OkHttp3支援"http/1.0","http/1.1","h2"和"spdy/3.1"
private final Protocol protocol;
//返回狀態碼,包括404(Not found),200(OK),504(Gateway timeout)...
private final int code;
//狀態資訊,與狀態碼對應
private final String message;
//TLS(傳輸層安全協議)的握手資訊(包含協議版本,密碼套件(https://en.wikipedia.org/wiki/Cipher_suite),證書列表
private final Handshake handshake;
//相應的頭資訊,格式與請求的頭資訊相同。
private final Headers headers;
//資料內容在ResponseBody中
private final ResponseBody body;
//網路返回的原聲資料(如果未使用網路,則為null)
private final Response networkResponse;
//從cache中讀取的網路原生資料
private final Response cacheResponse;
//網路重定向後的,儲存的上一次網路請求返回的資料。
private final Response priorResponse;
//發起請求的時間軸
private final long sentRequestAtMillis;
//收到返回資料時的時間軸
private final long receivedResponseAtMillis;
//快取控制指令,由服務端返回資料的中的Header資訊指定,或者客戶端發器請求的Header資訊指定。key:"Cache-Control"
//詳見<a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9">RFC 2616,14.9</a>
private volatile CacheControl cacheControl; // Lazily initialized.
//各種附值函式和Builder型別 ...
}
其中,上面比較重要的是:1.ResponseBody是獲取的資料內容,2.三個Response:網路返回的、從cache中讀取的、重定向後儲存的之前的網路請求返回資料,3.Requset網路請求資訊,4.Headers響應頭:可以知道快取指令,5.code狀態碼:404就是錯誤,6.CacheControl 快取控制指令,由伺服器返回的Header或客戶端Header指定
介紹了Request和Response之後,我們來了解如何通過這個Requset來得到Pesronse,已經這裡面API的內部邏輯
首先,OKHttp3在專案中發起網路請求的API如下:
okHttpClient.newCall(request).execute();
我們按順序來了解這個API涉及的類的原始碼:
okHttpClient——外觀模式,組合模式
OkHttp是一個比較龐大的網路請求框架(Retrofit內部也是使用OkHttp這個框架),為了方便地和這個框架內部複雜的子模組進行互動,OkHttpClient使用了外觀模式來實現。將OKHttp的很多功能模組,全部包裝進這個類中,讓這個類單獨提供對外的API,這種設計叫做外觀模式。將操作都隱藏起來,減少使用者的互動成本。
由於內部功能模組太多,使用了Builder模式(生成器模式)來構造。
它的方法只有一個:newCall.返回一個Call物件(一個準備好了的可以執行和取消的請求)。
newCall
先來看原始碼:
@Override
public Call newCall(Request request) {
return new RealCall(this, request);
}
我們可以看到,newCall其實是返回一個RealCall類,也就是說我們的同/非同步請求網路資料,實際上都是呼叫這個RealCall的execute/enqueue方法。這是一個Call介面的實現類
call
public interface Call {
Request request();
//同步的方法,直接返回Response
Response execute() throws IOException;
//非同步的,傳入回撥CallBack即可(介面,提供onFailure和onResponse方法)
void enqueue(Callback responseCallback);
void cancel();
boolean isExecuted();
boolean isCanceled();
interface Factory {
Call newCall(Request request);
}
}
Call介面提供了內部介面Factory(用於將物件的建立延遲到該工廠類的子類中進行,從而實現動態的配置,工廠方法模式)。
dispatcher與executorService
dispatcher與executorService分別是分發器與執行緒池,承接上面的RealCall,
OKHttpClient類中有個成員變數dispatcher負責請求的分發。既在真正的請求RealCall的execute方法中,使用dispatcher來執行任務:
-
RealCall的execute方法:
@Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } try { //使用dispatcher 來分發任務 client.dispatcher().executed(this); Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } finally { client.dispatcher().finished(this); } }
-
RealCall的enqueue方法:
@Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } //使用dispatcher來將人物加入佇列 client.dispatcher().enqueue(new AsyncCall(responseCallback)); }
OKHttp3中分發器只有一個類 ——Dispathcer.
也就是說,enqueue/execute(同/非同步)都是內部呼叫了dispatcher來執行任務,
同步操作:dispathcer呼叫自己的execute方法
流程:
1.在RealCall中client.dispatcher().execute(this); 其中this就是RealCall
2.executorService()就是獲取一個執行緒池
3.RealCall的execute內部是executorService().execute(this);執行緒池executorService呼叫他的execute(call) ( call也就是上面的new AsyncCall(responseCallback))
非同步操作:dispathcer呼叫自己的enqueue方法
流程:
1.在RealCall中client.dispatcher().enqueue(new AsyncCall(responseCallback));其中,AsyncCall與上面同步時的RealCall形對比,同步呼叫RealCall,非同步呼叫RealCall的內部類AsyncCall的enqueue
2.executorService()方法獲取一個執行緒池,而
3.AsyncCall的enqueue內部是executorService().execute(call);執行緒池executorService呼叫他的execute(call) ( call也就是上面的new AsyncCall(responseCallback))
下面我們可以通過原始碼來看看Dispatcher
執行緒池executorService:
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代表執行緒空閒後不會保留,等待一段時間後停止。
- Integer.MAX_VALUE: 執行緒池可容納執行緒數量。
- 60,TimeUnit.SECONDS: 當執行緒池中的執行緒數大於核心執行緒數時,空閒的執行緒會等待60s後才會終止。如果小於,則會立刻停止。
- new SynchronousQueue<Runnable>():執行緒的等待佇列。同步佇列,按序排隊,先來先服務。
Util.threadFactory("OkHttp Dispatcher", false): 執行緒工廠,直接建立一個名為 “OkHttp Dispathcer”的非守護執行緒。
(2) 執行同步的Call:直接加入runningSyncCalls佇列中,實際上並沒有執行該Call,交給外部執行。
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
(3) 將Call加入佇列:如果當前正在執行的call數量大於maxRequests,64,或者該call的Host上的call超過maxRequestsPerHost,5,則加入readyAsyncCalls排隊等待。否則加入runningAsyncCalls,並執行。
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
(4) 從ready到running的輪轉,在每個call 結束的時候呼叫finished,並:
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
//每次remove完後,執行promoteCalls來輪轉。
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
//執行緒池為空時,執行回撥
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
(5) 執行緒輪轉:遍歷readyAsyncCalls,將其中的calls新增到runningAysncCalls,直到後者滿。
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();
if (runningCallsForHost(call) < maxRequestsPerHost) { i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return;
}
}
執行請求
同步的請求RealCall 實現了Call介面:
可以execute,enqueue和cancle。
非同步的請求AsyncCall(RealCall的內部類)實現了Runnable介面:
只能run(呼叫了自定義函式execute).
execute 對比:
-
RealCall:
@Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } try { //分發。實際上只是假如了佇列,並沒有執行 client.dispatcher().executed(this); //實際上的執行。 Response result = getResponseWithInterceptorChain(); //返回結果 if (result == null) throw new IOException("Canceled"); return result; } finally { //執行完畢,finish client.dispatcher().finished(this); } }
-
AsyncCall:
@Override protected void execute() {
boolean signalledCallback = false;
try {
//實際執行。
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) {
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
//執行完畢,finish
client.dispatcher().finished(this);
}
}
實際上的執行函式都是getResponseWithInterceptorChain():
private Response getResponseWithInterceptorChain() throws IOException {
//建立一個攔截器列表
List<Interceptor> interceptors = new ArrayList<>();
//優先處理自定義攔截器
interceptors.addAll(client.interceptors());
//失敗重連攔截器
interceptors.add(retryAndFollowUpInterceptor);
//介面橋接攔截器(同時處理cookie邏輯)
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//快取攔截器
interceptors.add(new CacheInterceptor(client.internalCache()));
//分配連線攔截器
interceptors.add(new ConnectInterceptor(client));
//web的socket連線的網路配置攔截器
if (!retryAndFollowUpInterceptor.isForWebSocket()) {
interceptors.addAll(client.networkInterceptors());
}
//最後是連線伺服器發起真正的網路請求的攔截器
interceptors.add(new CallServerInterceptor(
retryAndFollowUpInterceptor.isForWebSocket()));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
//流式執行並返回response
return chain.proceed(originalRequest);
}
這裡額外提一下converter
Converter工廠模式
converter:序列化,反序列化的工具(對應Requset和Response),實現資料型別的轉換,例Gson解析等
converterFactory是converter的工廠模式,用來構建各種converter,
可以新增converterFactory由retrofit完成requestBody和responseBody的構造。
這裡對retrofit2不展開討論,後續會出新的文章來詳細討論。僅僅介紹一下converterFacotry,以及它是如何構建OkHttp3中的RequestBody和ResponseBody的。
-
Note: retrofit2中的Response與okhttp3中的response不同,前者是包含了後者。既retrofit2中的response是一層封裝,內部才是真正的okhttp3種的response。
我們專案中的一個converterFacotry程式碼如下:
public class RsaGsonConverterFactory extends Converter.Factory {
//省略部分程式碼
...
private final Gson gson;
private RsaGsonConverterFactory(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
this.gson = gson;
}
//將返回的response的Type,註釋,和retrofit的傳進來,返回response的轉換器。Gson只需要type就可以將responseBody轉換為需要的型別。
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new RsaGsonResponseBodyConverter<>(gson, adapter);
}
//將request的引數型別,引數註釋,方法註釋和retrofit傳進來,返回request的轉換器。Gson只需要type就可以將request物件轉換為OKHttp3的reqeustBody型別。
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new RsaGsonRequestBodyConverter<>(gson, adapter);
}
}
該Factory(工廠方法模式,用於動態的建立物件)主要是用來生產response的converter和request的converter。顯然我們使用了Gson作為資料轉換的橋樑。分別對應如下兩個類:
-
response的converter(之所以命名為Rsa,是做了一層加解密):
public class RsaGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> { private final Gson gson; private final TypeAdapter<T> adapter; RsaGsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) { this.gson = gson; this.adapter = adapter; } @Override public T convert(ResponseBody value) throws IOException { JsonReader jsonReader = gson.newJsonReader(value.charStream()); try { return adapter.read(jsonReader); } finally { value.close(); } } }
直接將value中的值封裝為JsonReader供Gson的TypeAdapter讀取,獲取轉換後的物件。
-
request的converter:
final class RsaGsonRequestBodyConverter<T> implements Converter<T, RequestBody> { private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8"); private static final Charset UTF_8 = Charset.forName("UTF-8"); private final Gson gson; private final TypeAdapter<T> adapter; RsaGsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) { this.gson = gson; this.adapter = adapter; } @Override public RequestBody convert(T value) throws IOException { Buffer buffer = new Buffer(); Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8); JsonWriter jsonWriter = gson.newJsonWriter(writer); adapter.write(jsonWriter, value); jsonWriter.close(); //如果是RsaReq的子類,則進行一層加密。 if(value instanceof RsaReq){ //加密過程 } //不需要加密,則直接讀取byte值,用來建立requestBody else { //這個構造方法是okhttp專門為okio服務的構造方法。 return RequestBody.create(MEDIA_TYPE, buffer.readByteString()); } } }
上面的流操作使用的是第三方庫okio。可以看到,retrofit,okhttp,okio這三個庫是完全相互相容並互相提供了專有的API。
好了,這裡就是本章內容,本章介紹了OkHttp的架構,API內部的原始碼,我們可以看到裡面包含了許多值得學習的設計模式,
Requset和Response的builder模式
converter的工廠模式
OkHttpClient的外觀模式,組合模式