在如今的Android開發中,請求網絡獲取數據基本上成了咱們的標配。在早期的Android開發中會有人使用HttpClient、HttpUrlConnection或者Volley等網絡請求方式,但對於現在(2018年)而言,絕大多數的開發者都會使用OkHttp+Retrofit+RxJava進行網絡請求,而對於這三者而言,實際請求網絡的框架是OkHttp,因此OkHttp的重要性不言而喻。web
//建立OkHttpClient對象
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
//建立Request請求對象
Request request = new Request.Builder()
.url(url)
.build();
//建立Call對象,並執行同步獲取網絡數據
Response response = client.newCall(request).execute();
return response.body().string();
}
複製代碼
使用OkHttp基本是如下四步:api
一般來講,咱們使用OkHttp並不會直接經過new OkHttpClient()
來建立出一個OkHttpClient。通常來講,咱們會對這個OkHttpClient作一些配置,好比:緩存
OkHttpClient.Builder().connectTimeout(
DEFAULT_MILLISECONDS, TimeUnit.SECONDS).readTimeout(
DEFAULT_MILLISECONDS, TimeUnit.SECONDS).addInterceptor { chain ->
val builder = chain.request().newBuilder()
headerMap?.forEach {
builder.addHeader(it.key, it.value)
}
val request = builder.build()
chain.proceed(request)
}.addInterceptor(httpLoggingInterceptor).build()
複製代碼
上面是一段使用Kotlin代碼建立OkHttpClient的過程,很明顯,OkHttpClient內部是使用了 Builder 模式,好處很明顯: 咱們在建立對象的同時能夠自由的配置咱們須要的參數 。咱們簡單看一下OkHttpClient內部類Builder中的構造方法,看一下OkHttpClient內部均可以作哪些配置:安全
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;
//默認鏈接超時10s
connectTimeout = 10_000;
//默認讀取超時10s
readTimeout = 10_000;
//默認寫入超時10s
writeTimeout = 10_000;
pingInterval = 0;
}
複製代碼
上面的代碼中咱們很是熟悉的就是鏈接超時、讀取超時、寫入超時,它們的默認事件都是10s,其實這也提醒咱們,若是咱們想要設置的超時時間也是10s的話,徹底沒有必要重複進行配置,其實個人建議也是不須要配置,直接使用默認的就好。值得注意的是 Dispatcher 這個類,這是一個網絡請求的分發器,主要做用是在同步,異步網絡請求時會作一些不一樣的分發處理,咱們先有個印象便可, Dispatcher 會在以後詳細的分析。bash
可能細心的小夥伴這時候會說了:我平時會對OkHttpClient加上一些interceptor來攔截網絡請求,比方說在請求以前加上token等請求頭之類的,上面這段代碼爲何沒有攔截器相關的變量呢?服務器
沒錯,OkHttpClient中的Builder類內部確實是有攔截器相關成員變量,只不過沒寫在Builder的構造方法內:cookie
public static final class Builder {
//省略無關代碼......
final List<Interceptor> interceptors = new ArrayList<>();
final List<Interceptor> networkInterceptors = new ArrayList<>();
//省略無關代碼......
}
複製代碼
咱們日常添加的interceptor就存放在interceptors這個ArrayList中。OkHttpClient對象的配置建立不是什麼難以理解的點,接下來咱們看Request對象的建立。網絡
爲何要建立Request對象,很簡單,咱們請求網絡須要一些必要的參數,好比url,請求方式是get或者post等等信息。而Request這個類就是對這些網絡請求參數的統一封裝。看一下代碼就一目瞭然了:框架
public final class Request {
final HttpUrl url;
final String method;
final Headers headers;
final @Nullable RequestBody body;
final Object tag;
private volatile CacheControl cacheControl; // Lazily initialized.
//省略無關代碼......
}
複製代碼
相信你們都能看明白,這個Request類中封裝了url、請求方式、請求頭、請求體等等網絡請求相關的信息。Request裏面也是一個Builder模式,這裏就不贅述了。異步
Call對象咱們能夠這樣理解:Call對象是對 一次 網絡請求的封裝。注意這個關鍵字: 一次 ,熟悉OkHttp的同窗應該都知道,一個Call對象只能被執行一次,不管是同步execute仍是異步的enqueue,那麼這個只能執行一次的特性是如何保證的呢?咱們來看代碼:
@Override public Call newCall(Request request) {
//其實是經過 RealCall.newRealCall 來獲取Call對象
return RealCall.newRealCall(this, request, false /* for web socket */);
}
複製代碼
上面的代碼能看到OkHttpClient的newCall其實是經過RealCall.newRealCall(this, request, false /* for web socket */)
來得到的,咱們來看一下這個RealCall:
final class RealCall implements Call {
final OkHttpClient client;
//錯誤重試與重定向攔截器
final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor;
//監聽OkHttp網絡請求各個階段的事件監聽器
private EventListener eventListener;
final Request originalRequest;
final boolean forWebSocket;
//判斷Call對象是否被執行過的標誌變量
private boolean executed;
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Call只是一個接口,咱們實際建立的是Call的實現類RealCall的對象
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
Override public Response execute() throws IOException {
//確保線程安全的狀況下經過executed來保證每一個Call只被執行一次
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
//省略無關代碼......
}
@Override public void enqueue(Callback responseCallback) {
/確保線程安全的狀況下經過executed來保證每一個Call只被執行一次
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
//省略無關代碼
}
//省略無關代碼......
}
複製代碼
咱們能夠看到,Call只是一個接口,咱們建立的其實是RealCall對象。在RealCall中存在一個 execute 的成員變量,在execute()和enqueue(Callback responseCallback) 方法中都是經過 execute 來確保每一個RealCall對象只會被執行一次。
建立Call對象的過程其實也是很簡單的,麻煩的地方在最後一步: **execute()和enqueue(Callback responseCallback) **
前三步很是簡單,咱們能夠知道並無涉及網絡的請求,因此核心確定是在這關鍵的第四步。
先說同步請求,看代碼:
@Override public Response execute() throws IOException {
//經過executed確保每一個Call對象只會被執行一次
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
//網絡請求開始的回調
eventListener.callStart(this);
try {
//調用分發器的executed(this)方法
client.dispatcher().executed(this);
//真實的網絡請求是在這裏處理的
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
//網絡請求失敗的回調
eventListener.callFailed(this, e);
throw e;
} finally {
//網絡請求結束
client.dispatcher().finished(this);
}
}
複製代碼
execute() 方法中首先經過executed確保每一個Call對象只會被執行一次,以後調用了eventListener.callStart(this);
來執行網絡請求開始的回調。接下來調用了client.dispatcher().executed(this)
,那麼這句代碼具體是作了什麼呢:
public Dispatcher dispatcher() {
//返回了一個OkHttpClient內部的dispather分發器
return dispatcher;
}
複製代碼
這句代碼首先返回一個 dispatcher ,這個分發器咱們在上面也提到過,這是一個比較重要的概念,來看一下這個分發器:
public final class Dispatcher {
//最大請求數
private int maxRequests = 64;
//每一個host的最大請求數
private int maxRequestsPerHost = 5;
//網絡請求處於空閒時的回調
private @Nullable Runnable idleCallback;
//線程池的實現
private @Nullable ExecutorService executorService;
//就緒等待網絡請求的異步隊列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//正在執行網絡請求的異步隊列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//正在執行網絡請求的同步隊列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
//忽略無關代碼......
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
synchronized void executed(RealCall call) {
//將call對象加入網絡請求的同步隊列中
runningSyncCalls.add(call);
}
//忽略無關代碼......
}
複製代碼
能夠看到 Dispatcher 這個分發器類內部定義了不少的成員變量:maxRequests 最大請求個數,默認值是64; maxRequestsPerHost 每一個host的最大請求個數,這個host是什麼?舉個栗子,一個URL爲 http://gank.io/api ,那麼host就是 http://gank.io/ 至關於baseUrl。 ** idleCallback** 這是一個空閒狀態時的回調,當咱們的全部的網絡請求隊列爲空時會執行。 executorService 這是一個線程池,主要是爲了高效執行異步的網絡請求而建立的線程池,以後會再次提到它。接下來就是比較重要的三個隊列:
對於這三個隊列來講,執行同步請求的Call對象會加入到runningSyncCalls中;執行異步請求的Call對象會加入到readyAsyncCalls或者runningAsyncCalls中,那麼何時加入到等待隊列,何時加入到執行隊列呢?簡單的說,若是執行異步網絡請求的線程池很忙,異步請求的Call對象會加入到等待隊列;反之則加入到執行隊列。那麼這個忙於不忙的標準是什麼呢?很簡單,在enqueue方法中有runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost
一個判斷的標準,即正在執行的異步隊列中Call對象個數小於maxRequests(64)而且執行隊列中的同一個host對應的Call對象個數小於maxRequestsPerHost(5)的時候。
說完了 Dispatcher 關鍵的成員變量,咱們來看一下它的 **executed(RealCall call) ** 方法:
synchronized void executed(RealCall call) {
//將call對象加入網絡請求的同步隊列中
runningSyncCalls.add(call);
}
複製代碼
這是一個synchronized修飾的方法,爲了確保線程安全。Dispatcher中的executed(RealCall call)方法及其簡單,就是把Call對象加入到同步Call隊列中。對,你沒有看錯,它確實就只有這一行代碼,沒什麼複雜的操做。
說完了 Dispatcher 中的同步方法,咱們再來看一下異步:
synchronized void enqueue(AsyncCall call) {
//判斷Call對象應該添加到等待隊列仍是執行隊列
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//加入執行隊列
runningAsyncCalls.add(call);
//線程池開啓線程執行異步網絡請求
executorService().execute(call);
} else {
//加入等待隊列
readyAsyncCalls.add(call);
}
}
複製代碼
和同步方法相比,異步方法中的內容要稍微多一點。首先是判斷Call對象應該添加到等待隊列仍是執行隊列,這個判斷上面已經說過。加入執行隊列後,開啓線程池並執行Call對象。這裏須要注意的是異步請求時的Call對象和同步請求時不同,會轉換成一個 AsyncCall 對象,這個 AsyncCall 其實是一個 NamedRunnable ,那既然是一個 Runnable ,咱們確定要看一下它的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);
}
}
}
複製代碼
其實整段代碼看似很是多,核心就只有Response response = getResponseWithInterceptorChain()
這一句:經過攔截器鏈獲取網絡返回結果。其實不止是異步請求,同步請求的核心也是這一行代碼。咱們繼續看一下RealCall中的execute方法:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
//跟異步請求同樣,核心也是經過攔截器鏈來獲取網絡數據
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
複製代碼
很明顯,在client.dispatcher().executed(this)
將Call對象加入同步請求隊列中以後,一樣調用的是Response result = getResponseWithInterceptorChain()
。明白了嗎,不管是在同步請求或者是異步請求,最終獲取網絡數據的核心處理都是一致的:getResponseWithInterceptorChain()
。
接下來咱們來分析這個在OkHttp中很是核心的方法:
Response getResponseWithInterceptorChain() throws IOException {
//建立存放攔截器的list
List<Interceptor> interceptors = new ArrayList<>();
//攔截器列表加入咱們配置OkHttpClient時添加的攔截器
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) {
//若是不是針對WebSocket的網絡訪問,加入網絡攔截器
interceptors.addAll(client.networkInterceptors());
}
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);
}
複製代碼
在這個方法中首先建立了一個ArrayList,用來存放全部的攔截器。從上到下能夠看到,一共是添加了7中不一樣的攔截器:
在添加完各類攔截器後,建立了一個攔截器鏈,而後執行了攔截器鏈的proceed方法,咱們來看一下這個proceed方法:
@Override public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}
複製代碼
這個方法調用的是 RealInterceptorChain 內部的另外一個proceed方法,再跟進去看一下:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
//忽略無關代碼......
// 獲取攔截鏈中的下一個攔截器
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
//經過調用攔截器的intercept方法獲取網絡數據
Response response = interceptor.intercept(next);
//忽略無關代碼......
return response;
}
複製代碼
這個方法中主要分爲兩步:獲取攔截鏈中的下一個攔截器,而後調用這個攔截器的 intercept(next) 方法,在構建OkHttpClietn時添加過interceptor的同窗應該都比較清楚,在intercept()方法中咱們必需要存在chain.proceed(request)
這樣一句代碼。相似於這樣:
OkHttpClient.Builder().connectTimeout(
DEFAULT_MILLISECONDS, TimeUnit.SECONDS).readTimeout(
DEFAULT_MILLISECONDS, TimeUnit.SECONDS).addInterceptor { chain ->
val builder = chain.request().newBuilder()
headerMap?.forEach {
builder.addHeader(it.key, it.value)
}
val request = builder.build()
//將攔截器鏈執行下去
chain.proceed(request)
}.addInterceptor(httpLoggingInterceptor).build()
複製代碼
每一個攔截器內部的intercept方法內部必須存在chain.proceed(request)
,一樣,OkHttp提供的攔截器的intercept方法內部都必須存在chain.proceed(request)
這句代碼,除了最後一個攔截器CallServerInterceptor。
整個邏輯是否是有些混亂,不要緊,咱們來整理一下。
chain.proceed(request)
,因此可以結束循環調用。再來張圖,加深你們對攔截器鏈的理解:
看完這張圖,小夥伴們應該會對整個攔截器鏈的運做流程有必定的瞭解。到此爲止,OkHttp的源碼分析就告一段落了,具體每一個攔截器中的實現細節,你們若是有興趣的話能夠本身去深刻了解一下,我這裏就再也不贅述了。