okhttp3 簡單原始碼解析
前言
對OKhttp的原始碼看了有一段時間了,感覺有點體會,準備寫點東西,深刻深刻,不然過段時間就忘了O(∩_∩)O哈哈~
網上已經有了很多對OKhttp3的原始碼分析文章,說的也都比較好,但是覺得還是看別人寫的再好終究是別人的東西,自己寫的過程中會對OKhttp的理解更加深刻,而且1000個人的眼裡有一千個哈姆雷特,每個人對OKhttp的理解,認識,還是會有不同的。
對OKhttp的原始碼分析,一篇文章也是說不完的,能說完的話,那也太省略,長篇大論的文章,也很難讓人看下去,所以,準備寫一個關於OKhttp原始碼的分析的系列文章,本篇主要寫的是OKhttp的工作流程,並對流程中的要點進行簡要概述,後續的文章中在精要分析。
概述
基本用法
先看基本用法,通過他的用法,來分析他的原理。
OKhttp請求分為同步請求和非同步請求,程式碼如下:
同步請求:
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
return response .body().string();
}
非同步請求:
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.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 {
}
});
同步請求比較簡單,所以我們來分析,非同步請求,非同步請求的原理明白了,同步的自然而然就知道了。
流程原始碼剖析
從程式碼中我們不難看出來,OKhttpClient,Request,Response這些都是比較重要的類。我們看OKhttpClient的構造方法 new OKhttpClient();
public OkHttpClient() {
this(new Builder());
}
他的無參構造方法中呼叫了有Builder引數的構造方法,自己new 了一個Builder物件穿了進去。我們接著看new Builder()方法。
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
...
...
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
發現該構造方法中初始化了一些物件,賦值了變數。
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
this.proxy = builder.proxy;
this.protocols = builder.protocols;
...
...
this.connectTimeout = builder.connectTimeout;
this.readTimeout = builder.readTimeout;
this.writeTimeout = builder.writeTimeout;
this.pingInterval = builder.pingInterval;
if (interceptors.contains(null)) {
throw new IllegalStateException("Null interceptor: " + interceptors);
}
if (networkInterceptors.contains(null)) {
throw new IllegalStateException("Null network interceptor: " + networkInterceptors);
}
}
上面程式碼我們看到,把Builder的變數的值賦給了OKhttpClient,我省略的部分程式碼,不然程式碼量會比較多。
這種Builder建造者模式的設計在OKhttp專案中有很多,比如Request,Response,CacheControl等類,都採用了Builder模式來設計。比較靈活方便。
然後我們看Request.Builder.url(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);
}
HttpUrl parsed = HttpUrl.parse(url);
if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
return url(parsed);
}
這個方法的作用,是對url進行的判斷,轉化。如果傳進來的url字串是一個無效的http,https URL,會出現IllegalArgumentException,為了避免這種情況,就出現了這個方法,通過呼叫HttpUrl.parse()方法來進行轉化,如果是無效的http,https URL,會返回null,而不出現IllegalArgumentException。
通過上面的方法,把要請求的url設定進去之後,接下來就呼叫了client.newCall(request).enqueue(Callback)請求程式碼,我們看一下newCall(request)的原始碼:
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
我們發現他是建立了一個RealCall物件,RealCall是實現Call介面,enqueue()方法是Call介面的方法,所以,執行enqueue()方法的是RealCall物件,所以我們來看RealCall物件裡面的enqueue()方法。
okhttp裡面的Call概念是一個將要被執行網路請求,可以被取消,只可以被執行一次。
@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)); 這裡我們發現,繼續呼叫了Dispatcher的enqueue()方法,此刻OKhttp裡面很重要的一個類出現在我們面前了,那就是Dispatcher(排程分配器)。其實之前在OKhttpClient物件建立的時候,就有dispatcher的身影了,不過這次是比較明顯的看到 。。。
Dispatcher (排程分配器)
Dispatcher是一個主要在非同步請求時會用到的一個策略,我看有的文章說的比較絕對,說只有在非同步請求才會用到,其實同步也有用到,只不過沒非同步所佔的份額大罷了。
Dispatcher內部維護了一個執行緒池,用來對Call網路請求,進行處理。每一個dispatcher可以最大併發64個請求,每一臺主機可以最大併發5個。我們從下面程式碼可以看到:
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;
}
上面程式碼中還有一些通過名字就知道的變數,就不介紹了。我們主要看一下執行緒池的創鍵,這邊在建立執行緒池的時候,沒有用常用的四大執行緒池,他建立了一個核心執行緒數為0,最大執行緒數為Integer.MAX_VALUE,執行緒存活時間為1分鐘,容器為SynchronizeQueue的這樣一個執行緒池。
關於SynchronizeQueue的介紹可以看這裡SynchronizeQueue內容
他這個執行緒池很像四大執行緒池中的newCacheThreadPool。適合處理請求頻繁,高併發的任務。
這裡的執行緒池,預設是這樣的,如果有需求可以自己設定的。
Dispatcher如他名字的含義一樣,排程。進他的原始碼可以看到,它裡面的方法就是,執行請求,取消請求,結束請求等之類的呼叫,他的類裡面的程式碼不多,大家可以自己進去看看,很容易理解。這裡提一嘴,建議在看這篇文章的同學,採取邊看文章,邊看原始碼的方式來理解掌握。
回過頭來繼續看這句程式碼:
client.dispatcher().enqueue(new AsyncCall(responseCallback));
我們知道他就是呼叫Dispatcher裡面的enqueue方法,我們來看,Dispatcher裡面的enqueue方法。
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
} }
runningAsyncCalls是一個儲存正在進行非同步請求的容器,這邊對它進行了判斷,如果runningAsyncCalls的size小於最大併發量64,並且每臺主機的正在請求量小於5(maxRequestsPerHost),runningAsyncCalls就把這個非同步請求新增進去,接著用通過呼叫執行緒池中的執行緒去執行這個AsyncCall,否者就用readyAsyncCalls容器去儲存該請求。這段邏輯很好理解。我們現在看一下AsyncCall。看看這個AsyncCall是什麼。
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
這裡我們發現了之前傳過來的Callback回撥,AsyncCall是RealCall的內部類。AsyncCall 繼承NamedRunnable ,咦。。有Runnable,難到。。。我們接著看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 {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
}
果然~~,NameRunnable是實現了Runnable介面。是一個抽象類,在run方法中執行了execute()方法,execute方法是一個抽象方法。他的實現在NameRunnable子類中,AsyncCall是NameRunnable的子類。所以,我們就定位到了AsyncCall類中的關鍵方法execute()。
@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) {
// 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);
}
}
看到這裡,我們要知道,之前Dispatcher裡面的執行緒池執行的任務就是上面的execute()方法。然後根據上面的原始碼我們發現,在這裡獲取到了Response,網路響應結果。並且對根據條件,處理了對Callback回撥的響應。
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
之前我們在使用OKhttp進行一步請求的時候,傳進去的Callback,結果回撥,就是AsyncCall 裡面execute()方法裡面的Callback。
獲取到Response的這行程式碼
Response response = getResponseWithInterceptorChain();
我們待會再看。我們接著看,發現finally語句塊中執行的下面的語句:
client.dispatcher().finished(this);
呼叫了Dispatcher的finished方法。
/** Used by {@code AsyncCall#run} to signal completion. */
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
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();
}
}
這裡的promoteCalls引數,在非同步請求的情況下,Boolean值為true,同步的話,值為false。我們看到finished方法裡面有一個同步程式碼塊,首先他移除了當前正在執行的call(請求都完成了,到這該結束),邏輯沒毛病。然後獲取正在執行的請求數量,如果數量==0,idleCallback不為零,就執行idleCallback的run方法。idleCallback預設為null的,使用者有需要得設定了才有。咦,我們剛剛是不是錯過了什麼邏輯。對了,這行程式碼還沒看呢。沒事咱現在看。
if (promoteCalls) promoteCalls();
我們分析的是非同步請求,所以promoteCalls是true。我們看promoteCalls()方法。
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.
}
}
邏輯很簡單,我就不細說了,就是把readyAsyncCalls準備執行裡的請求放到runningAsyncCalls正在執行的容器裡,並通過executorService().execute(call);來執行。
接下來我們來看之前被我們放在一邊的獲取Response的方法,程式碼如下:
Response response = getResponseWithInterceptorChain();
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());//注意1
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());//注意2
}
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);
}
這裡我們見到了OKhttp裡面的很重要一個介面Interceptor(攔截器)。
Interceptor(攔截器)
interceptor是什麼?他是用來對發出去的網路請求和對應響應的responses 進行(Observes)觀察,(modifies)修改和potentially short-circuits(可能的短路?掉線?不好翻譯,理解吧),也就是對Request和Response進行修改,處理的一個藉口。通常,interceptor(攔截器)會對Request和Response的Header進行add,remove,transform等操作。
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
...
...
省略...
我們現在來看上面的getResponseWithInterceptorChain()方法。他首先建立了一個ArrayList集合把一些xxxInterceptor新增進去,然後建立了RealInterceptorChain,並執行chain.proceed(originalRequest);方法。這裡先說一個結論就是interceptors容器裡面的各式各樣的interceptor攔截器會,按新增的順序,逐個執行intercept()方法。這個結論後續會被證明。然後我們上面程式碼,有兩個注意點,那兩個地方的interceptor是開發者,根據需要選擇是否設定的。設定後執行順序就是add的順序。
接著我們看RealInterceptorChain.proceed(originalRequest);方法。
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
這裡,我刪除了一些非關鍵程式碼,方便我們瀏覽。剛開始,index和calls的值都是0,我們看到他建立了一個RealInterceptorChain型別的next物件,他的構造方法這裡注意一下,他的構造方法傳進去的引數,有一個index+1,也就是說這個RealInterceptorChain的index值+1了。變成了1 注意這裡是新建立的next物件裡面的index值加一了,並不是說現在在所的物件裡面index加一,Interceptor interceptor = interceptors.get(index);在執行該語句時,index還是0,這裡如果開發者通過呼叫OKhttpClient的addInterceptor(Interceptor interceptor)方法,設定了攔截器,則會先執行使用者通過該方法設定的攔截器,否則就是執行RetryAndFollowUpInterceptor 攔截器。這個攔截器看名字我們很容易知道,他是失敗重試和重定向的時候發揮作用的,這裡我們先不細說了。本篇文章的主要目的是捋清流程。我們接著看,這裡我們沒有add 自定義的攔截器,所以就執行了RetryAndFollowUpInterceptor 攔截器的interceptor.intercept(next);方法,注意這裡把next物件,穿進去了,還有要記得我們之前強調的,next物件裡面的index的值加一了已經,變成了1了。
然後我們看RetryAndFollowUpInterceptor 攔截器的interceptor.intercept(next);方法。
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;//注意1
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
call, eventListener, callStackTrace);
int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
response = realChain.proceed(request, streamAllocation, null, null);//注意2
releaseConnection = false;
上面的程式碼,別的我們先不看,我們主要看我上面標註的注意1,和注意2處的程式碼。看注意1,這裡傳進來的chain就是next物件,然後注意2,那邊realChain.proceed()方法,(⊙o⊙)哦~有點熟悉哦,我們進去看一下。
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
發現又走了回來,注意,此時index的值不再是0了,此時變成了1,接著就是上面一樣的邏輯,建立新的next物件,index加1,從interceptors容器中取出下一個interceptor攔截器,執行intercept(next)方法。正是這種遞迴式的呼叫,來吧interceptors集合裡面的所有攔截器都跑一邊,最後獲取到Response物件。
總結
不自覺中說了這麼多,之後的文章會對Interceptor的各個實現類,CacheControl等重要類進行剖析。最後上一張總結圖: