對OkHttp介紹,我們分爲使用篇和源碼分析篇兩個系列進行介紹。
在OkHttp使用篇當中,我們分幾篇文章介紹了OkHttp框架的使用,但並沒有去分析整個框架的源碼和實現。這裏單獨開一個系列來捋一捋OkHttp3的源碼,整個過程也是我自己學習的過程,所以有不好的地方還請見諒了。
下面就進入到本篇文章主題,從源碼角度分析下整體的執行流程。在OkHttp3使用(二)-常用類介紹中,我們已經分析了整個執行流程,不過那個時候是從使用的角度,並沒有深入去了解每個類的實現,這篇文章也可以算是對那篇文章的補充說明吧。
1 整體說明
這裏照舊還是先放一張OkHttp執行的整體流程圖
後續我們對整個執行流程的整體分析就是按照這個圖所示進行的。所以先了解這個圖,對後面我們流程跟蹤是有幫助的。
除了瞭解流程圖,這裏我們先對整個OkHttp框架的設計模式做個簡單的介紹:
- OkHttp整體上使用外觀模式,通過OkHttpClient來統一對外暴露功能接口給使用者使用
- 在內部的模塊上呢,又使用了建造者模式來進行對象的創建和管理,從而使得對於複雜對象的管理和擴展都有較好的支持
2 源碼流程分析
2.1 同步請求源碼分析
一般我們同步請求會按照如下方式進行使用:
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
還是很簡潔的,但是其實內部流程可就沒有這麼簡潔了。先從我們new OkHttpClient進行說起。
2.1.1 OkHttpClient類介紹
這個類如果你去看源碼,會發現其有將近1000行的代碼,所以這裏就不貼出源代碼了。
這個類的設計是採用的外觀模式。這種模式使得OkHttp的很多功能接口都封裝在了內部,然後單獨對外提供OkHttpClient這個類來暴露給外部使用者進行調用。所以之前我們介紹的時候也說了,我們可以通過這個類可以配置許許多多的東西:
final Dispatcher dispatcher;
final @Nullable Proxy proxy;
final List<Protocol> protocols;
final List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors;
final List<Interceptor> networkInterceptors;
final EventListener.Factory eventListenerFactory;
final ProxySelector proxySelector;
final CookieJar cookieJar;
final @Nullable Cache cache;
final @Nullable InternalCache internalCache;
final SocketFactory socketFactory;
final SSLSocketFactory sslSocketFactory;
final CertificateChainCleaner certificateChainCleaner;
final HostnameVerifier hostnameVerifier;
final CertificatePinner certificatePinner;
final Authenticator proxyAuthenticator;
final Authenticator authenticator;
final ConnectionPool connectionPool;
final Dns dns;
final boolean followSslRedirects;
final boolean followRedirects;
final boolean retryOnConnectionFailure;
final int callTimeout;
final int connectTimeout;
final int readTimeout;
final int writeTimeout;
final int pingInterval;
上面列出了OkHttpClient類裏面的大部分可配置屬性,本篇文章對流程進行梳理,所以這裏不對每個屬性展開進行詳細說明了。當然了,對於這些屬性的配置都是通過內部類OkHttpClient.Builder
進行配置的。說完OkHttpClient,接着我們的流程,我們創建了Request對象。
2.1.2 Request和Response
這裏我們將Request和Response放在一起進行說明,因爲這兩個類是對HTTP中的請求和響應的抽象。
2.1.2.1 Request
這個類比較簡單,主要就是封裝了請求的url、method、headers、requestBody幾個屬性
public final class Request {
final HttpUrl url;
final String method;
final Headers headers; // 對HTTP中請求頭信息的封裝
final @Nullable RequestBody body; // 對HTTP請求體信息的封裝
final Map<Class<?>, Object> tags;
private volatile @Nullable 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.tags = Util.immutableMap(builder.tags);
}
public HttpUrl url() {
return url;
}
public String method() {
return method;
}
public Headers headers() {
return headers;
}
public @Nullable String header(String name) {
return headers.get(name);
}
public List<String> headers(String name) {
return headers.values(name);
}
public @Nullable RequestBody body() {
return body;
}
// 獲取當前請求中tag,我們可以通過其內部類Builder.tag()方法設置一個tag,以便我們在自定義Interceptor等情形下可以通過該tag做一些操作
public @Nullable Object tag() {
return tag(Object.class);
}
// 同上,只是該方法返回一個指定類型的tag
public @Nullable <T> T tag(Class<? extends T> type) {
return type.cast(tags.get(type));
}
public Builder newBuilder() {
return new Builder(this);
}
// 獲得本次請求的緩存控制策略(OkHttp中對緩存策略的控制信息抽象爲了CacheControl類)
public CacheControl cacheControl() {
CacheControl result = cacheControl;
return result != null ? result : (cacheControl = CacheControl.parse(headers));
}
// 判斷當前請求是否是https協議
public boolean isHttps() {
return url.isHttps();
}
}
由於這裏是採用的Builder模式,所以屬性的設置都統一放到內部類Request.Builder
中去了:
public static class Builder {
@Nullable HttpUrl url;
String method;
// 這裏涉及到的Headers也是用Builder模式進行構建
Headers.Builder headers;
@Nullable RequestBody body;
// 用於存放我們設置的tag
Map<Class<?>, Object> tags = Collections.emptyMap();
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder();
}
Builder(Request request) {
this.url = request.url;
this.method = request.method;
this.body = request.body;
this.tags = request.tags.isEmpty()
? Collections.<Class<?>, Object>emptyMap()
: new LinkedHashMap<>(request.tags);
this.headers = request.headers.newBuilder();
}
public Builder url(HttpUrl url) {
if (url == null) throw new NullPointerException("url == null");
this.url = url;
return this;
}
// 設置url
public Builder url(String url) {
if (url == null) throw new NullPointerException("url == null");
// Silently replace web socket URLs with HTTP URLs.
if (url.regionMatches(true, 0, "ws:", 0, 3)) {
url = "http:" + url.substring(3);
} else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
url = "https:" + url.substring(4);
}
return url(HttpUrl.get(url));
}
// 重置設置url的方法
public Builder url(URL url) {
if (url == null) throw new NullPointerException("url == null");
return url(HttpUrl.get(url.toString()));
}
// 設置header,如果之前有同name的值,會被替換
public Builder header(String name, String value) {
headers.set(name, value);
return this;
}
// 添加header,如果之前有同name的值,不會覆蓋,會形成一個name對應多個value的情況
public Builder addHeader(String name, String value) {
headers.add(name, value);
return this;
}
// 移除指定name的header
public Builder removeHeader(String name) {
headers.removeAll(name);
return this;
}
// 移除指定header
public Builder headers(Headers headers) {
this.headers = headers.newBuilder();
return this;
}
// 設置緩存策略
public Builder cacheControl(CacheControl cacheControl) {
String value = cacheControl.toString();
if (value.isEmpty()) return removeHeader("Cache-Control");
return header("Cache-Control", value);
}
// 下面就是實現不同的請求方式 就不再做一一說明了
public Builder get() {
return method("GET", null);
}
public Builder head() {
return method("HEAD", null);
}
public Builder post(RequestBody body) {
return method("POST", body);
}
public Builder delete(@Nullable RequestBody body) {
return method("DELETE", body);
}
public Builder delete() {
return delete(Util.EMPTY_REQUEST);
}
public Builder put(RequestBody body) {
return method("PUT", body);
}
public Builder patch(RequestBody body) {
return method("PATCH", body);
}
// 設置請求方式和請求體
public Builder method(String method, @Nullable RequestBody body) {
if (method == null) throw new NullPointerException("method == null");
if (method.length() == 0) throw new IllegalArgumentException("method.length() == 0");
if (body != null && !HttpMethod.permitsRequestBody(method)) {
throw new IllegalArgumentException("method " + method + " must not have a request body.");
}
if (body == null && HttpMethod.requiresRequestBody(method)) {
throw new IllegalArgumentException("method " + method + " must have a request body.");
}
this.method = method;
this.body = body;
return this;
}
// 設置tag
public Builder tag(@Nullable Object tag) {
return tag(Object.class, tag);
}
// 設置指定類型的tag
public <T> Builder tag(Class<? super T> type, @Nullable T tag) {
if (type == null) throw new NullPointerException("type == null");
if (tag == null) {
tags.remove(type);
} else {
if (tags.isEmpty()) tags = new LinkedHashMap<>();
tags.put(type, type.cast(tag));
}
return this;
}
public Request build() {
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
}
這個類不復雜,就是對HTTP中的請求進行了抽象,將對應的信息進行了封裝。url和method等簡單的信息,直接封裝爲對應屬性即可,複雜的請求頭和請求體信息用Headers和RequestBody兩個類來進一步進行處理。
2.1.2.2 Headers
通過前面Request的源碼,我們知道請求體都是封裝在這個類裏面的。
由於整個Headers的源碼有400多行,這裏就不全部貼出了,我們看一些常用的關鍵的信息即可:
public final class Headers {
// 用數組存儲請求頭的信息
private final String[] namesAndValues;
Headers(Builder builder) {
this.namesAndValues = builder.namesAndValues.toArray(new String[builder.namesAndValues.size()]);
}
private Headers(String[] namesAndValues) {
this.namesAndValues = namesAndValues;
}
// 獲取指定key最後(因爲一個key可能對應多個value,所以這裏是強調是最後一個)添加的value
public @Nullable String get(String name) {
return get(namesAndValues, name);
}
// 同上,只是這裏獲取的是Date(如果你添加了日期參數)
public @Nullable Date getDate(String name) {
String value = get(name);
return value != null ? HttpDate.parse(value) : null;
}
// 請求體打下(總共有多少對鍵值對)
public int size() {
return namesAndValues.length / 2;
}
// 通過索引獲取對應的key值
public String name(int index) {
return namesAndValues[index * 2];
}
// 通過索引獲取對應的value值
public String value(int index) {
return namesAndValues[index * 2 + 1];
}
// 下面還有些類似的方法 這裏就不過多介紹了
……
public Builder newBuilder() {
Builder result = new Builder();
Collections.addAll(result.namesAndValues, namesAndValues);
return result;
}
// 將請求體從數組轉爲Map
public Map<String, List<String>> toMultimap() {
Map<String, List<String>> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for (int i = 0, size = size(); i < size; i++) {
String name = name(i).toLowerCase(Locale.US);
List<String> values = result.get(name);
if (values == null) {
values = new ArrayList<>(2);
result.put(name, values);
}
values.add(value(i));
}
return result;
}
// 上面很多獲取name或者value的方法都是通過該方法獲取
private static String get(String[] namesAndValues, String name) {
// 這裏需要注意是從後往前取,有對應值的時候就返回,所以前面很多是取最後一個值原因就在這
for (int i = namesAndValues.length - 2; i >= 0; i -= 2) { // 同時這裏以2爲一個步長
if (name.equalsIgnoreCase(namesAndValues[i])) {
return namesAndValues[i + 1];
}
}
return null;
}
……
public static final class Builder {
// Builder裏面是用ArrayList來進行維護增刪改查的操作
final List<String> namesAndValues = new ArrayList<>(20);
……
// 常用的添加請求體的操作
public Builder add(String name, String value) {
checkName(name);
checkValue(value, name);
// 通過addLenient
return addLenient(name, value);
}
// 真正添加的地方
Builder addLenient(String name, String value) {
namesAndValues.add(name);
namesAndValues.add(value.trim());
return this;
}
……
// 通過set方法設置請求體
public Builder set(String name, String value) {
checkName(name);
checkValue(value, name);
// 這裏會先移除之前已經添加了該name的所有請求體
removeAll(name);
//通過addLenient添加
addLenient(name, value);
return this;
}
// 構建Headers對象
public Headers build() {
return new Headers(this);
}
}
}
這個類源代碼並沒有全部貼出,但是找了一些具有代表性的方法進行說麼,其他的功能和上面的方法類似,只是手段稍微有點不同。比如添加請求頭,我們這裏只貼出了添加指定name和value的方法,其他的還有直接通過"namve:value"這種格式字符串添加,或者直接添加日期參數。但是原理都一樣,所以就不全部一個一個解釋了。
上面的方法註釋也都比較清楚了,這裏就不再囉嗦解釋了。只是注意兩點就是
add
和set
兩個方法不同的地方在於set
多了一個’remove’操作。也就驗證了之前所述兩者的不同。- 另外這裏並沒有使用Map來維護請求頭的信息,而是用
ArrayList
來維護,最終build()
構建Headers的時候,再轉爲數組。這樣設計目的是爲了應對請求頭中同一name可能對應多個value的情況。在維護信息的時候,採取沒2個String
的值爲一個header步長。從上面的get(String[] namesAndValues, String name)
方法取值的時候就能看出來了。
2.1.2.3 RequestBody
說完請求頭,接下來說說請求體的信息封裝類RequestBody
public abstract class RequestBody {}
從聲明中看到這是一個抽象類,使用的時候,需要我們實現它的兩個抽象方法:
// 返回當前請求的MIME類型
public abstract @Nullable MediaType contentType();
// 將請求信息寫入到BufferedSink
public abstract void writeTo(BufferedSink sink) throws IOException;
具體使用在OkHttp3使用(一)-基本使用中提交流的小節當中有介紹。
當然考慮周到的OkHttp的開發者,是不會讓我們每次使用都自己去重新繼承這個類來實現請求體封裝的。如果是那樣,估計就沒這麼多人會使用這個框架了。所以OkHttp提供了常用的兩個實現類給我們日常開發使用。分別是:FormBody
(表單提交)和MultipartBody
(分塊提交,一般上傳文件使用)分別對應兩種不同的MIME類型
FormBody :“application/x-www-form-urlencoded”
MultipartBody:“multipart/”+xxx.
FormBody
和MultipartBody
的實現都不復雜,而且我們在OkHttp3使用(二)-常用類介紹也對這兩個類做了介紹,這裏就不再贅述了。
說完Request,再說下對應HTTP協議中響應的Response
類
2.1.2.4 Response
有了上面對Request的介紹,這個類理解起來也就比較簡單了:
public final class Response implements Closeable {
final Request request;
final Protocol protocol;
final int code;
final String message;
final @Nullable Handshake handshake;
final Headers headers;
// 相應體
final @Nullable ResponseBody body;
// 網絡請求的響應
final @Nullable Response networkResponse;
// 緩存響應
final @Nullable Response cacheResponse;
// 如果重定向等操作 代表重定向之前的響應
final @Nullable Response priorResponse;
// 發送請求的時間
final long sentRequestAtMillis;
// 收到響應的時間
final long receivedResponseAtMillis;
// 緩存控制
private volatile @Nullable CacheControl cacheControl; // Lazily initialized.
Response(Builder builder) {
this.request = builder.request;
this.protocol = builder.protocol;
this.code = builder.code;
this.message = builder.message;
this.handshake = builder.handshake;
this.headers = builder.headers.build();
this.body = builder.body;
this.networkResponse = builder.networkResponse;
this.cacheResponse = builder.cacheResponse;
this.priorResponse = builder.priorResponse;
this.sentRequestAtMillis = builder.sentRequestAtMillis;
this.receivedResponseAtMillis = builder.receivedResponseAtMillis;
}
// 獲取該次請求的Request
public Request request() {
return request;
}
// 返回採用的協議(如HTTP1/HTTP2等)
public Protocol protocol() {
return protocol;
}
// 返回碼
public int code() {
return code;
}
// 請求是否成功
public boolean isSuccessful() {
return code >= 200 && code < 300;
}
// 響應當中的提示信息
public String message() {
return message;
}
/**
* TLS相關
*/
public @Nullable Handshake handshake() {
return handshake;
}
// 響應頭信息
public List<String> headers(String name) {
return headers.values(name);
}
// 響應頭信息
public @Nullable String header(String name) {
return header(name, null);
}
// 響應頭信息
public @Nullable String header(String name, @Nullable String defaultValue) {
String result = headers.get(name);
return result != null ? result : defaultValue;
}
// 響應頭信息
public Headers headers() {
return headers;
}
// 獲取指定數量的響應信息
public ResponseBody peekBody(long byteCount) throws IOException {
BufferedSource peeked = body.source().peek();
Buffer buffer = new Buffer();
peeked.request(byteCount);
buffer.write(peeked, Math.min(byteCount, peeked.getBuffer().size()));
return ResponseBody.create(body.contentType(), buffer.size(), buffer);
}
// 獲取相應體
public @Nullable ResponseBody body() {
return body;
}
}
代碼裏面已經註釋的比較清楚了,加上之前對Request的解析,Response也是同樣的實現邏輯,將響應頭和響應體抽象爲了Headers
和ResponseBody
,這兩個類也很簡單,篇幅有限,所以這裏就不再囉嗦了,大家翻出源碼看看就應該清楚了。
接着最開始那個示例的流程,創建好Request之後,就交給OkHttpClient的newCall(request)
方法了,並返回了Call
對象。那我們看下這個方法裏面做了什麼
2.1.3 Call的創建
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
簡單,就一行代碼,調用了RealCall.newRealCall()
方法,並傳入了OkHttpClient
對象和Request
對象。繼續跟下去看看:
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
和Request
創建了RealCall
對象並返回了該對象。這樣我們就創建好了Call對象。
2.1.4 執行Call的excute()方法發起請求
上面創建好Call對象後,接着下面的流程就是調用了Call的excute()
方法。這裏我們知道,Call的實際類型是RealCall。所以進入RealCall的excute()
方法:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
// 通過Dispatcher進行請求任務管理,後面的章節會細講,這裏不影響我們隊流程的分析,跳過
client.dispatcher().executed(this);
// 獲得響應信息
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e);
throw e;
} finally {
// 任務調度有關(這裏暫不做分析)
client.dispatcher().finished(this);
}
}
上面我們通過第一個if判斷可以看出,一個Call對象只能請求一次,否則就會報錯。
最終是通過調用getResponseWithInterceptorChain();
獲得響應的信息並返回給調用者。
getResponseWithInterceptorChain();
裏面就是通過發起網絡請求獲取數據的過程,裏面涉及到非常多的東西,後續我們的章節會進入到該方法進行詳細說明,這裏我們暫時知道該方法會發起網絡請求,並且返回數據給我們就行了。
這樣我們整個同步請求的流程就算是走完了,下面接着說說異步請求的流程。
2.2 異步請求執行流程
使用過異步請求的朋友應該知道異步請求很多步驟和上面同步請求是一樣的,所以後續我們分析的時候,同樣的步驟我們就直接跳過了。
我們還是先看一段異步請求的代碼:
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
client.newCall(request).enqueue(new Callback() {
@Override public void onFailure(Call call, IOException e) {
……
}
@Override public void onResponse(Call call, Response response) throws IOException {
……
}
}
});
可以看到前面一直到Call對象的創建。流程和同步請求都是一致的,這裏就不做分析了。最後通過調用Call.enqueue(CallBack)
方法就完成了異步請求,我們進去看下這個方法裏面做了什麼:
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
非常簡單,通過client.dispatcher().enqueue(new AsyncCall(responseCallback));
執行的。之前我們分析同步流程的時候,也有類似的流程,只是最終調用的是client.dispatcher().executed(this);
。前面也說了,這裏涉及到OkHttp對請求任務的調度管理,後面章節會詳細介紹。這裏我們先知道通過該處調用後。最終會交給AsyncCall的executeOn(ExecutorService executorService)
方法進行處理。就進入AsyncCall這個類中看看。
2.2.1 AsyncCall中流程
接着上面說的流程,會進入executeOn(ExecutorService executorService)
方法
// 這裏通過線程池執行任務。
void executeOn(ExecutorService executorService) {
assert (!Thread.holdsLock(client.dispatcher()));
boolean success = false;
try {
// 通過線程池執行
executorService.execute(this);
success = true;
} catch (RejectedExecutionException e) {
InterruptedIOException ioException = new InterruptedIOException("executor rejected");
ioException.initCause(e);
eventListener.callFailed(RealCall.this, ioException);
responseCallback.onFailure(RealCall.this, ioException);
} finally {
if (!success) {
client.dispatcher().finished(this); // This call is no longer running!
}
}
}
這個方法裏面也比較簡單,調用了executorService.execute(this);
方法。executorService是一個線程池對象,瞭解線程池知識的朋友應該知道,最終會進入到該類的run
方法(這裏補充AsyncCall繼承自NamedRunnable,其又實現了Runnable接口)。但是你會發現AsyncCall
中沒有run
方法,它的實現是在其父類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 {
// run方法裏面最終調用了excute()方法。
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
// 抽象方法,由其子類實現(該處爲AsyncCall)
protected abstract void execute();
}
該類的run方法裏面,又調用了execute();
方法,這是一個抽象方法,所以實現在其子類(AsyncCall)中,又回到AysncCall
:
final class AsyncCall extends NamedRunnable {
// 這個就是我們的異步回調的CallBack對象,通過上述AsyncCAll構造方法傳入。
private final Callback responseCallback;
……
// 真正發起請求的地方
@Override protected void execute() {
boolean signalledCallback = false;
timeout.enter();
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) {
e = timeoutExit(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);
}
}
}
這裏面也是通過getResponseWithInterceptorChain()
方法獲取到Response
。獲取到之後,通過我們之前通過AsyncCall
構造方法傳入的Callback
對象,回調對應的成功、失敗的方法即可。
通過前面的分析知道,是通過線程池來執行AsyncCall
的,所以該處運行的線程是在Runnable的run
方法所在的線程,回調並沒有進行線程切換,也是發生在該線程,並不是在主線程,所以我們使用的時候,要注意不要在回調方法中做更新UI的操作。
到此,我們同步請求和異步請求的流程就分析完了,這個時候再回過去看最開始那張流程圖,是不是感覺就清晰了許多。