最近抽時間分析一下 OKHttp3 的源碼,關(guān)于 OKHttp 源碼解析的資料已經(jīng)有很多了,但是關(guān)于 OKHttp3 源碼解析的文章似乎不太多,這一系列文章主要是針對 OKHttp3 的源碼進行解析。首先,本篇文章將分析一下 OKHttp3 的整體流程,細節(jié)相關(guān)的知識會在后續(xù)的文章中進行介紹。好了,那咱就發(fā)車吧~ 本文中涉及到的自定義類的源碼都在 Github 上的 OkHttpPractice 工程中。
- OKHttp 的簡單使用
- 請求的整體流程
1. OKHttp 的簡單使用
OKHttp 支持異步請求和同步請求,下面簡單介紹一下異步請求和同步請求。
1.1 異步請求
因為是異步的網(wǎng)絡請求,不會阻塞 UI 主線程,所以異步請求可以直接寫在 UI 主線程中。下面是異步的 GET 請求:
String url = "https://www.baidu.com";
OkHttpClient client = new OkHttpClient.Builder()
.build();
Request request = new Request.Builder()
.method("GET", null)
.url(url)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.i("lijk", "onFailure " + e.toString());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.i("lijk", "onResponse ");
}
});
1.2 同步請求
相對于異步請求,不可以在 UI 主線程中直接使用異步請求,必須子線程中進行該請求。
下面是同步的 GET 請求:
try {
String url = "https://www.baidu.com";
OkHttpClient client = new OkHttpClient.Builder()
.build();
Request request = new Request.Builder()
.method("GET", null)
.url(url)
.build();
client.newCall(request).execute();
} catch (IOException e) {
e.printStackTrace();
}
2. 請求的整體流程
從上一小節(jié)中可以大概知道 OKHttp 簡單的使用,我們就從這兒開始分析 OKHttp 請求的整體流程。先分析異步請求,了解異步請求的流程之后,自然會明白同步請求。
2.1 異步請求
2.1.1 OkHttpClient
首先是創(chuàng)建一個 OkHttpClient
對象,可以通過下面兩種方式創(chuàng)建 OKHttpClient
的對象,如下所示:
// 方式一
OkHttpClient client = new OkHttpClient.Builder()
.build();
// 方式二
OkHttpClient client = new OkHttpClient();
OKHttpClient
的源碼如下所示:
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
......
public OkHttpClient() {
this(new Builder());
}
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
this.proxy = builder.proxy;
this.protocols = builder.protocols;
this.connectionSpecs = builder.connectionSpecs;
this.interceptors = Util.immutableList(builder.interceptors);
this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
this.eventListenerFactory = builder.eventListenerFactory;
this.proxySelector = builder.proxySelector;
this.cookieJar = builder.cookieJar;
this.cache = builder.cache;
this.internalCache = builder.internalCache;
this.socketFactory = builder.socketFactory;
boolean isTLS = false;
for (ConnectionSpec spec : connectionSpecs) {
isTLS = isTLS || spec.isTls();
}
if (builder.sslSocketFactory != null || !isTLS) {
this.sslSocketFactory = builder.sslSocketFactory;
this.certificateChainCleaner = builder.certificateChainCleaner;
} else {
X509TrustManager trustManager = systemDefaultTrustManager();
this.sslSocketFactory = systemDefaultSslSocketFactory(trustManager);
this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
}
this.hostnameVerifier = builder.hostnameVerifier;
this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
certificateChainCleaner);
this.proxyAuthenticator = builder.proxyAuthenticator;
this.authenticator = builder.authenticator;
this.connectionPool = builder.connectionPool;
this.dns = builder.dns;
this.followSslRedirects = builder.followSslRedirects;
this.followRedirects = builder.followRedirects;
this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
this.connectTimeout = builder.connectTimeout;
this.readTimeout = builder.readTimeout;
this.writeTimeout = builder.writeTimeout;
this.pingInterval = builder.pingInterval;
}
......
public static final class Builder {
Dispatcher dispatcher;
@Nullable Proxy proxy;
List<Protocol> protocols;
List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors = new ArrayList<>();
final List<Interceptor> networkInterceptors = new ArrayList<>();
EventListener.Factory eventListenerFactory;
ProxySelector proxySelector;
CookieJar cookieJar;
@Nullable Cache cache;
@Nullable InternalCache internalCache;
SocketFactory socketFactory;
@Nullable SSLSocketFactory sslSocketFactory;
@Nullable CertificateChainCleaner certificateChainCleaner;
HostnameVerifier hostnameVerifier;
CertificatePinner certificatePinner;
Authenticator proxyAuthenticator;
Authenticator authenticator;
ConnectionPool connectionPool;
Dns dns;
boolean followSslRedirects;
boolean followRedirects;
boolean retryOnConnectionFailure;
int connectTimeout;
int readTimeout;
int writeTimeout;
int pingInterval;
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
proxySelector = ProxySelector.getDefault();
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
connectionPool = new ConnectionPool();
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
......
}
......
}
可以看到,OKHttpClient
對象是通過 Builder 模式創(chuàng)建的,在 OKHttpClient.Builder()
類的構(gòu)造方法中,對它的屬性都有默認值和默認對象。
2.1.2 RealCall
創(chuàng)建好 OkHttpClient
對象之后,通過調(diào)用 OkHttpClient
對象的 newCall(Request request)
方法即可創(chuàng)建一個 Call
對象,newCall(Request request)
方法如下所示:
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
可以看到,newCall(Request request)
方法生成的是 RealCall
類型的對象,RealCall
的源碼并不算長,如下所示:
final class RealCall implements Call {
final OkHttpClient client;
final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor;
final EventListener eventListener;
/** The application's original request unadulterated by redirects or auth headers. */
final Request originalRequest;
final boolean forWebSocket;
// Guarded by this.
private boolean executed;
RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
final EventListener.Factory eventListenerFactory = client.eventListenerFactory();
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
// TODO(jwilson): this is unsafe publication and not threadsafe.
this.eventListener = eventListenerFactory.create(this);
}
......
}
對于異步請求,會調(diào)用 RealCall
對象的 enqueue(Callback responseCallback)
方法,如下所示:
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
在 enqueue(Callback responseCallback)
方法中,會調(diào)用 Dispatcher
的 enqueue(AsyncCall call)
方法,生成一個 AsyncCall
對象,并將其傳入到 Dispatcher
對象中去。
2.1.3 Dispatcher
接著分析一下 Dispatcher
的源碼,如下所示:
public final class Dispatcher {
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
private @Nullable Runnable idleCallback;
/** Executes calls. Created lazily. */
private @Nullable ExecutorService executorService;
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
public Dispatcher(ExecutorService executorService) {
this.executorService = executorService;
}
public Dispatcher() {
}
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;
}
......
synchronized void enqueue(AsyncCall call) {
// runningAsyncCalls異步運行任務的隊列個數(shù)如果小于 64
// 每個主機同時被請求的個數(shù)小于 5
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
// 將異步任務加入到異步運行隊列中
runningAsyncCalls.add(call);
// 通過線程池運行該異步任務
executorService().execute(call);
} else {
// 如果不滿足上面兩個條件,則將異步任務加入到預備異步任務隊列中
readyAsyncCalls.add(call);
}
}
......
}
從 Dispatcher
的 enqueue(AsyncCall call)
方法中可以看到,得到異步任務之后,如果異步任務運行隊列中的個數(shù)小于 64 并且每個主機正在運行的異步任務小于 5,則將該異步任務加入到異步運行隊列中,并通過線程池執(zhí)行該異步任務,若不滿足以上兩個條件,則將該異步任務加入到預備異步任務隊列中。
2.1.4 AsyncCall
AsyncCall
是 RealCall
的內(nèi)部類,源碼如下:
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
String host() {
return originalRequest.url().host();
}
Request request() {
return originalRequest;
}
RealCall get() {
return RealCall.this;
}
@Override protected void execute() {
boolean signalledCallback = false;
try {
// 得到響應對象
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
// 取消請求時,回調(diào)失敗方法
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
// 回調(diào)成功方法
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 {
// 拋出異常時,回調(diào)失敗方法
responseCallback.onFailure(RealCall.this, e);
}
} finally {
// 從 Dispatcher 對象的異步請求隊列中移除該任務
client.dispatcher().finished(this);
}
}
}
可以先看一下在 finally
代碼塊中的部分,調(diào)用 Dispatcher
對象的 finished(AsyncCall call)
方法,源碼如下:
/** Used by {@code AsyncCall#run} to signal completion. */
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
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!");
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
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; // Reached max capacity.
}
}
最終會調(diào)用 finished(Deque<T> calls, T call, boolean promoteCalls)
方法,會將傳入的 Call
對象從正在運行的異步任務隊列中移除,并將符合條件的預備異步任務隊列中的任務加入到正在運行的異步任務隊列中,并將其放入線程池中執(zhí)行。
在 AsyncCall
中最重要的就是 execute()
方法,其中最重要的就是下面一行:
Response response = getResponseWithInterceptorChain();
可以看到從 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) {
// 用戶定義的網(wǎng)絡攔截器
interceptors.addAll(client.networkInterceptors());
}
// 請求服務器攔截器
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
// 通過 RealInterceptorChain 對象鏈式的調(diào)用攔截器,從而得到響應。
return chain.proceed(originalRequest);
}
對 OKHttp 有所了解的人應該都知道,攔截器 (interceptor)
是 OKHttp 中非常重要的思想,在源碼中攔截器應用的也非常廣泛和重要,上面源碼中的攔截器串聯(lián)起來并執(zhí)行,就完成了本次的網(wǎng)絡請求,在下一篇文章中會重點介紹攔截器的知識。
通過對 getResponseWithInterceptorChain()
方法的分析,可以看到將所有的攔截器都聚合之后,生成一個 RealInterceptorChain
攔截器調(diào)用鏈對象 chain
,在 chain
中遞歸的調(diào)用所有的攔截器,最后將得到的結(jié)果返回。
這里使用的是責任鏈模式,攔截器分層的思想也是借鑒了網(wǎng)絡協(xié)議中的分層思想,請求從最上層到最下層,響應是從最下層到最上層。
2.1.5 RealInterceptorChain
public final class RealInterceptorChain implements Interceptor.Chain {
......
@Override public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
return response;
}
}
在 RealInterceptorChain
中最重要的就是 proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection)
方法,將之前的攔截器列表串聯(lián)起來并執(zhí)行,得到響應結(jié)果之后,再將結(jié)果向上一層層返回。
2.2 同步請求
分析完異步請求的流程之后,同步請求的流程就比較容易理解。
和異步請求一樣,會先調(diào)用 OkHttpClient
中的 newCall(Request request)
方法生成一個 RealCall
對象
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
接下來和異步請求就不一樣了,會調(diào)用 RealCall
對象的 execute()
方法,源碼如下所示:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
- 會調(diào)用
Dispatcher
對象的executed(RealCall call)
將該同步任務加入到Dispatcher
對象的同步任務隊列中去 - 接著會調(diào)用
getResponseWithInterceptorChain()
方法獲取到請求的響應對象。對,就是之前異步任務請求獲取響應對象的同一個方法。 - 最終,會走
finally
代碼塊,將此同步任務從Dispatcher
對象的同步任務隊列中移除。
如果理解之前異步任務請求的流程,那么同步請求的流程就非常容易理解了。
本文中涉及到的自定義類的源碼都在 Github 上的 OkHttpPractice 工程中。
參考資料:
OkHttp源碼解析 -- 俞其榮
OkHttp源碼解析 -- 高沛